XLS/XLSX/XLSB Interpret Chartsheets as Data Tables

- chartsheets are now stored as sheets with "!type" set to "chart"
- fixed Strict XML Workbook Relationships
- stubbed support for dialog and macro sheets
- removed legacy XLS `!range` field
This commit is contained in:
SheetJS 2017-03-27 17:35:15 -04:00
parent 471275b761
commit 663270b762
28 changed files with 796 additions and 235 deletions

@ -7,6 +7,10 @@ changes may not be included if they are not expected to break existing code.
## Unreleased
* XLS legacy `!range` field removed
## 0.9.6 (2017-03-25)
* `sheet_to_json` now passes `null` values when `raw` is set to `true`
* `sheet_to_json` treats `null` stub cells as values in conjunction with `raw`

@ -69,6 +69,10 @@ dist-deps: ## Copy dependencies for distribution
.PHONY: aux
aux: $(AUXTARGETS)
.PHONY: bytes
bytes: ## display minified and gzipped file sizes
for i in dist/xlsx.min.js dist/xlsx.{core,full}.min.js; do printj "%-30s %7d %10d" $$i $$(wc -c < $$i) $$(gzip --best --stdout $$i | wc -c); done
.PHONY: graph
graph: formats.png ## Rebuild format conversion graph
formats.png: formats.dot

@ -38,6 +38,7 @@ with a unified JS representation, and ES3/ES5 browser compatibility back to IE6.
+ [Data Types](#data-types)
+ [Dates](#dates)
* [Worksheet Object](#worksheet-object)
* [Chartsheet Object](#chartsheet-object)
* [Workbook Object](#workbook-object)
* [Document Features](#document-features)
+ [Formulae](#formulae)
@ -543,6 +544,13 @@ Special worksheet keys (accessible as `worksheet[key]`, each starting with `!`):
will write all cells in the merge range if they exist, so be sure that only
the first cell (upper-left) in the range is set.
### Chartsheet Object
Chartsheets are represented as standard worksheets. They are distinguished with
the `!type` property set to `"chart"`.
The underlying data and `!ref` refer to the cached data in the chartsheet.
### Workbook Object
`workbook.SheetNames` is an ordered list of the sheets in the workbook
@ -762,8 +770,8 @@ The exported `write` and `writeFile` functions accept an options argument:
- `bookSST` is slower and more memory intensive, but has better compatibility
with older versions of iOS Numbers
- The raw data is the only thing guaranteed to be saved. Formulae, formatting,
and other niceties may not be serialized (pending CSF standardization)
- The raw data is the only thing guaranteed to be saved. Features not described
in this README may not be serialized.
- `cellDates` only applies to XLSX output and is not guaranteed to work with
third-party readers. Excel itself does not usually write cells with type `d`
so non-Excel tools may ignore the data or blow up in the presence of dates.

@ -60,3 +60,15 @@ if (typeof exports !== 'undefined') {
_fs = require('fs');
}
}
function resolve_path(path/*:string*/, base/*:string*/)/*:string*/ {
var result = base.split('/');
if(base.slice(-1) != "/") result.pop(); // folder path
var target = path.split('/');
while (target.length !== 0) {
var step = target.shift();
if (step === '..') result.pop();
else if (step !== '.') result.push(step);
}
return result.join('/');
}

@ -10,17 +10,7 @@ var ct2type/*{[string]:string}*/ = ({
/* Worksheet */
"application/vnd.ms-excel.binIndexWs": "TODO", /* Binary Index */
/* Chartsheet */
"application/vnd.ms-excel.chartsheet": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml": "TODO",
/* Dialogsheet */
"application/vnd.ms-excel.dialogsheet": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml": "TODO",
/* Macrosheet */
"application/vnd.ms-excel.macrosheet": "TODO",
"application/vnd.ms-excel.macrosheet+xml": "TODO",
"application/vnd.ms-excel.intlmacrosheet": "TODO",
"application/vnd.ms-excel.binIndexMs": "TODO", /* Binary Index */
@ -31,6 +21,7 @@ var ct2type/*{[string]:string}*/ = ({
/* Custom Data Properties */
"application/vnd.openxmlformats-officedocument.customXmlProperties+xml": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty": "TODO",
/* Comments */
"application/vnd.ms-excel.comments": "comments",
@ -110,6 +101,9 @@ var ct2type/*{[string]:string}*/ = ({
/* Themes */
"application/vnd.openxmlformats-officedocument.theme+xml": "themes",
/* Theme Override */
"application/vnd.openxmlformats-officedocument.themeOverride+xml": "TODO",
/* Timeline */
"application/vnd.ms-excel.Timeline+xml": "TODO", /* verify */
"application/vnd.ms-excel.TimelineCache+xml": "TODO", /* verify */
@ -146,6 +140,9 @@ var ct2type/*{[string]:string}*/ = ({
"application/vnd.openxmlformats-package.relationships+xml": "rels",
"application/vnd.openxmlformats-officedocument.oleObject": "TODO",
/* Image */
"image/png": "TODO",
"sheet": "js"
}/*:any*/);
@ -161,11 +158,23 @@ var CT_LIST = (function(){
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
xlsb: "application/vnd.ms-excel.sharedStrings"
},
sheets: {
sheets: { /* Worksheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
xlsb: "application/vnd.ms-excel.worksheet"
},
styles: {/* Styles */
charts: { /* Chartsheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml",
xlsb: "application/vnd.ms-excel.chartsheet"
},
dialogs: { /* Dialogsheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml",
xlsb: "application/vnd.ms-excel.dialogsheet"
},
macros: { /* Macrosheet (Excel 4.0 Macros) */
xlsx: "application/vnd.ms-excel.macrosheet+xml",
xlsb: "application/vnd.ms-excel.macrosheet"
},
styles: { /* Styles */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
xlsb: "application/vnd.ms-excel.styles"
}
@ -180,9 +189,12 @@ var type2ct/*{[string]:Array<string>}*/ = evert_arr(ct2type);
XMLNS.CT = 'http://schemas.openxmlformats.org/package/2006/content-types';
function parse_ct(data/*:?string*/, opts) {
var ct = ({ workbooks: [], sheets: [], calcchains: [], themes: [], styles: [],
coreprops: [], extprops: [], custprops: [], strs:[], comments: [], vba: [],
TODO:[], rels:[], xmlns: "" }/*:any*/);
var ct = ({
workbooks:[], sheets:[], charts:[], dialogs:[],
rels:[], strs:[], comments:[],
coreprops:[], extprops:[], custprops:[], themes:[], styles:[],
calcchains:[], vba: [],
TODO:[], xmlns: "" }/*:any*/);
if(!data || !data.match) return ct;
var ctext = {};
(data.match(tagregex)||[]).forEach(function(x) {

@ -1,9 +1,15 @@
/* 9.3.2 OPC Relationships Markup */
/* 9.3 Relationships */
var RELS = ({
WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
}/*:any*/);
/* 9.3.3 Representing Relationships */
function get_rels_path(file/*:string*/)/*:string*/ {
var n = file.lastIndexOf("/");
return file.substr(0,n) + '/_rels' + file.substr(n) + ".rels";
}
function parse_rels(data/*:?string*/, currentFilePath/*:string*/) {
if (!data) return data;
if (currentFilePath.charAt(0) !== '/') {
@ -11,28 +17,13 @@ function parse_rels(data/*:?string*/, currentFilePath/*:string*/) {
}
var rels = {};
var hash = {};
var resolveRelativePathIntoAbsolute = function (to) {
var toksFrom = currentFilePath.split('/');
toksFrom.pop(); // folder path
var toksTo = to.split('/');
var reversed = [];
while (toksTo.length !== 0) {
var tokTo = toksTo.shift();
if (tokTo === '..') {
toksFrom.pop();
} else if (tokTo !== '.') {
toksFrom.push(tokTo);
}
}
return toksFrom.join('/');
};
(data.match(tagregex)||[]).forEach(function(x) {
var y = parsexmltag(x);
/* 9.3.2.2 OPC_Relationships */
if (y[0] === '<Relationship') {
var rel = {}; rel.Type = y.Type; rel.Target = y.Target; rel.Id = y.Id; rel.TargetMode = y.TargetMode;
var canonictarget = y.TargetMode === 'External' ? y.Target : resolveRelativePathIntoAbsolute(y.Target);
var canonictarget = y.TargetMode === 'External' ? y.Target : resolve_path(y.Target, currentFilePath);
rels[canonictarget] = rel;
hash[y.Id] = rel;
}

@ -33,9 +33,12 @@ function parse_ext_props(data, p) {
if(q.HeadingPairs && q.TitlesOfParts) {
var v = parseVector(q.HeadingPairs);
var j = 0, widx = 0, cidx = -1;
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);
switch(v[i].v) {
case "Worksheets":
case "工作表":
case "Листы":
case "ワークシート":
@ -47,17 +50,24 @@ function parse_ext_props(data, p) {
case "Folhas de cálculo":
case "Planilhas":
case "Werkbladen":
case "Worksheets": widx = j; p.Worksheets = +(v[++i].v); break;
p.Worksheets = len;
p.SheetNames = parts.slice(idx, idx + len);
break;
case "Named Ranges":
case "Benannte Bereiche":
case "Named Ranges": ++i; break; // TODO: Handle Named Ranges
p.NamedRanges = len;
p.DefinedNames = parts.slice(idx, idx + len);
break;
case "Charts": cidx = j; p.Charts = +(v[++i].v); break;
default: break; //throw new Error(v[i].v);
case "Charts":
case "Diagramme":
p.Chartsheets = len;
p.ChartNames = parts.slice(idx, idx + len);
break;
}
idx += len;
}
var parts = parseVector(q.TitlesOfParts).map(function(x) { return x.v; });
p.SheetNames = parts.slice(widx, widx + p.Worksheets);
}
return p;
}

@ -247,10 +247,10 @@ function parse_PropertySetStream(file, PIDSI) {
var SystemIdentifier = blob.read_shift(4);
blob.chk(CFB.utils.consts.HEADER_CLSID, 'CLSID: ');
NumSets = blob.read_shift(4);
if(NumSets !== 1 && NumSets !== 2) throw "Unrecognized #Sets: " + NumSets;
if(NumSets !== 1 && NumSets !== 2) throw new Error("Unrecognized #Sets: " + NumSets);
FMTID0 = blob.read_shift(16); Offset0 = blob.read_shift(4);
if(NumSets === 1 && Offset0 !== blob.l) throw "Length mismatch";
if(NumSets === 1 && Offset0 !== blob.l) throw new Error("Length mismatch: " + Offset0 + " !== " + blob.l);
else if(NumSets === 2) { FMTID1 = blob.read_shift(16); Offset1 = blob.read_shift(4); }
var PSet0 = parse_PropertySet(blob, PIDSI);
@ -405,7 +405,7 @@ var parse_HyperlinkMoniker = function(blob, length) {
switch(clsid) {
case "e0c9ea79f9bace118c8200aa004ba90b": return parse_URLMoniker(blob, length);
case "0303000000000000c000000000000046": return parse_FileMoniker(blob, length);
default: throw "unsupported moniker " + clsid;
default: throw new Error("Unsupported Moniker " + clsid);
}
};

@ -44,7 +44,7 @@ function parse_AddinUdf(blob, length, opts) {
var udfName = parse_ShortXLUnicodeString(blob, length, opts);
var cb = blob.read_shift(2);
l -= blob.l;
if(cb !== l) throw "Malformed AddinUdf: padding = " + l + " != " + cb;
if(cb !== l) throw new Error("Malformed AddinUdf: padding = " + l + " != " + cb);
blob.l += cb;
return udfName;
}
@ -127,7 +127,7 @@ function parse_FtArray(blob, length, ot) {
fts.push(FtTab[ft](blob, s + length - blob.l));
} catch(e) { blob.l = s + length; return fts; }
}
if(blob.l != s + length) blob.l = s + length; //throw "bad Object Ft-sequence";
if(blob.l != s + length) blob.l = s + length; //throw new Error("bad Object Ft-sequence");
return fts;
}
@ -138,8 +138,9 @@ var parse_FontIndex = parseuint16;
/* 2.4.21 */
function parse_BOF(blob, length) {
var o = {};
var o = {BIFFVer:0, dt:0};
o.BIFFVer = blob.read_shift(2); length -= 2;
if(length >= 2) { o.dt = blob.read_shift(2); blob.l -= 2; }
switch(o.BIFFVer) {
case 0x0600: /* BIFF8 */
case 0x0500: /* BIFF5 */
@ -147,6 +148,7 @@ function parse_BOF(blob, length) {
break;
default: if(length > 6) throw new Error("Unexpected BIFF Ver " + o.BIFFVer);
}
blob.read_shift(length);
return o;
}
@ -156,7 +158,7 @@ function parse_BOF(blob, length) {
function parse_InterfaceHdr(blob, length) {
if(length === 0) return 0x04b0;
var q;
if((q=blob.read_shift(2))!==0x04b0) throw 'InterfaceHdr codePage ' + q;
if((q=blob.read_shift(2))!==0x04b0) throw new Error("InterfaceHdr codePage " + q);
return 0x04b0;
}
@ -222,7 +224,7 @@ function parse_Row(blob, length) {
/* 2.4.125 */
function parse_ForceFullCalculation(blob, length) {
var header = parse_frtHeader(blob);
if(header.type != 0x08A3) throw "Invalid Future Record " + header.type;
if(header.type != 0x08A3) throw new Error("Invalid Future Record " + header.type);
var fullcalc = blob.read_shift(4);
return fullcalc !== 0x0;
}
@ -311,9 +313,9 @@ function parse_MulRk(blob, length) {
var rw = blob.read_shift(2), col = blob.read_shift(2);
var rkrecs = [];
while(blob.l < target) rkrecs.push(parse_RkRec(blob));
if(blob.l !== target) throw "MulRK read error";
if(blob.l !== target) throw new Error("MulRK read error");
var lastcol = blob.read_shift(2);
if(rkrecs.length != lastcol - col + 1) throw "MulRK length mismatch";
if(rkrecs.length != lastcol - col + 1) throw new Error("MulRK length mismatch");
return {r:rw, c:col, C:lastcol, rkrec:rkrecs};
}
/* 2.4.174 */
@ -322,9 +324,9 @@ function parse_MulBlank(blob, length) {
var rw = blob.read_shift(2), col = blob.read_shift(2);
var ixfes = [];
while(blob.l < target) ixfes.push(blob.read_shift(2));
if(blob.l !== target) throw "MulBlank read error";
if(blob.l !== target) throw new Error("MulBlank read error");
var lastcol = blob.read_shift(2);
if(ixfes.length != lastcol - col + 1) throw "MulBlank length mismatch";
if(ixfes.length != lastcol - col + 1) throw new Error("MulBlank length mismatch");
return {r:rw, c:col, C:lastcol, ixfe:ixfes};
}
@ -388,7 +390,7 @@ function parse_Guts(blob, length) {
var out = [blob.read_shift(2), blob.read_shift(2)];
if(out[0] !== 0) out[0]--;
if(out[1] !== 0) out[1]--;
if(out[0] > 7 || out[1] > 7) throw "Bad Gutters: " + out.join("|");
if(out[0] > 7 || out[1] > 7) throw new Error("Bad Gutters: " + out.join("|"));
return out;
}
@ -555,14 +557,14 @@ try {
//var fmla = parse_ObjFmla(blob, s + length - blob.l);
for(var i = 1; i < blob.lens.length-1; ++i) {
if(blob.l-s != blob.lens[i]) throw "TxO: bad continue record";
if(blob.l-s != blob.lens[i]) throw new Error("TxO: bad continue record");
var hdr = blob[blob.l];
var t = parse_XLUnicodeStringNoCch(blob, blob.lens[i+1]-blob.lens[i]-1);
texts += t;
if(texts.length >= (hdr ? cchText : 2*cchText)) break;
}
if(texts.length !== cchText && texts.length !== cchText*2) {
throw "cchText: " + cchText + " != " + texts.length;
throw new Error("cchText: " + cchText + " != " + texts.length);
}
blob.l = s + length;
@ -570,9 +572,9 @@ try {
// var rgTxoRuns = [];
// for(var j = 0; j != cbRuns/8-1; ++j) blob.l += 8;
// var cchText2 = blob.read_shift(2);
// if(cchText2 !== cchText) throw "TxOLastRun mismatch: " + cchText2 + " " + cchText;
// if(cchText2 !== cchText) throw new Error("TxOLastRun mismatch: " + cchText2 + " " + cchText);
// blob.l += 6;
// if(s + length != blob.l) throw "TxO " + (s + length) + ", at " + blob.l;
// if(s + length != blob.l) throw new Error("TxO " + (s + length) + ", at " + blob.l);
return { t: texts };
} catch(e) { blob.l = s + length; return { t: texts }; }
}
@ -642,6 +644,15 @@ function parse_ColInfo(blob, length, opts) {
return {s:colFirst, e:colLast, w:coldx, ixfe:ixfe, flags:flags};
}
/* 2.4.261 */
function parse_ShtProps(blob, length, opts) {
var def = {area:false};
if(opts.biff != 5) { blob.l += length; return def; }
var d = blob.read_shift(1); blob.l += 3;
if((d & 0x10)) def.area = true;
return def;
}
var parse_Style = parsenoop;
var parse_StyleExt = parsenoop;
@ -934,7 +945,6 @@ var parse_Surf = parsenoop;
var parse_RadarArea = parsenoop;
var parse_AxisParent = parsenoop;
var parse_LegendException = parsenoop;
var parse_ShtProps = parsenoop;
var parse_SerToCrt = parsenoop;
var parse_AxesUsed = parsenoop;
var parse_SBaseRef = parsenoop;

@ -267,7 +267,7 @@ function parse_cellXfs(t, styles, opts) {
/* 18.2.10 extLst CT_ExtensionList ? */
case '<extLst': case '</extLst>': break;
case '<ext': break;
default: if(opts.WTF) throw 'unrecognized ' + y[0] + ' in cellXfs';
default: if(opts.WTF) throw new Error('unrecognized ' + y[0] + ' in cellXfs');
}
});
}

@ -104,7 +104,7 @@ function parse_theme_xml(data/*:string*/, opts) {
var themes = {};
/* themeElements CT_BaseStyles */
if(!(t=data.match(themeltregex))) throw 'themeElements not found in theme';
if(!(t=data.match(themeltregex))) throw new Error('themeElements not found in theme');
parse_themeElements(t[0], themes, opts);
return themes;

18
bits/54_drawing.js Normal file

@ -0,0 +1,18 @@
/* 20.5 DrawingML - SpreadsheetML Drawing */
function parse_drawing(data, rels/*:any*/) {
if(!data) return "??";
/*
Chartsheet Drawing:
- 20.5.2.35 wsDr CT_Drawing
- 20.5.2.1 absoluteAnchor CT_AbsoluteAnchor
- 20.5.2.16 graphicFrame CT_GraphicalObjectFrame
- 20.1.2.2.16 graphic CT_GraphicalObject
- 20.1.2.2.17 graphicData CT_GraphicalObjectData
- chart reference
the actual type is based on the URI of the graphicData
TODO: handle embedded charts and other types of graphics
*/
var id = (data.match(/<c:chart [^>]*r:id="([^"]*)"/)||["",""])[1];
return rels['!id'][id].Target;
}

@ -1,7 +1,10 @@
var strs = {}; // shared strings
var _ssfopts = {}; // spreadsheet formatting options
RELS.WS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
RELS.WS = [
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
"http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet"
];
function get_sst_id(sst/*:SST*/, str/*:string*/)/*:number*/ {
for(var i = 0, len = sst.length; i < len; ++i) if(sst[i].t === str) { sst.Count ++; return i; }

41
bits/69_chartxml.js Normal file

@ -0,0 +1,41 @@
function parse_numCache(data) {
var col = [];
/* 21.2.2.150 pt CT_NumVal */
(data.match(/<c:pt idx="(\d*)">(.*?)<\/c:pt>/mg)||[]).forEach(function(pt) {
var q = pt.match(/<c:pt idx="(.*?)"><c:v>(.*)<\/c:v><\/c:pt>/);
if(!q) return;
col[+q[1]] = +q[2];
});
/* 21.2.2.71 formatCode CT_Xstring */
var nf = unescapexml((data.match(/<c:formatCode>(.*?)<\/c:formatCode>/) || ["","General"])[1]);
return [col, nf];
}
/* 21.2 DrawingML - Charts */
function parse_chart(data, name/*:string*/, opts, rels, wb, csheet) {
var cs = ((csheet || {"!type":"chart"})/*:any*/);
if(!data) return csheet;
/* 21.2.2.27 chart CT_Chart */
var C = 0, R = 0, col = "A";
var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
/* 21.2.2.120 numCache CT_NumData */
(data.match(/<c:numCache>.*?<\/c:numCache>/gm)||[]).forEach(function(nc) {
var cache = parse_numCache(nc);
refguess.s.r = refguess.s.c = 0;
refguess.e.c = C;
col = encode_col(C);
cache[0].forEach(function(n,i) {
cs[col + encode_row(i)] = {t:'n', v:n, z:cache[1] };
R = i;
});
if(refguess.e.r < R) refguess.e.r = R;
++C;
});
if(C > 0) cs["!ref"] = encode_range(refguess);
return cs;
}

47
bits/70_csheet.js Normal file

@ -0,0 +1,47 @@
RELS.CS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet";
/* 18.3 Worksheets also covers Chartsheets */
function parse_cs_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
/* 18.3.1.12 chartsheet CT_ChartSheet */
if(!rels) rels = {'!id':{}};
var s = {'!type':"chart", '!chart':null, '!rel':""};
var m;
/* 18.3.1.36 drawing CT_Drawing */
if((m = data.match(/drawing r:id="(.*?)"/))) s['!rel'] = m[1];
if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']];
return s;
}
/* [MS-XLSB] 2.1.7.7 Chart Sheet */
function parse_cs_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
if(!rels) rels = {'!id':{}};
var s = {'!type':"chart", '!chart':null, '!rel':""};
var pass = false;
recordhopper(data, function cs_parse(val, Record) {
switch(Record.n) {
case 'BrtDrawing': s['!rel'] = val; break;
case 'BrtBeginSheet': break;
case 'BrtCsProp': break; // TODO
case 'BrtBeginCsViews': break; // TODO
case 'BrtBeginCsView': break; // TODO
case 'BrtEndCsView': break; // TODO
case 'BrtEndCsViews': break; // TODO
case 'BrtCsProtection': break; // TODO
case 'BrtMargins': break; // TODO
case 'BrtCsPageSetup': break; // TODO
case 'BrtEndSheet': break; // TODO
case 'BrtBeginHeaderFooter': break; // TODO
case 'BrtEndHeaderFooter': break; // TODO
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + Record.n);
}
}, opts);
if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']];
return s;
}

@ -8,6 +8,11 @@ function parse_ws(data, name/*:string*/, opts, rels, wb, themes, styles)/*:Works
return parse_ws_xml((data/*:any*/), opts, rels, wb, themes, styles);
}
function parse_cs(data, name/*:string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(name.slice(-4)===".bin") return parse_cs_bin((data/*:any*/), opts, rels, wb, themes, styles);
return parse_cs_xml((data/*:any*/), opts, rels, wb, themes, styles);
}
function parse_sty(data, name/*:string*/, themes, opts) {
if(name.slice(-4)===".bin") return parse_sty_bin((data/*:any*/), themes, opts);
return parse_sty_xml((data/*:any*/), themes, opts);

@ -260,7 +260,6 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
case 'EOF': {
if(--file_depth) break;
if(range.e) {
out["!range"] = range;
if(range.e.r > 0 && range.e.c > 0) {
range.e.r--; range.e.c--;
out["!ref"] = encode_range(range);
@ -295,6 +294,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
opts.snames.push(cur_sheet);
}
else cur_sheet = (Directory[s] || {name:""}).name;
if(val.dt == 0x20) out["!type"] = "chart";
mergecells = [];
objects = [];
array_formulae = []; opts.arrayf = array_formulae;
@ -304,6 +304,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
} break;
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
if(out["!type"] == "chart" && out[encode_cell({c:val.c, r:val.r})]) ++val.c;
temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:'n'};
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);

@ -475,7 +475,7 @@ var XLSBRecordEnum = {
/*::[*/0x021E/*::]*/: { n:"BrtBeginECTwFldInfo", f:parsenoop },
/*::[*/0x0224/*::]*/: { n:"BrtFileSharing", f:parsenoop },
/*::[*/0x0225/*::]*/: { n:"BrtOleSize", f:parsenoop },
/*::[*/0x0226/*::]*/: { n:"BrtDrawing", f:parsenoop },
/*::[*/0x0226/*::]*/: { n:"BrtDrawing", f:parse_RelID },
/*::[*/0x0227/*::]*/: { n:"BrtLegacyDrawing", f:parsenoop },
/*::[*/0x0228/*::]*/: { n:"BrtLegacyDrawingHF", f:parsenoop },
/*::[*/0x0229/*::]*/: { n:"BrtWebOpt", f:parsenoop },

@ -1,15 +1,37 @@
function safe_parse_wbrels(wbrels, sheets) {
if(!wbrels) return 0;
function get_type(n) {
if(RELS.WS.indexOf(n) > -1) return "sheet";
if(RELS.CS && n == RELS.CS) return "chart";
if(RELS.DS && n == RELS.DS) return "dialog";
if(RELS.MS && n == RELS.MS) return "macro";
if(!n || !n.length) return "sheet";
return n;
}
try {
wbrels = sheets.map(function pwbr(w) { return [w.name, wbrels['!id'][w.id].Target]; });
wbrels = sheets.map(function pwbr(w) { if(!w.id) w.id = w.strRelID; return [w.name, wbrels['!id'][w.id].Target, get_type(wbrels['!id'][w.id].Type)]; });
} catch(e) { return null; }
return !wbrels || wbrels.length === 0 ? null : wbrels;
}
function safe_parse_ws(zip, path/*:string*/, relsPath/*:string*/, sheet, sheetRels, sheets, opts, wb, themes, styles) {
function safe_parse_sheet(zip, path/*:string*/, relsPath/*:string*/, sheet, sheetRels, sheets, stype/*:string*/, opts, wb, themes, styles) {
try {
sheetRels[sheet]=parse_rels(getzipstr(zip, relsPath, true), path);
sheets[sheet]=parse_ws(getzipdata(zip, path),path,opts,sheetRels[sheet], wb, themes, styles);
var data = getzipdata(zip, path);
switch(stype) {
case 'sheet': sheets[sheet]=parse_ws(data, path, opts,sheetRels[sheet], wb, themes, styles); break;
case 'chart':
var cs = parse_cs(data, path, opts,sheetRels[sheet], wb, themes, styles);
sheets[sheet] = cs;
if(!cs || !cs['!chart']) break;
var dfile = resolve_path(cs['!chart'].Target, path);
var drelsp = get_rels_path(dfile);
var draw = parse_drawing(getzipstr(zip, dfile, true), parse_rels(getzipstr(zip,drelsp,true), dfile));
var chartp = resolve_path(draw, dfile);
var crelsp = get_rels_path(chartp);
cs = parse_chart(getzipstr(zip, chartp, true), chartp, opts, parse_rels(getzipstr(zip, crelsp,true), chartp), wb, cs);
break;
}
} catch(e) { if(opts.WTF) throw e; }
}
@ -76,8 +98,8 @@ function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
var out = ({}/*:any*/);
if(opts.bookSheets || opts.bookProps) {
if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames;
else if(wb.Sheets) sheets = wb.Sheets.map(function pluck(x){ return x.name; });
if(wb.Sheets) sheets = wb.Sheets.map(function pluck(x){ return x.name; });
else if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames;
if(opts.bookProps) { out.Props = props; out.Custprops = custprops; }
if(opts.bookSheets && typeof sheets !== 'undefined') out.SheetNames = sheets;
if(opts.bookSheets ? out.SheetNames : opts.bookProps) return out;
@ -106,13 +128,16 @@ function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
/* Numbers iOS hack */
var nmode = (getzipdata(zip,"xl/worksheets/sheet.xml",true))?1:0;
for(i = 0; i != props.Worksheets; ++i) {
if(wbrels && wbrels[i]) path = 'xl/' + (wbrels[i][1]).replace(/[\/]?xl\//, "");
else {
var stype = "sheet";
if(wbrels && wbrels[i]) {
path = 'xl/' + (wbrels[i][1]).replace(/[\/]?xl\//, "");
stype = wbrels[i][2];
} else {
path = 'xl/worksheets/sheet'+(i+1-nmode)+"." + wbext;
path = path.replace(/sheet0\./,"sheet.");
}
relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels");
safe_parse_ws(zip, path, relsPath, props.SheetNames[i], sheetRels, sheets, opts, wb, themes, styles);
safe_parse_sheet(zip, path, relsPath, props.SheetNames[i], sheetRels, sheets, stype, opts, wb, themes, styles);
}
if(dir.comments) parse_comments(zip, dir.comments, sheets, sheetRels, opts);

@ -65,7 +65,7 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
f = "xl/worksheets/sheet" + rId + "." + wbext;
zip.file(f, write_ws(rId-1, f, opts, wb));
ct.sheets.push(f);
add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS);
add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
}
if(opts.Strings != null && opts.Strings.length > 0) {

7
docbits/54_shobject.md Normal file

@ -0,0 +1,7 @@
### Chartsheet Object
Chartsheets are represented as standard worksheets. They are distinguished with
the `!type` property set to `"chart"`.
The underlying data and `!ref` refer to the cached data in the chartsheet.

@ -13,8 +13,8 @@ The exported `write` and `writeFile` functions accept an options argument:
- `bookSST` is slower and more memory intensive, but has better compatibility
with older versions of iOS Numbers
- The raw data is the only thing guaranteed to be saved. Formulae, formatting,
and other niceties may not be serialized (pending CSF standardization)
- The raw data is the only thing guaranteed to be saved. Features not described
in this README may not be serialized.
- `cellDates` only applies to XLSX output and is not guaranteed to work with
third-party readers. Excel itself does not usually write cells with type `d`
so non-Excel tools may ignore the data or blow up in the presence of dates.

22
test.js

@ -88,15 +88,17 @@ function parsetest(x, wb, full, ext) {
ext = (ext ? " [" + ext + "]": "");
if(!full && ext) return;
describe(x + ext + ' should have all bits', function() {
var sname = dir + '2011/' + x.substr(x.lastIndexOf('/')+1) + '.sheetnames';
var sname = dir + '2016/' + x.substr(x.lastIndexOf('/')+1) + '.sheetnames';
if(!fs.existsSync(sname)) sname = dir + '2011/' + x.substr(x.lastIndexOf('/')+1) + '.sheetnames';
if(!fs.existsSync(sname)) sname = dir + '2013/' + x.substr(x.lastIndexOf('/')+1) + '.sheetnames';
it('should have all sheets', function() {
wb.SheetNames.forEach(function(y) { assert(wb.Sheets[y], 'bad sheet ' + y); });
});
it('should have the right sheet names', fs.existsSync(sname) ? function() {
if(fs.existsSync(sname)) it('should have the right sheet names', function() {
var file = fs.readFileSync(sname, 'utf-8').replace(/\r/g,"");
var names = wb.SheetNames.map(fixsheetname).join("\n") + "\n";
assert.equal(names, file);
} : null);
if(file.length) assert.equal(names, file);
});
});
describe(x + ext + ' should generate CSV', function() {
wb.SheetNames.forEach(function(ws, i) {
@ -140,11 +142,11 @@ function parsetest(x, wb, full, ext) {
describe(x + ext + ' should generate correct CSV output', function() {
wb.SheetNames.forEach(function(ws, i) {
var name = getfile(dir, x, i, ".csv");
it('#' + i + ' (' + ws + ')', fs.existsSync(name) ? function() {
if(fs.existsSync(name)) it('#' + i + ' (' + ws + ')', function() {
var file = fs.readFileSync(name, 'utf-8');
var csv = X.utils.make_csv(wb.Sheets[ws]);
assert.equal(fixcsv(csv), fixcsv(file), "CSV badness");
} : null);
});
});
});
describe(x + ext + ' should generate correct JSON output', function() {
@ -188,8 +190,8 @@ var wbtable = {};
describe('should parse test files', function() {
files.forEach(function(x) {
if(!fs.existsSync(dir + x)) return;
it(x, x.substr(-8) == ".pending" ? null : function() {
if(x.slice(-8) == ".pending" || !fs.existsSync(dir + x)) return;
it(x, function() {
var wb = X.readFile(dir + x, opts);
wbtable[dir + x] = wb;
parsetest(x, wb, true);
@ -206,8 +208,8 @@ describe('should parse test files', function() {
});
});
fileA.forEach(function(x) {
if(!fs.existsSync(dir + x)) return;
it(x, x.substr(-8) == ".pending" ? null : function() {
if(x.slice(-8) == ".pending" || !fs.existsSync(dir + x)) return;
it(x, function() {
var wb = X.readFile(dir + x, {WTF:opts.wtf, sheetRows:10});
parsetest(x, wb, false);
});

@ -1 +1 @@
Subproject commit 7fe887ff7b26b233db24f3d4df513319c493efc8
Subproject commit cbafd3a184d0c668aaae1f9f8420ccaed74ad8e5

@ -1,15 +1,24 @@
AutoFilter.xlsb
BlankSheetTypes.xlsb.pending
BlankSheetTypes.xlsb.pending # macrosheets
ErrorTypes.xlsb
NumberFormatCondition.xlsb
RkNumber.xlsb
apachepoi_Simple.xlsb.pending
apachepoi_51519.xlsb
# apachepoi_Simple.xlsb # Beta file
apachepoi_WithTextBox.xlsb
apachepoi_comments.xlsb
apachepoi_date.xlsb
apachepoi_hyperlink.xlsb
apachepoi_protected_passtika.xlsb.pending
apachepoi_sample.xlsb
apachepoi_testVarious.xlsb
calendar_stress_test.xlsb.pending
cell_style_simple.xlsb
column_width.xlsb
comments_stress_test.xlsb
custom_properties.xlsb
defined_names_simple.xlsb
# formula_stress_test.xlsb # xlml
formula_stress_test.xlsb
formulae_test_simple.xlsb
hyperlink_no_rels.xlsb
hyperlink_stress_test_2011.xlsb
@ -22,8 +31,10 @@ number_format_russian.xlsb
numfmt_1_russian.xlsb
phonetic_text.xlsb
pivot_table_named_range.xlsb
# pivot_table_test.xlsb # xlml
pivot_table_test.xlsb
rich_text_stress.xlsb
row_height.xlsb
sized_boxen.xlsb
smart_tags_2007.xlsb
sushi.xlsb
text_and_numbers.xlsb
@ -226,7 +237,7 @@ apachepoi_Themes2.xlsx
apachepoi_TwoSheetsNoneHidden.xlsx
apachepoi_TwoSheetsOneHidden.xlsx
apachepoi_WithChart.xlsx
apachepoi_WithChartSheet.xlsx.pending
apachepoi_WithChartSheet.xlsx
apachepoi_WithConditionalFormatting.xlsx
apachepoi_WithDrawing.xlsx
apachepoi_WithEmbeded.xlsx
@ -242,7 +253,7 @@ apachepoi_atp.xlsx
apachepoi_bug60858.xlsx
apachepoi_chartTitle_noTitle.xlsx
apachepoi_chartTitle_withTitle.xlsx
apachepoi_chart_sheet.xlsx.pending
apachepoi_chart_sheet.xlsx
apachepoi_commentTest.xlsx
apachepoi_comments.xlsx
apachepoi_craftonhills.edu_programreview_report.aspx_goalpriorityreport_0011d159-1eeb-4b63-8833-867b0926e5f3.xlsx
@ -343,7 +354,7 @@ openpyxl_r_bug304.xlsx.pending
openpyxl_r_comments.xlsx
openpyxl_r_complex-styles.xlsx
openpyxl_r_conditional-formatting.xlsx
openpyxl_r_contains_chartsheets.xlsx.pending
openpyxl_r_contains_chartsheets.xlsx
openpyxl_r_date_1900.xlsx
openpyxl_r_date_1904.xlsx
openpyxl_r_formulae.xlsx

@ -1533,6 +1533,18 @@ if (typeof exports !== 'undefined') {
_fs = require('fs');
}
}
function resolve_path(path/*:string*/, base/*:string*/)/*:string*/ {
var result = base.split('/');
if(base.slice(-1) != "/") result.pop(); // folder path
var target = path.split('/');
while (target.length !== 0) {
var step = target.shift();
if (step === '..') result.pop();
else if (step !== '.') result.push(step);
}
return result.join('/');
}
var attregexg=/([^\s?>\/]+)=((?:")([^"]*)(?:")|(?:')([^']*)(?:'))/g;
var tagregex=/<[^>]*>/g;
var nsregex=/<\w*:/, nsregex2 = /<(\/?)\w+:/;
@ -2552,17 +2564,7 @@ var ct2type/*{[string]:string}*/ = ({
/* Worksheet */
"application/vnd.ms-excel.binIndexWs": "TODO", /* Binary Index */
/* Chartsheet */
"application/vnd.ms-excel.chartsheet": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml": "TODO",
/* Dialogsheet */
"application/vnd.ms-excel.dialogsheet": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml": "TODO",
/* Macrosheet */
"application/vnd.ms-excel.macrosheet": "TODO",
"application/vnd.ms-excel.macrosheet+xml": "TODO",
"application/vnd.ms-excel.intlmacrosheet": "TODO",
"application/vnd.ms-excel.binIndexMs": "TODO", /* Binary Index */
@ -2573,6 +2575,7 @@ var ct2type/*{[string]:string}*/ = ({
/* Custom Data Properties */
"application/vnd.openxmlformats-officedocument.customXmlProperties+xml": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty": "TODO",
/* Comments */
"application/vnd.ms-excel.comments": "comments",
@ -2652,6 +2655,9 @@ var ct2type/*{[string]:string}*/ = ({
/* Themes */
"application/vnd.openxmlformats-officedocument.theme+xml": "themes",
/* Theme Override */
"application/vnd.openxmlformats-officedocument.themeOverride+xml": "TODO",
/* Timeline */
"application/vnd.ms-excel.Timeline+xml": "TODO", /* verify */
"application/vnd.ms-excel.TimelineCache+xml": "TODO", /* verify */
@ -2688,6 +2694,9 @@ var ct2type/*{[string]:string}*/ = ({
"application/vnd.openxmlformats-package.relationships+xml": "rels",
"application/vnd.openxmlformats-officedocument.oleObject": "TODO",
/* Image */
"image/png": "TODO",
"sheet": "js"
}/*:any*/);
@ -2703,11 +2712,23 @@ var CT_LIST = (function(){
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
xlsb: "application/vnd.ms-excel.sharedStrings"
},
sheets: {
sheets: { /* Worksheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
xlsb: "application/vnd.ms-excel.worksheet"
},
styles: {/* Styles */
charts: { /* Chartsheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml",
xlsb: "application/vnd.ms-excel.chartsheet"
},
dialogs: { /* Dialogsheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml",
xlsb: "application/vnd.ms-excel.dialogsheet"
},
macros: { /* Macrosheet (Excel 4.0 Macros) */
xlsx: "application/vnd.ms-excel.macrosheet+xml",
xlsb: "application/vnd.ms-excel.macrosheet"
},
styles: { /* Styles */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
xlsb: "application/vnd.ms-excel.styles"
}
@ -2722,9 +2743,12 @@ var type2ct/*{[string]:Array<string>}*/ = evert_arr(ct2type);
XMLNS.CT = 'http://schemas.openxmlformats.org/package/2006/content-types';
function parse_ct(data/*:?string*/, opts) {
var ct = ({ workbooks: [], sheets: [], calcchains: [], themes: [], styles: [],
coreprops: [], extprops: [], custprops: [], strs:[], comments: [], vba: [],
TODO:[], rels:[], xmlns: "" }/*:any*/);
var ct = ({
workbooks:[], sheets:[], charts:[], dialogs:[],
rels:[], strs:[], comments:[],
coreprops:[], extprops:[], custprops:[], themes:[], styles:[],
calcchains:[], vba: [],
TODO:[], xmlns: "" }/*:any*/);
if(!data || !data.match) return ct;
var ctext = {};
(data.match(tagregex)||[]).forEach(function(x) {
@ -2799,12 +2823,18 @@ function write_ct(ct, opts)/*:string*/ {
if(o.length>2){ o[o.length] = ('</Types>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}
/* 9.3.2 OPC Relationships Markup */
/* 9.3 Relationships */
var RELS = ({
WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
}/*:any*/);
/* 9.3.3 Representing Relationships */
function get_rels_path(file/*:string*/)/*:string*/ {
var n = file.lastIndexOf("/");
return file.substr(0,n) + '/_rels' + file.substr(n) + ".rels";
}
function parse_rels(data/*:?string*/, currentFilePath/*:string*/) {
if (!data) return data;
if (currentFilePath.charAt(0) !== '/') {
@ -2812,28 +2842,13 @@ function parse_rels(data/*:?string*/, currentFilePath/*:string*/) {
}
var rels = {};
var hash = {};
var resolveRelativePathIntoAbsolute = function (to) {
var toksFrom = currentFilePath.split('/');
toksFrom.pop(); // folder path
var toksTo = to.split('/');
var reversed = [];
while (toksTo.length !== 0) {
var tokTo = toksTo.shift();
if (tokTo === '..') {
toksFrom.pop();
} else if (tokTo !== '.') {
toksFrom.push(tokTo);
}
}
return toksFrom.join('/');
};
(data.match(tagregex)||[]).forEach(function(x) {
var y = parsexmltag(x);
/* 9.3.2.2 OPC_Relationships */
if (y[0] === '<Relationship') {
var rel = {}; rel.Type = y.Type; rel.Target = y.Target; rel.Id = y.Id; rel.TargetMode = y.TargetMode;
var canonictarget = y.TargetMode === 'External' ? y.Target : resolveRelativePathIntoAbsolute(y.Target);
var canonictarget = y.TargetMode === 'External' ? y.Target : resolve_path(y.Target, currentFilePath);
rels[canonictarget] = rel;
hash[y.Id] = rel;
}
@ -3024,9 +3039,12 @@ function parse_ext_props(data, p) {
if(q.HeadingPairs && q.TitlesOfParts) {
var v = parseVector(q.HeadingPairs);
var j = 0, widx = 0, cidx = -1;
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);
switch(v[i].v) {
case "Worksheets":
case "工作表":
case "Листы":
case "ワークシート":
@ -3038,17 +3056,24 @@ function parse_ext_props(data, p) {
case "Folhas de cálculo":
case "Planilhas":
case "Werkbladen":
case "Worksheets": widx = j; p.Worksheets = +(v[++i].v); break;
p.Worksheets = len;
p.SheetNames = parts.slice(idx, idx + len);
break;
case "Named Ranges":
case "Benannte Bereiche":
case "Named Ranges": ++i; break; // TODO: Handle Named Ranges
p.NamedRanges = len;
p.DefinedNames = parts.slice(idx, idx + len);
break;
case "Charts": cidx = j; p.Charts = +(v[++i].v); break;
default: break; //throw new Error(v[i].v);
case "Charts":
case "Diagramme":
p.Chartsheets = len;
p.ChartNames = parts.slice(idx, idx + len);
break;
}
idx += len;
}
var parts = parseVector(q.TitlesOfParts).map(function(x) { return x.v; });
p.SheetNames = parts.slice(widx, widx + p.Worksheets);
}
return p;
}
@ -3465,10 +3490,10 @@ function parse_PropertySetStream(file, PIDSI) {
var SystemIdentifier = blob.read_shift(4);
blob.chk(CFB.utils.consts.HEADER_CLSID, 'CLSID: ');
NumSets = blob.read_shift(4);
if(NumSets !== 1 && NumSets !== 2) throw "Unrecognized #Sets: " + NumSets;
if(NumSets !== 1 && NumSets !== 2) throw new Error("Unrecognized #Sets: " + NumSets);
FMTID0 = blob.read_shift(16); Offset0 = blob.read_shift(4);
if(NumSets === 1 && Offset0 !== blob.l) throw "Length mismatch";
if(NumSets === 1 && Offset0 !== blob.l) throw new Error("Length mismatch: " + Offset0 + " !== " + blob.l);
else if(NumSets === 2) { FMTID1 = blob.read_shift(16); Offset1 = blob.read_shift(4); }
var PSet0 = parse_PropertySet(blob, PIDSI);
@ -3623,7 +3648,7 @@ var parse_HyperlinkMoniker = function(blob, length) {
switch(clsid) {
case "e0c9ea79f9bace118c8200aa004ba90b": return parse_URLMoniker(blob, length);
case "0303000000000000c000000000000046": return parse_FileMoniker(blob, length);
default: throw "unsupported moniker " + clsid;
default: throw new Error("Unsupported Moniker " + clsid);
}
};
@ -3708,7 +3733,7 @@ function parse_AddinUdf(blob, length, opts) {
var udfName = parse_ShortXLUnicodeString(blob, length, opts);
var cb = blob.read_shift(2);
l -= blob.l;
if(cb !== l) throw "Malformed AddinUdf: padding = " + l + " != " + cb;
if(cb !== l) throw new Error("Malformed AddinUdf: padding = " + l + " != " + cb);
blob.l += cb;
return udfName;
}
@ -3791,7 +3816,7 @@ function parse_FtArray(blob, length, ot) {
fts.push(FtTab[ft](blob, s + length - blob.l));
} catch(e) { blob.l = s + length; return fts; }
}
if(blob.l != s + length) blob.l = s + length; //throw "bad Object Ft-sequence";
if(blob.l != s + length) blob.l = s + length; //throw new Error("bad Object Ft-sequence");
return fts;
}
@ -3802,8 +3827,9 @@ var parse_FontIndex = parseuint16;
/* 2.4.21 */
function parse_BOF(blob, length) {
var o = {};
var o = {BIFFVer:0, dt:0};
o.BIFFVer = blob.read_shift(2); length -= 2;
if(length >= 2) { o.dt = blob.read_shift(2); blob.l -= 2; }
switch(o.BIFFVer) {
case 0x0600: /* BIFF8 */
case 0x0500: /* BIFF5 */
@ -3811,6 +3837,7 @@ function parse_BOF(blob, length) {
break;
default: if(length > 6) throw new Error("Unexpected BIFF Ver " + o.BIFFVer);
}
blob.read_shift(length);
return o;
}
@ -3820,7 +3847,7 @@ function parse_BOF(blob, length) {
function parse_InterfaceHdr(blob, length) {
if(length === 0) return 0x04b0;
var q;
if((q=blob.read_shift(2))!==0x04b0) throw 'InterfaceHdr codePage ' + q;
if((q=blob.read_shift(2))!==0x04b0) throw new Error("InterfaceHdr codePage " + q);
return 0x04b0;
}
@ -3886,7 +3913,7 @@ function parse_Row(blob, length) {
/* 2.4.125 */
function parse_ForceFullCalculation(blob, length) {
var header = parse_frtHeader(blob);
if(header.type != 0x08A3) throw "Invalid Future Record " + header.type;
if(header.type != 0x08A3) throw new Error("Invalid Future Record " + header.type);
var fullcalc = blob.read_shift(4);
return fullcalc !== 0x0;
}
@ -3975,9 +4002,9 @@ function parse_MulRk(blob, length) {
var rw = blob.read_shift(2), col = blob.read_shift(2);
var rkrecs = [];
while(blob.l < target) rkrecs.push(parse_RkRec(blob));
if(blob.l !== target) throw "MulRK read error";
if(blob.l !== target) throw new Error("MulRK read error");
var lastcol = blob.read_shift(2);
if(rkrecs.length != lastcol - col + 1) throw "MulRK length mismatch";
if(rkrecs.length != lastcol - col + 1) throw new Error("MulRK length mismatch");
return {r:rw, c:col, C:lastcol, rkrec:rkrecs};
}
/* 2.4.174 */
@ -3986,9 +4013,9 @@ function parse_MulBlank(blob, length) {
var rw = blob.read_shift(2), col = blob.read_shift(2);
var ixfes = [];
while(blob.l < target) ixfes.push(blob.read_shift(2));
if(blob.l !== target) throw "MulBlank read error";
if(blob.l !== target) throw new Error("MulBlank read error");
var lastcol = blob.read_shift(2);
if(ixfes.length != lastcol - col + 1) throw "MulBlank length mismatch";
if(ixfes.length != lastcol - col + 1) throw new Error("MulBlank length mismatch");
return {r:rw, c:col, C:lastcol, ixfe:ixfes};
}
@ -4052,7 +4079,7 @@ function parse_Guts(blob, length) {
var out = [blob.read_shift(2), blob.read_shift(2)];
if(out[0] !== 0) out[0]--;
if(out[1] !== 0) out[1]--;
if(out[0] > 7 || out[1] > 7) throw "Bad Gutters: " + out.join("|");
if(out[0] > 7 || out[1] > 7) throw new Error("Bad Gutters: " + out.join("|"));
return out;
}
@ -4219,14 +4246,14 @@ try {
//var fmla = parse_ObjFmla(blob, s + length - blob.l);
for(var i = 1; i < blob.lens.length-1; ++i) {
if(blob.l-s != blob.lens[i]) throw "TxO: bad continue record";
if(blob.l-s != blob.lens[i]) throw new Error("TxO: bad continue record");
var hdr = blob[blob.l];
var t = parse_XLUnicodeStringNoCch(blob, blob.lens[i+1]-blob.lens[i]-1);
texts += t;
if(texts.length >= (hdr ? cchText : 2*cchText)) break;
}
if(texts.length !== cchText && texts.length !== cchText*2) {
throw "cchText: " + cchText + " != " + texts.length;
throw new Error("cchText: " + cchText + " != " + texts.length);
}
blob.l = s + length;
@ -4234,9 +4261,9 @@ try {
// var rgTxoRuns = [];
// for(var j = 0; j != cbRuns/8-1; ++j) blob.l += 8;
// var cchText2 = blob.read_shift(2);
// if(cchText2 !== cchText) throw "TxOLastRun mismatch: " + cchText2 + " " + cchText;
// if(cchText2 !== cchText) throw new Error("TxOLastRun mismatch: " + cchText2 + " " + cchText);
// blob.l += 6;
// if(s + length != blob.l) throw "TxO " + (s + length) + ", at " + blob.l;
// if(s + length != blob.l) throw new Error("TxO " + (s + length) + ", at " + blob.l);
return { t: texts };
} catch(e) { blob.l = s + length; return { t: texts }; }
}
@ -4306,6 +4333,15 @@ function parse_ColInfo(blob, length, opts) {
return {s:colFirst, e:colLast, w:coldx, ixfe:ixfe, flags:flags};
}
/* 2.4.261 */
function parse_ShtProps(blob, length, opts) {
var def = {area:false};
if(opts.biff != 5) { blob.l += length; return def; }
var d = blob.read_shift(1); blob.l += 3;
if((d & 0x10)) def.area = true;
return def;
}
var parse_Style = parsenoop;
var parse_StyleExt = parsenoop;
@ -4598,7 +4634,6 @@ var parse_Surf = parsenoop;
var parse_RadarArea = parsenoop;
var parse_AxisParent = parsenoop;
var parse_LegendException = parsenoop;
var parse_ShtProps = parsenoop;
var parse_SerToCrt = parsenoop;
var parse_AxesUsed = parsenoop;
var parse_SBaseRef = parsenoop;
@ -5498,7 +5533,7 @@ function parse_cellXfs(t, styles, opts) {
/* 18.2.10 extLst CT_ExtensionList ? */
case '<extLst': case '</extLst>': break;
case '<ext': break;
default: if(opts.WTF) throw 'unrecognized ' + y[0] + ' in cellXfs';
default: if(opts.WTF) throw new Error('unrecognized ' + y[0] + ' in cellXfs');
}
});
}
@ -5815,7 +5850,7 @@ function parse_theme_xml(data/*:string*/, opts) {
var themes = {};
/* themeElements CT_BaseStyles */
if(!(t=data.match(themeltregex))) throw 'themeElements not found in theme';
if(!(t=data.match(themeltregex))) throw new Error('themeElements not found in theme');
parse_themeElements(t[0], themes, opts);
return themes;
@ -6122,6 +6157,24 @@ function parse_cc_bin(data, opts) {
}
function write_cc_bin(data, opts) { }
/* 20.5 DrawingML - SpreadsheetML Drawing */
function parse_drawing(data, rels/*:any*/) {
if(!data) return "??";
/*
Chartsheet Drawing:
- 20.5.2.35 wsDr CT_Drawing
- 20.5.2.1 absoluteAnchor CT_AbsoluteAnchor
- 20.5.2.16 graphicFrame CT_GraphicalObjectFrame
- 20.1.2.2.16 graphic CT_GraphicalObject
- 20.1.2.2.17 graphicData CT_GraphicalObjectData
- chart reference
the actual type is based on the URI of the graphicData
TODO: handle embedded charts and other types of graphics
*/
var id = (data.match(/<c:chart [^>]*r:id="([^"]*)"/)||["",""])[1];
return rels['!id'][id].Target;
}
function parse_comments(zip, dirComments, sheets, sheetRels, opts) {
for(var i = 0; i != dirComments.length; ++i) {
@ -8555,7 +8608,10 @@ function csf_to_ods_formula(f/*:string*/)/*:string*/ {
var strs = {}; // shared strings
var _ssfopts = {}; // spreadsheet formatting options
RELS.WS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
RELS.WS = [
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
"http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet"
];
function get_sst_id(sst/*:SST*/, str/*:string*/)/*:number*/ {
for(var i = 0, len = sst.length; i < len; ++i) if(sst[i].t === str) { sst.Count ++; return i; }
@ -9581,6 +9637,94 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) {
write_record(ba, "BrtEndSheet");
return ba.end();
}
function parse_numCache(data) {
var col = [];
/* 21.2.2.150 pt CT_NumVal */
(data.match(/<c:pt idx="(\d*)">(.*?)<\/c:pt>/mg)||[]).forEach(function(pt) {
var q = pt.match(/<c:pt idx="(.*?)"><c:v>(.*)<\/c:v><\/c:pt>/);
if(!q) return;
col[+q[1]] = +q[2];
});
/* 21.2.2.71 formatCode CT_Xstring */
var nf = unescapexml((data.match(/<c:formatCode>(.*?)<\/c:formatCode>/) || ["","General"])[1]);
return [col, nf];
}
/* 21.2 DrawingML - Charts */
function parse_chart(data, name/*:string*/, opts, rels, wb, csheet) {
var cs = ((csheet || {"!type":"chart"})/*:any*/);
if(!data) return csheet;
/* 21.2.2.27 chart CT_Chart */
var C = 0, R = 0, col = "A";
var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
/* 21.2.2.120 numCache CT_NumData */
(data.match(/<c:numCache>.*?<\/c:numCache>/gm)||[]).forEach(function(nc) {
var cache = parse_numCache(nc);
refguess.s.r = refguess.s.c = 0;
refguess.e.c = C;
col = encode_col(C);
cache[0].forEach(function(n,i) {
cs[col + encode_row(i)] = {t:'n', v:n, z:cache[1] };
R = i;
});
if(refguess.e.r < R) refguess.e.r = R;
++C;
});
if(C > 0) cs["!ref"] = encode_range(refguess);
return cs;
}
RELS.CS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet";
/* 18.3 Worksheets also covers Chartsheets */
function parse_cs_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
/* 18.3.1.12 chartsheet CT_ChartSheet */
if(!rels) rels = {'!id':{}};
var s = {'!type':"chart", '!chart':null, '!rel':""};
var m;
/* 18.3.1.36 drawing CT_Drawing */
if((m = data.match(/drawing r:id="(.*?)"/))) s['!rel'] = m[1];
if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']];
return s;
}
/* [MS-XLSB] 2.1.7.7 Chart Sheet */
function parse_cs_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
if(!rels) rels = {'!id':{}};
var s = {'!type':"chart", '!chart':null, '!rel':""};
var pass = false;
recordhopper(data, function cs_parse(val, Record) {
switch(Record.n) {
case 'BrtDrawing': s['!rel'] = val; break;
case 'BrtBeginSheet': break;
case 'BrtCsProp': break; // TODO
case 'BrtBeginCsViews': break; // TODO
case 'BrtBeginCsView': break; // TODO
case 'BrtEndCsView': break; // TODO
case 'BrtEndCsViews': break; // TODO
case 'BrtCsProtection': break; // TODO
case 'BrtMargins': break; // TODO
case 'BrtCsPageSetup': break; // TODO
case 'BrtEndSheet': break; // TODO
case 'BrtBeginHeaderFooter': break; // TODO
case 'BrtEndHeaderFooter': break; // TODO
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + Record.n);
}
}, opts);
if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']];
return s;
}
/* 18.2.28 (CT_WorkbookProtection) Defaults */
var WBPropsDef = [
['allowRefreshQuery', '0'],
@ -10072,6 +10216,11 @@ function parse_ws(data, name/*:string*/, opts, rels, wb, themes, styles)/*:Works
return parse_ws_xml((data/*:any*/), opts, rels, wb, themes, styles);
}
function parse_cs(data, name/*:string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(name.slice(-4)===".bin") return parse_cs_bin((data/*:any*/), opts, rels, wb, themes, styles);
return parse_cs_xml((data/*:any*/), opts, rels, wb, themes, styles);
}
function parse_sty(data, name/*:string*/, themes, opts) {
if(name.slice(-4)===".bin") return parse_sty_bin((data/*:any*/), themes, opts);
return parse_sty_xml((data/*:any*/), themes, opts);
@ -11255,7 +11404,6 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
case 'EOF': {
if(--file_depth) break;
if(range.e) {
out["!range"] = range;
if(range.e.r > 0 && range.e.c > 0) {
range.e.r--; range.e.c--;
out["!ref"] = encode_range(range);
@ -11290,6 +11438,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
opts.snames.push(cur_sheet);
}
else cur_sheet = (Directory[s] || {name:""}).name;
if(val.dt == 0x20) out["!type"] = "chart";
mergecells = [];
objects = [];
array_formulae = []; opts.arrayf = array_formulae;
@ -11299,6 +11448,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
} break;
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
if(out["!type"] == "chart" && out[encode_cell({c:val.c, r:val.r})]) ++val.c;
temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:'n'};
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
@ -12225,7 +12375,7 @@ var XLSBRecordEnum = {
/*::[*/0x021E/*::]*/: { n:"BrtBeginECTwFldInfo", f:parsenoop },
/*::[*/0x0224/*::]*/: { n:"BrtFileSharing", f:parsenoop },
/*::[*/0x0225/*::]*/: { n:"BrtOleSize", f:parsenoop },
/*::[*/0x0226/*::]*/: { n:"BrtDrawing", f:parsenoop },
/*::[*/0x0226/*::]*/: { n:"BrtDrawing", f:parse_RelID },
/*::[*/0x0227/*::]*/: { n:"BrtLegacyDrawing", f:parsenoop },
/*::[*/0x0228/*::]*/: { n:"BrtLegacyDrawingHF", f:parsenoop },
/*::[*/0x0229/*::]*/: { n:"BrtWebOpt", f:parsenoop },
@ -13702,16 +13852,38 @@ var fix_write_opts = fix_opts_func([
]);
function safe_parse_wbrels(wbrels, sheets) {
if(!wbrels) return 0;
function get_type(n) {
if(RELS.WS.indexOf(n) > -1) return "sheet";
if(RELS.CS && n == RELS.CS) return "chart";
if(RELS.DS && n == RELS.DS) return "dialog";
if(RELS.MS && n == RELS.MS) return "macro";
if(!n || !n.length) return "sheet";
return n;
}
try {
wbrels = sheets.map(function pwbr(w) { return [w.name, wbrels['!id'][w.id].Target]; });
wbrels = sheets.map(function pwbr(w) { if(!w.id) w.id = w.strRelID; return [w.name, wbrels['!id'][w.id].Target, get_type(wbrels['!id'][w.id].Type)]; });
} catch(e) { return null; }
return !wbrels || wbrels.length === 0 ? null : wbrels;
}
function safe_parse_ws(zip, path/*:string*/, relsPath/*:string*/, sheet, sheetRels, sheets, opts, wb, themes, styles) {
function safe_parse_sheet(zip, path/*:string*/, relsPath/*:string*/, sheet, sheetRels, sheets, stype/*:string*/, opts, wb, themes, styles) {
try {
sheetRels[sheet]=parse_rels(getzipstr(zip, relsPath, true), path);
sheets[sheet]=parse_ws(getzipdata(zip, path),path,opts,sheetRels[sheet], wb, themes, styles);
var data = getzipdata(zip, path);
switch(stype) {
case 'sheet': sheets[sheet]=parse_ws(data, path, opts,sheetRels[sheet], wb, themes, styles); break;
case 'chart':
var cs = parse_cs(data, path, opts,sheetRels[sheet], wb, themes, styles);
sheets[sheet] = cs;
if(!cs || !cs['!chart']) break;
var dfile = resolve_path(cs['!chart'].Target, path);
var drelsp = get_rels_path(dfile);
var draw = parse_drawing(getzipstr(zip, dfile, true), parse_rels(getzipstr(zip,drelsp,true), dfile));
var chartp = resolve_path(draw, dfile);
var crelsp = get_rels_path(chartp);
cs = parse_chart(getzipstr(zip, chartp, true), chartp, opts, parse_rels(getzipstr(zip, crelsp,true), chartp), wb, cs);
break;
}
} catch(e) { if(opts.WTF) throw e; }
}
@ -13778,8 +13950,8 @@ function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
var out = ({}/*:any*/);
if(opts.bookSheets || opts.bookProps) {
if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames;
else if(wb.Sheets) sheets = wb.Sheets.map(function pluck(x){ return x.name; });
if(wb.Sheets) sheets = wb.Sheets.map(function pluck(x){ return x.name; });
else if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames;
if(opts.bookProps) { out.Props = props; out.Custprops = custprops; }
if(opts.bookSheets && typeof sheets !== 'undefined') out.SheetNames = sheets;
if(opts.bookSheets ? out.SheetNames : opts.bookProps) return out;
@ -13808,13 +13980,16 @@ function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
/* Numbers iOS hack */
var nmode = (getzipdata(zip,"xl/worksheets/sheet.xml",true))?1:0;
for(i = 0; i != props.Worksheets; ++i) {
if(wbrels && wbrels[i]) path = 'xl/' + (wbrels[i][1]).replace(/[\/]?xl\//, "");
else {
var stype = "sheet";
if(wbrels && wbrels[i]) {
path = 'xl/' + (wbrels[i][1]).replace(/[\/]?xl\//, "");
stype = wbrels[i][2];
} else {
path = 'xl/worksheets/sheet'+(i+1-nmode)+"." + wbext;
path = path.replace(/sheet0\./,"sheet.");
}
relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels");
safe_parse_ws(zip, path, relsPath, props.SheetNames[i], sheetRels, sheets, opts, wb, themes, styles);
safe_parse_sheet(zip, path, relsPath, props.SheetNames[i], sheetRels, sheets, stype, opts, wb, themes, styles);
}
if(dir.comments) parse_comments(zip, dir.comments, sheets, sheetRels, opts);
@ -13909,7 +14084,7 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
f = "xl/worksheets/sheet" + rId + "." + wbext;
zip.file(f, write_ws(rId-1, f, opts, wb));
ct.sheets.push(f);
add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS);
add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
}
if(opts.Strings != null && opts.Strings.length > 0) {

317
xlsx.js

@ -1484,6 +1484,18 @@ if (typeof exports !== 'undefined') {
_fs = require('fs');
}
}
function resolve_path(path, base) {
var result = base.split('/');
if(base.slice(-1) != "/") result.pop(); // folder path
var target = path.split('/');
while (target.length !== 0) {
var step = target.shift();
if (step === '..') result.pop();
else if (step !== '.') result.push(step);
}
return result.join('/');
}
var attregexg=/([^\s?>\/]+)=((?:")([^"]*)(?:")|(?:')([^']*)(?:'))/g;
var tagregex=/<[^>]*>/g;
var nsregex=/<\w*:/, nsregex2 = /<(\/?)\w+:/;
@ -2500,17 +2512,7 @@ var ct2type/*{[string]:string}*/ = ({
/* Worksheet */
"application/vnd.ms-excel.binIndexWs": "TODO", /* Binary Index */
/* Chartsheet */
"application/vnd.ms-excel.chartsheet": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml": "TODO",
/* Dialogsheet */
"application/vnd.ms-excel.dialogsheet": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml": "TODO",
/* Macrosheet */
"application/vnd.ms-excel.macrosheet": "TODO",
"application/vnd.ms-excel.macrosheet+xml": "TODO",
"application/vnd.ms-excel.intlmacrosheet": "TODO",
"application/vnd.ms-excel.binIndexMs": "TODO", /* Binary Index */
@ -2521,6 +2523,7 @@ var ct2type/*{[string]:string}*/ = ({
/* Custom Data Properties */
"application/vnd.openxmlformats-officedocument.customXmlProperties+xml": "TODO",
"application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty": "TODO",
/* Comments */
"application/vnd.ms-excel.comments": "comments",
@ -2600,6 +2603,9 @@ var ct2type/*{[string]:string}*/ = ({
/* Themes */
"application/vnd.openxmlformats-officedocument.theme+xml": "themes",
/* Theme Override */
"application/vnd.openxmlformats-officedocument.themeOverride+xml": "TODO",
/* Timeline */
"application/vnd.ms-excel.Timeline+xml": "TODO", /* verify */
"application/vnd.ms-excel.TimelineCache+xml": "TODO", /* verify */
@ -2636,6 +2642,9 @@ var ct2type/*{[string]:string}*/ = ({
"application/vnd.openxmlformats-package.relationships+xml": "rels",
"application/vnd.openxmlformats-officedocument.oleObject": "TODO",
/* Image */
"image/png": "TODO",
"sheet": "js"
});
@ -2651,11 +2660,23 @@ var CT_LIST = (function(){
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
xlsb: "application/vnd.ms-excel.sharedStrings"
},
sheets: {
sheets: { /* Worksheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
xlsb: "application/vnd.ms-excel.worksheet"
},
styles: {/* Styles */
charts: { /* Chartsheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml",
xlsb: "application/vnd.ms-excel.chartsheet"
},
dialogs: { /* Dialogsheet */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml",
xlsb: "application/vnd.ms-excel.dialogsheet"
},
macros: { /* Macrosheet (Excel 4.0 Macros) */
xlsx: "application/vnd.ms-excel.macrosheet+xml",
xlsb: "application/vnd.ms-excel.macrosheet"
},
styles: { /* Styles */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
xlsb: "application/vnd.ms-excel.styles"
}
@ -2670,9 +2691,12 @@ var type2ct/*{[string]:Array<string>}*/ = evert_arr(ct2type);
XMLNS.CT = 'http://schemas.openxmlformats.org/package/2006/content-types';
function parse_ct(data, opts) {
var ct = ({ workbooks: [], sheets: [], calcchains: [], themes: [], styles: [],
coreprops: [], extprops: [], custprops: [], strs:[], comments: [], vba: [],
TODO:[], rels:[], xmlns: "" });
var ct = ({
workbooks:[], sheets:[], charts:[], dialogs:[],
rels:[], strs:[], comments:[],
coreprops:[], extprops:[], custprops:[], themes:[], styles:[],
calcchains:[], vba: [],
TODO:[], xmlns: "" });
if(!data || !data.match) return ct;
var ctext = {};
(data.match(tagregex)||[]).forEach(function(x) {
@ -2747,12 +2771,18 @@ function write_ct(ct, opts) {
if(o.length>2){ o[o.length] = ('</Types>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}
/* 9.3.2 OPC Relationships Markup */
/* 9.3 Relationships */
var RELS = ({
WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
});
/* 9.3.3 Representing Relationships */
function get_rels_path(file) {
var n = file.lastIndexOf("/");
return file.substr(0,n) + '/_rels' + file.substr(n) + ".rels";
}
function parse_rels(data, currentFilePath) {
if (!data) return data;
if (currentFilePath.charAt(0) !== '/') {
@ -2760,28 +2790,13 @@ function parse_rels(data, currentFilePath) {
}
var rels = {};
var hash = {};
var resolveRelativePathIntoAbsolute = function (to) {
var toksFrom = currentFilePath.split('/');
toksFrom.pop(); // folder path
var toksTo = to.split('/');
var reversed = [];
while (toksTo.length !== 0) {
var tokTo = toksTo.shift();
if (tokTo === '..') {
toksFrom.pop();
} else if (tokTo !== '.') {
toksFrom.push(tokTo);
}
}
return toksFrom.join('/');
};
(data.match(tagregex)||[]).forEach(function(x) {
var y = parsexmltag(x);
/* 9.3.2.2 OPC_Relationships */
if (y[0] === '<Relationship') {
var rel = {}; rel.Type = y.Type; rel.Target = y.Target; rel.Id = y.Id; rel.TargetMode = y.TargetMode;
var canonictarget = y.TargetMode === 'External' ? y.Target : resolveRelativePathIntoAbsolute(y.Target);
var canonictarget = y.TargetMode === 'External' ? y.Target : resolve_path(y.Target, currentFilePath);
rels[canonictarget] = rel;
hash[y.Id] = rel;
}
@ -2972,9 +2987,12 @@ function parse_ext_props(data, p) {
if(q.HeadingPairs && q.TitlesOfParts) {
var v = parseVector(q.HeadingPairs);
var j = 0, widx = 0, cidx = -1;
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);
switch(v[i].v) {
case "Worksheets":
case "工作表":
case "Листы":
case "ワークシート":
@ -2986,17 +3004,24 @@ function parse_ext_props(data, p) {
case "Folhas de cálculo":
case "Planilhas":
case "Werkbladen":
case "Worksheets": widx = j; p.Worksheets = +(v[++i].v); break;
p.Worksheets = len;
p.SheetNames = parts.slice(idx, idx + len);
break;
case "Named Ranges":
case "Benannte Bereiche":
case "Named Ranges": ++i; break; // TODO: Handle Named Ranges
p.NamedRanges = len;
p.DefinedNames = parts.slice(idx, idx + len);
break;
case "Charts": cidx = j; p.Charts = +(v[++i].v); break;
default: break; //throw new Error(v[i].v);
case "Charts":
case "Diagramme":
p.Chartsheets = len;
p.ChartNames = parts.slice(idx, idx + len);
break;
}
idx += len;
}
var parts = parseVector(q.TitlesOfParts).map(function(x) { return x.v; });
p.SheetNames = parts.slice(widx, widx + p.Worksheets);
}
return p;
}
@ -3411,10 +3436,10 @@ function parse_PropertySetStream(file, PIDSI) {
var SystemIdentifier = blob.read_shift(4);
blob.chk(CFB.utils.consts.HEADER_CLSID, 'CLSID: ');
NumSets = blob.read_shift(4);
if(NumSets !== 1 && NumSets !== 2) throw "Unrecognized #Sets: " + NumSets;
if(NumSets !== 1 && NumSets !== 2) throw new Error("Unrecognized #Sets: " + NumSets);
FMTID0 = blob.read_shift(16); Offset0 = blob.read_shift(4);
if(NumSets === 1 && Offset0 !== blob.l) throw "Length mismatch";
if(NumSets === 1 && Offset0 !== blob.l) throw new Error("Length mismatch: " + Offset0 + " !== " + blob.l);
else if(NumSets === 2) { FMTID1 = blob.read_shift(16); Offset1 = blob.read_shift(4); }
var PSet0 = parse_PropertySet(blob, PIDSI);
@ -3569,7 +3594,7 @@ var parse_HyperlinkMoniker = function(blob, length) {
switch(clsid) {
case "e0c9ea79f9bace118c8200aa004ba90b": return parse_URLMoniker(blob, length);
case "0303000000000000c000000000000046": return parse_FileMoniker(blob, length);
default: throw "unsupported moniker " + clsid;
default: throw new Error("Unsupported Moniker " + clsid);
}
};
@ -3654,7 +3679,7 @@ function parse_AddinUdf(blob, length, opts) {
var udfName = parse_ShortXLUnicodeString(blob, length, opts);
var cb = blob.read_shift(2);
l -= blob.l;
if(cb !== l) throw "Malformed AddinUdf: padding = " + l + " != " + cb;
if(cb !== l) throw new Error("Malformed AddinUdf: padding = " + l + " != " + cb);
blob.l += cb;
return udfName;
}
@ -3737,7 +3762,7 @@ function parse_FtArray(blob, length, ot) {
fts.push(FtTab[ft](blob, s + length - blob.l));
} catch(e) { blob.l = s + length; return fts; }
}
if(blob.l != s + length) blob.l = s + length; //throw "bad Object Ft-sequence";
if(blob.l != s + length) blob.l = s + length; //throw new Error("bad Object Ft-sequence");
return fts;
}
@ -3748,8 +3773,9 @@ var parse_FontIndex = parseuint16;
/* 2.4.21 */
function parse_BOF(blob, length) {
var o = {};
var o = {BIFFVer:0, dt:0};
o.BIFFVer = blob.read_shift(2); length -= 2;
if(length >= 2) { o.dt = blob.read_shift(2); blob.l -= 2; }
switch(o.BIFFVer) {
case 0x0600: /* BIFF8 */
case 0x0500: /* BIFF5 */
@ -3757,6 +3783,7 @@ function parse_BOF(blob, length) {
break;
default: if(length > 6) throw new Error("Unexpected BIFF Ver " + o.BIFFVer);
}
blob.read_shift(length);
return o;
}
@ -3766,7 +3793,7 @@ function parse_BOF(blob, length) {
function parse_InterfaceHdr(blob, length) {
if(length === 0) return 0x04b0;
var q;
if((q=blob.read_shift(2))!==0x04b0) throw 'InterfaceHdr codePage ' + q;
if((q=blob.read_shift(2))!==0x04b0) throw new Error("InterfaceHdr codePage " + q);
return 0x04b0;
}
@ -3832,7 +3859,7 @@ function parse_Row(blob, length) {
/* 2.4.125 */
function parse_ForceFullCalculation(blob, length) {
var header = parse_frtHeader(blob);
if(header.type != 0x08A3) throw "Invalid Future Record " + header.type;
if(header.type != 0x08A3) throw new Error("Invalid Future Record " + header.type);
var fullcalc = blob.read_shift(4);
return fullcalc !== 0x0;
}
@ -3921,9 +3948,9 @@ function parse_MulRk(blob, length) {
var rw = blob.read_shift(2), col = blob.read_shift(2);
var rkrecs = [];
while(blob.l < target) rkrecs.push(parse_RkRec(blob));
if(blob.l !== target) throw "MulRK read error";
if(blob.l !== target) throw new Error("MulRK read error");
var lastcol = blob.read_shift(2);
if(rkrecs.length != lastcol - col + 1) throw "MulRK length mismatch";
if(rkrecs.length != lastcol - col + 1) throw new Error("MulRK length mismatch");
return {r:rw, c:col, C:lastcol, rkrec:rkrecs};
}
/* 2.4.174 */
@ -3932,9 +3959,9 @@ function parse_MulBlank(blob, length) {
var rw = blob.read_shift(2), col = blob.read_shift(2);
var ixfes = [];
while(blob.l < target) ixfes.push(blob.read_shift(2));
if(blob.l !== target) throw "MulBlank read error";
if(blob.l !== target) throw new Error("MulBlank read error");
var lastcol = blob.read_shift(2);
if(ixfes.length != lastcol - col + 1) throw "MulBlank length mismatch";
if(ixfes.length != lastcol - col + 1) throw new Error("MulBlank length mismatch");
return {r:rw, c:col, C:lastcol, ixfe:ixfes};
}
@ -3998,7 +4025,7 @@ function parse_Guts(blob, length) {
var out = [blob.read_shift(2), blob.read_shift(2)];
if(out[0] !== 0) out[0]--;
if(out[1] !== 0) out[1]--;
if(out[0] > 7 || out[1] > 7) throw "Bad Gutters: " + out.join("|");
if(out[0] > 7 || out[1] > 7) throw new Error("Bad Gutters: " + out.join("|"));
return out;
}
@ -4165,14 +4192,14 @@ try {
//var fmla = parse_ObjFmla(blob, s + length - blob.l);
for(var i = 1; i < blob.lens.length-1; ++i) {
if(blob.l-s != blob.lens[i]) throw "TxO: bad continue record";
if(blob.l-s != blob.lens[i]) throw new Error("TxO: bad continue record");
var hdr = blob[blob.l];
var t = parse_XLUnicodeStringNoCch(blob, blob.lens[i+1]-blob.lens[i]-1);
texts += t;
if(texts.length >= (hdr ? cchText : 2*cchText)) break;
}
if(texts.length !== cchText && texts.length !== cchText*2) {
throw "cchText: " + cchText + " != " + texts.length;
throw new Error("cchText: " + cchText + " != " + texts.length);
}
blob.l = s + length;
@ -4180,9 +4207,9 @@ try {
// var rgTxoRuns = [];
// for(var j = 0; j != cbRuns/8-1; ++j) blob.l += 8;
// var cchText2 = blob.read_shift(2);
// if(cchText2 !== cchText) throw "TxOLastRun mismatch: " + cchText2 + " " + cchText;
// if(cchText2 !== cchText) throw new Error("TxOLastRun mismatch: " + cchText2 + " " + cchText);
// blob.l += 6;
// if(s + length != blob.l) throw "TxO " + (s + length) + ", at " + blob.l;
// if(s + length != blob.l) throw new Error("TxO " + (s + length) + ", at " + blob.l);
return { t: texts };
} catch(e) { blob.l = s + length; return { t: texts }; }
}
@ -4252,6 +4279,15 @@ function parse_ColInfo(blob, length, opts) {
return {s:colFirst, e:colLast, w:coldx, ixfe:ixfe, flags:flags};
}
/* 2.4.261 */
function parse_ShtProps(blob, length, opts) {
var def = {area:false};
if(opts.biff != 5) { blob.l += length; return def; }
var d = blob.read_shift(1); blob.l += 3;
if((d & 0x10)) def.area = true;
return def;
}
var parse_Style = parsenoop;
var parse_StyleExt = parsenoop;
@ -4544,7 +4580,6 @@ var parse_Surf = parsenoop;
var parse_RadarArea = parsenoop;
var parse_AxisParent = parsenoop;
var parse_LegendException = parsenoop;
var parse_ShtProps = parsenoop;
var parse_SerToCrt = parsenoop;
var parse_AxesUsed = parsenoop;
var parse_SBaseRef = parsenoop;
@ -5444,7 +5479,7 @@ function parse_cellXfs(t, styles, opts) {
/* 18.2.10 extLst CT_ExtensionList ? */
case '<extLst': case '</extLst>': break;
case '<ext': break;
default: if(opts.WTF) throw 'unrecognized ' + y[0] + ' in cellXfs';
default: if(opts.WTF) throw new Error('unrecognized ' + y[0] + ' in cellXfs');
}
});
}
@ -5761,7 +5796,7 @@ function parse_theme_xml(data, opts) {
var themes = {};
/* themeElements CT_BaseStyles */
if(!(t=data.match(themeltregex))) throw 'themeElements not found in theme';
if(!(t=data.match(themeltregex))) throw new Error('themeElements not found in theme');
parse_themeElements(t[0], themes, opts);
return themes;
@ -6068,6 +6103,24 @@ function parse_cc_bin(data, opts) {
}
function write_cc_bin(data, opts) { }
/* 20.5 DrawingML - SpreadsheetML Drawing */
function parse_drawing(data, rels) {
if(!data) return "??";
/*
Chartsheet Drawing:
- 20.5.2.35 wsDr CT_Drawing
- 20.5.2.1 absoluteAnchor CT_AbsoluteAnchor
- 20.5.2.16 graphicFrame CT_GraphicalObjectFrame
- 20.1.2.2.16 graphic CT_GraphicalObject
- 20.1.2.2.17 graphicData CT_GraphicalObjectData
- chart reference
the actual type is based on the URI of the graphicData
TODO: handle embedded charts and other types of graphics
*/
var id = (data.match(/<c:chart [^>]*r:id="([^"]*)"/)||["",""])[1];
return rels['!id'][id].Target;
}
function parse_comments(zip, dirComments, sheets, sheetRels, opts) {
for(var i = 0; i != dirComments.length; ++i) {
@ -8500,7 +8553,10 @@ function csf_to_ods_formula(f) {
var strs = {}; // shared strings
var _ssfopts = {}; // spreadsheet formatting options
RELS.WS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
RELS.WS = [
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
"http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet"
];
function get_sst_id(sst, str) {
for(var i = 0, len = sst.length; i < len; ++i) if(sst[i].t === str) { sst.Count ++; return i; }
@ -9526,6 +9582,94 @@ function write_ws_bin(idx, opts, wb) {
write_record(ba, "BrtEndSheet");
return ba.end();
}
function parse_numCache(data) {
var col = [];
/* 21.2.2.150 pt CT_NumVal */
(data.match(/<c:pt idx="(\d*)">(.*?)<\/c:pt>/mg)||[]).forEach(function(pt) {
var q = pt.match(/<c:pt idx="(.*?)"><c:v>(.*)<\/c:v><\/c:pt>/);
if(!q) return;
col[+q[1]] = +q[2];
});
/* 21.2.2.71 formatCode CT_Xstring */
var nf = unescapexml((data.match(/<c:formatCode>(.*?)<\/c:formatCode>/) || ["","General"])[1]);
return [col, nf];
}
/* 21.2 DrawingML - Charts */
function parse_chart(data, name, opts, rels, wb, csheet) {
var cs = ((csheet || {"!type":"chart"}));
if(!data) return csheet;
/* 21.2.2.27 chart CT_Chart */
var C = 0, R = 0, col = "A";
var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
/* 21.2.2.120 numCache CT_NumData */
(data.match(/<c:numCache>.*?<\/c:numCache>/gm)||[]).forEach(function(nc) {
var cache = parse_numCache(nc);
refguess.s.r = refguess.s.c = 0;
refguess.e.c = C;
col = encode_col(C);
cache[0].forEach(function(n,i) {
cs[col + encode_row(i)] = {t:'n', v:n, z:cache[1] };
R = i;
});
if(refguess.e.r < R) refguess.e.r = R;
++C;
});
if(C > 0) cs["!ref"] = encode_range(refguess);
return cs;
}
RELS.CS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet";
/* 18.3 Worksheets also covers Chartsheets */
function parse_cs_xml(data, opts, rels, wb, themes, styles) {
if(!data) return data;
/* 18.3.1.12 chartsheet CT_ChartSheet */
if(!rels) rels = {'!id':{}};
var s = {'!type':"chart", '!chart':null, '!rel':""};
var m;
/* 18.3.1.36 drawing CT_Drawing */
if((m = data.match(/drawing r:id="(.*?)"/))) s['!rel'] = m[1];
if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']];
return s;
}
/* [MS-XLSB] 2.1.7.7 Chart Sheet */
function parse_cs_bin(data, opts, rels, wb, themes, styles) {
if(!data) return data;
if(!rels) rels = {'!id':{}};
var s = {'!type':"chart", '!chart':null, '!rel':""};
var pass = false;
recordhopper(data, function cs_parse(val, Record) {
switch(Record.n) {
case 'BrtDrawing': s['!rel'] = val; break;
case 'BrtBeginSheet': break;
case 'BrtCsProp': break; // TODO
case 'BrtBeginCsViews': break; // TODO
case 'BrtBeginCsView': break; // TODO
case 'BrtEndCsView': break; // TODO
case 'BrtEndCsViews': break; // TODO
case 'BrtCsProtection': break; // TODO
case 'BrtMargins': break; // TODO
case 'BrtCsPageSetup': break; // TODO
case 'BrtEndSheet': break; // TODO
case 'BrtBeginHeaderFooter': break; // TODO
case 'BrtEndHeaderFooter': break; // TODO
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + Record.n);
}
}, opts);
if(rels['!id'][s['!rel']]) s['!chart'] = rels['!id'][s['!rel']];
return s;
}
/* 18.2.28 (CT_WorkbookProtection) Defaults */
var WBPropsDef = [
['allowRefreshQuery', '0'],
@ -10017,6 +10161,11 @@ function parse_ws(data, name, opts, rels, wb, themes, styles) {
return parse_ws_xml((data), opts, rels, wb, themes, styles);
}
function parse_cs(data, name, opts, rels, wb, themes, styles) {
if(name.slice(-4)===".bin") return parse_cs_bin((data), opts, rels, wb, themes, styles);
return parse_cs_xml((data), opts, rels, wb, themes, styles);
}
function parse_sty(data, name, themes, opts) {
if(name.slice(-4)===".bin") return parse_sty_bin((data), themes, opts);
return parse_sty_xml((data), themes, opts);
@ -11196,7 +11345,6 @@ function parse_workbook(blob, options) {
case 'EOF': {
if(--file_depth) break;
if(range.e) {
out["!range"] = range;
if(range.e.r > 0 && range.e.c > 0) {
range.e.r--; range.e.c--;
out["!ref"] = encode_range(range);
@ -11231,6 +11379,7 @@ function parse_workbook(blob, options) {
opts.snames.push(cur_sheet);
}
else cur_sheet = (Directory[s] || {name:""}).name;
if(val.dt == 0x20) out["!type"] = "chart";
mergecells = [];
objects = [];
array_formulae = []; opts.arrayf = array_formulae;
@ -11240,6 +11389,7 @@ function parse_workbook(blob, options) {
} break;
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
if(out["!type"] == "chart" && out[encode_cell({c:val.c, r:val.r})]) ++val.c;
temp_val = {ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:'n'};
safe_format_xf(temp_val, options, wb.opts.Date1904);
addcell({c:val.c, r:val.r}, temp_val, options);
@ -12166,7 +12316,7 @@ var XLSBRecordEnum = {
0x021E: { n:"BrtBeginECTwFldInfo", f:parsenoop },
0x0224: { n:"BrtFileSharing", f:parsenoop },
0x0225: { n:"BrtOleSize", f:parsenoop },
0x0226: { n:"BrtDrawing", f:parsenoop },
0x0226: { n:"BrtDrawing", f:parse_RelID },
0x0227: { n:"BrtLegacyDrawing", f:parsenoop },
0x0228: { n:"BrtLegacyDrawingHF", f:parsenoop },
0x0229: { n:"BrtWebOpt", f:parsenoop },
@ -13642,16 +13792,38 @@ var fix_write_opts = fix_opts_func([
]);
function safe_parse_wbrels(wbrels, sheets) {
if(!wbrels) return 0;
function get_type(n) {
if(RELS.WS.indexOf(n) > -1) return "sheet";
if(RELS.CS && n == RELS.CS) return "chart";
if(RELS.DS && n == RELS.DS) return "dialog";
if(RELS.MS && n == RELS.MS) return "macro";
if(!n || !n.length) return "sheet";
return n;
}
try {
wbrels = sheets.map(function pwbr(w) { return [w.name, wbrels['!id'][w.id].Target]; });
wbrels = sheets.map(function pwbr(w) { if(!w.id) w.id = w.strRelID; return [w.name, wbrels['!id'][w.id].Target, get_type(wbrels['!id'][w.id].Type)]; });
} catch(e) { return null; }
return !wbrels || wbrels.length === 0 ? null : wbrels;
}
function safe_parse_ws(zip, path, relsPath, sheet, sheetRels, sheets, opts, wb, themes, styles) {
function safe_parse_sheet(zip, path, relsPath, sheet, sheetRels, sheets, stype, opts, wb, themes, styles) {
try {
sheetRels[sheet]=parse_rels(getzipstr(zip, relsPath, true), path);
sheets[sheet]=parse_ws(getzipdata(zip, path),path,opts,sheetRels[sheet], wb, themes, styles);
var data = getzipdata(zip, path);
switch(stype) {
case 'sheet': sheets[sheet]=parse_ws(data, path, opts,sheetRels[sheet], wb, themes, styles); break;
case 'chart':
var cs = parse_cs(data, path, opts,sheetRels[sheet], wb, themes, styles);
sheets[sheet] = cs;
if(!cs || !cs['!chart']) break;
var dfile = resolve_path(cs['!chart'].Target, path);
var drelsp = get_rels_path(dfile);
var draw = parse_drawing(getzipstr(zip, dfile, true), parse_rels(getzipstr(zip,drelsp,true), dfile));
var chartp = resolve_path(draw, dfile);
var crelsp = get_rels_path(chartp);
cs = parse_chart(getzipstr(zip, chartp, true), chartp, opts, parse_rels(getzipstr(zip, crelsp,true), chartp), wb, cs);
break;
}
} catch(e) { if(opts.WTF) throw e; }
}
@ -13718,8 +13890,8 @@ function parse_zip(zip, opts) {
var out = ({});
if(opts.bookSheets || opts.bookProps) {
if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames;
else if(wb.Sheets) sheets = wb.Sheets.map(function pluck(x){ return x.name; });
if(wb.Sheets) sheets = wb.Sheets.map(function pluck(x){ return x.name; });
else if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames;
if(opts.bookProps) { out.Props = props; out.Custprops = custprops; }
if(opts.bookSheets && typeof sheets !== 'undefined') out.SheetNames = sheets;
if(opts.bookSheets ? out.SheetNames : opts.bookProps) return out;
@ -13748,13 +13920,16 @@ function parse_zip(zip, opts) {
/* Numbers iOS hack */
var nmode = (getzipdata(zip,"xl/worksheets/sheet.xml",true))?1:0;
for(i = 0; i != props.Worksheets; ++i) {
if(wbrels && wbrels[i]) path = 'xl/' + (wbrels[i][1]).replace(/[\/]?xl\//, "");
else {
var stype = "sheet";
if(wbrels && wbrels[i]) {
path = 'xl/' + (wbrels[i][1]).replace(/[\/]?xl\//, "");
stype = wbrels[i][2];
} else {
path = 'xl/worksheets/sheet'+(i+1-nmode)+"." + wbext;
path = path.replace(/sheet0\./,"sheet.");
}
relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels");
safe_parse_ws(zip, path, relsPath, props.SheetNames[i], sheetRels, sheets, opts, wb, themes, styles);
safe_parse_sheet(zip, path, relsPath, props.SheetNames[i], sheetRels, sheets, stype, opts, wb, themes, styles);
}
if(dir.comments) parse_comments(zip, dir.comments, sheets, sheetRels, opts);
@ -13847,7 +14022,7 @@ f = "docProps/app.xml";
f = "xl/worksheets/sheet" + rId + "." + wbext;
zip.file(f, write_ws(rId-1, f, opts, wb));
ct.sheets.push(f);
add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS);
add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
}
if(opts.Strings != null && opts.Strings.length > 0) {