diff --git a/README.md b/README.md index ba7c48d..18ee067 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ enhancements and additional features by request. [**Commercial Support**](http://sheetjs.com/support) +[**Rendered Documentation**](https://sheetjs.gitbooks.io/docs/) + [**In-Browser Demos**](http://sheetjs.com/demos) [**Source Code**](http://git.io/xlsx) @@ -634,8 +636,9 @@ In addition to the base sheet keys, worksheets also add: the first cell (upper-left) in the range is set. - `ws['protect']`: object of write sheet protection properties. The `password` - key specifies the password. The writer uses the XOR obfuscation method. The - following keys control the sheet protection (same as ECMA-376 18.3.1.85): + key specifies the password for formats that support password-protected sheets + (XLSX/XLSB/XLS). The writer uses the XOR obfuscation method. The following + keys control the sheet protection (same as ECMA-376 18.3.1.85): | key | functionality disabled if value is true | |:----------------------|:-----------------------------------------------------| @@ -659,7 +662,7 @@ In addition to the base sheet keys, worksheets also add: ```typescript type AutoFilter = { - ref:string; // A-1 based range representing the AutoFilter table range + ref:string; // A-1 based range representing the AutoFilter table range } ``` diff --git a/bits/36_xlsprops.js b/bits/36_xlsprops.js index 617acf8..02a491d 100644 --- a/bits/36_xlsprops.js +++ b/bits/36_xlsprops.js @@ -1,25 +1,35 @@ /* Common Name -> XLML Name */ var XLMLDocPropsMap = { - Category: 'Category', - ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ - Keywords: 'Keywords', - LastAuthor: 'LastAuthor', - LastPrinted: 'LastPrinted', - RevNumber: 'Revision', - Author: 'Author', - Comments: 'Description', - Identifier: 'Identifier', /* NOTE: missing from schema */ - Language: 'Language', /* NOTE: missing from schema */ - Subject: 'Subject', Title: 'Title', + Subject: 'Subject', + Author: 'Author', + Keywords: 'Keywords', + Comments: 'Description', + LastAuthor: 'LastAuthor', + RevNumber: 'Revision', + Application: 'AppName', + /* TotalTime: 'TotalTime', */ + LastPrinted: 'LastPrinted', CreatedDate: 'Created', ModifiedDate: 'LastSaved', - - Application: 'AppName', - AppVersion: 'Version', - TotalTime: 'TotalTime', + /* Pages */ + /* Words */ + /* Characters */ + Category: 'Category', + /* PresentationFormat */ Manager: 'Manager', - Company: 'Company' + Company: 'Company', + /* Guid */ + /* HyperlinkBase */ + /* Bytes */ + /* Lines */ + /* Paragraphs */ + /* CharactersWithSpaces */ + AppVersion: 'Version', + + ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ + Identifier: 'Identifier', /* NOTE: missing from schema */ + Language: 'Language' /* NOTE: missing from schema */ }; var evert_XLMLDPM = evert(XLMLDocPropsMap); @@ -28,19 +38,21 @@ function xlml_set_prop(Props, tag/*:string*/, val) { Props[tag] = val; } - -/* TODO: verify */ function xlml_write_docprops(Props, opts) { var o = []; - CORE_PROPS.concat(EXT_PROPS).forEach(function(p) { + keys(XLMLDocPropsMap).map(function(m) { + for(var i = 0; i < CORE_PROPS.length; ++i) if(CORE_PROPS[i][1] == m) return CORE_PROPS[i]; + for(i = 0; i < EXT_PROPS.length; ++i) if(EXT_PROPS[i][1] == m) return EXT_PROPS[i]; + throw m; + }).forEach(function(p) { if(Props[p[1]] == null) return; var m = opts && opts.Props && opts.Props[p[1]] != null ? opts.Props[p[1]] : Props[p[1]]; switch(p[2]) { - case 'date': m = new Date(m).toISOString(); break; + case 'date': m = new Date(m).toISOString().replace(/\.\d*Z/,"Z"); break; } if(typeof m == 'number') m = String(m); else if(m === true || m === false) { m = m ? "1" : "0"; } - else if(m instanceof Date) m = new Date(m).toISOString(); + else if(m instanceof Date) m = new Date(m).toISOString().replace(/\.\d*Z/,""); o.push(writetag(XLMLDocPropsMap[p[1]] || p[1], m)); }); return writextag('DocumentProperties', o.join(""), {xmlns:XLMLNS.o }); diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js index 01d4f5a..9038e83 100644 --- a/bits/67_wsxml.js +++ b/bits/67_wsxml.js @@ -244,8 +244,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { /* 18.3.1.73 row CT_Row */ for(ri = 0; ri < xlen; ++ri) if(x.charCodeAt(ri) === 62) break; ++ri; tag = parsexmltag(x.substr(0,ri), true); - /* SpreadSheetGear uses implicit r/c */ - tagr = typeof tag.r !== 'undefined' ? parseInt(tag.r, 10) : tagr+1; tagc = -1; + tagr = tag.r != null ? parseInt(tag.r, 10) : tagr+1; tagc = -1; if(opts.sheetRows && opts.sheetRows < tagr) continue; if(guess.s.r > tagr - 1) guess.s.r = tagr - 1; if(guess.e.r < tagr - 1) guess.e.r = tagr - 1; @@ -297,7 +296,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { p.F = arrayf[i][1]; } - if(tag.t === undefined && p.v === undefined) { + if(tag.t == null && p.v === undefined) { if(!opts.sheetStubs) continue; p.t = "z"; } @@ -335,7 +334,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { break; /* error string in .w, number in .v */ case 'e': - if(opts && opts.cellText === false) p.w = p.v; + if(!opts || opts.cellText !== false) p.w = p.v; p.v = RBErr[p.v]; break; } /* formatting */ diff --git a/bits/68_wsbin.js b/bits/68_wsbin.js index b073512..3b30a40 100644 --- a/bits/68_wsbin.js +++ b/bits/68_wsbin.js @@ -400,7 +400,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { case 'n': p.v = val[1]; break; case 's': sstr = strs[val[1]]; p.v = sstr.t; p.r = sstr.r; break; case 'b': p.v = val[1] ? true : false; break; - case 'e': p.v = val[1]; p.w = BErr[p.v]; break; + case 'e': p.v = val[1]; if(opts.cellText !== false) p.w = BErr[p.v]; break; case 'str': p.t = 's'; p.v = utf8read(val[1]); break; } if((cf = styles.CellXf[val[0].iStyleRef])) safe_format(p,cf.ifmt,null,opts, themes, styles); diff --git a/bits/75_xlml.js b/bits/75_xlml.js index 2bec86a..9e90060 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -121,11 +121,11 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a if(cell.v === undefined) cell.v=+xml; if(!cell.t) cell.t = 'n'; break; - case 'Error': cell.t = 'e'; cell.v = RBErr[xml]; cell.w = xml; break; + case 'Error': cell.t = 'e'; cell.v = RBErr[xml]; if(o.cellText !== false) cell.w = xml; break; default: cell.t = 's'; cell.v = xlml_fixstr(ss||xml); break; } safe_format_xlml(cell, nf, o); - if(o.cellFormula != null) { + if(o.cellFormula !== false) { if(cell.Formula) { var fstr = unescapexml(cell.Formula); /* strictly speaking, the leading = is required but some writers omit */ @@ -837,17 +837,107 @@ function write_sty_xlml(wb, opts)/*:string*/ { } /* WorksheetOptions */ function write_ws_xlml_wsopts(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ { + if(!ws) return ""; var o = []; + /* NOTE: spec technically allows any order, but stick with implied order */ + + /* FitToPage */ + /* DoNotDisplayColHeaders */ + /* DoNotDisplayRowHeaders */ + /* ViewableRange */ + /* Selection */ + /* GridlineColor */ + /* Name */ + /* ExcelWorksheetType */ + /* IntlMacro */ + /* Unsynced */ + /* Selected */ + /* CodeName */ + + if(ws['!margins']) { + o.push(""); + if(ws['!margins'].header) o.push(writextag("Header", null, {'x:Margin':ws['!margins'].header})); + if(ws['!margins'].footer) o.push(writextag("Footer", null, {'x:Margin':ws['!margins'].footer})); + o.push(writextag("PageMargins", null, { + 'x:Bottom': ws['!margins'].bottom || "0.75", + 'x:Left': ws['!margins'].left || "0.7", + 'x:Right': ws['!margins'].right || "0.7", + 'x:Top': ws['!margins'].top || "0.75" + })); + o.push(""); + } + /* PageSetup */ + /* DisplayPageBreak */ + /* TransitionExpressionEvaluation */ + /* TransitionFormulaEntry */ + /* Print */ + /* Zoom */ + /* PageLayoutZoom */ + /* PageBreakZoom */ + /* ShowPageBreakZoom */ + /* DefaultRowHeight */ + /* DefaultColumnWidth */ + /* StandardWidth */ + if(wb && wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx]) { /* Visible */ - if(!!wb.Workbook.Sheets[idx].Hidden) o.push("" + (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden") + ""); + if(!!wb.Workbook.Sheets[idx].Hidden) o.push(writextag("Visible", (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden"), {})); 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(""); } } + + /* LeftColumnVisible */ + /* DisplayRightToLeft */ + /* GridlineColorIndex */ + /* DisplayFormulas */ + /* DoNotDisplayGridlines */ + /* DoNotDisplayHeadings */ + /* DoNotDisplayOutline */ + /* ApplyAutomaticOutlineStyles */ + /* NoSummaryRowsBelowDetail */ + /* NoSummaryColumnsRightDetail */ + /* DoNotDisplayZeros */ + /* ActiveRow */ + /* ActiveColumn */ + /* FilterOn */ + /* RangeSelection */ + /* TopRowVisible */ + /* TopRowBottomPane */ + /* LeftColumnRightPane */ + /* ActivePane */ + /* SplitHorizontal */ + /* SplitVertical */ + /* FreezePanes */ + /* FrozenNoSplit */ + /* TabColorIndex */ + /* Panes */ + + /* NOTE: Password not supported in XLML Format */ + if(ws['!protect']) { + o.push(writetag("ProtectContents", "True")); + if(ws['!protect'].objects) o.push(writetag("ProtectObjects", "True")); + if(ws['!protect'].scenarios) o.push(writetag("ProtectScenarios", "True")); + if(ws['!protect'].selectLockedCells != null && !ws['!protect'].selectLockedCells) o.push(writetag("EnableSelection", "NoSelection")); + else if(ws['!protect'].selectUnlockedCells != null && !ws['!protect'].selectUnlockedCells) o.push(writetag("EnableSelection", "UnlockedCells")); + [ + [ "formatColumns", "AllowFormatCells" ], + [ "formatRows", "AllowSizeCols" ], + [ "formatCells", "AllowSizeRows" ], + [ "insertColumns", "AllowInsertCols" ], + [ "insertRows", "AllowInsertRows" ], + [ "insertHyperlinks", "AllowInsertHyperlinks" ], + [ "deleteColumns", "AllowDeleteCols" ], + [ "deleteRows", "AllowDeleteRows" ], + [ "sort", "AllowSort" ], + [ "autoFilter", "AllowFilter" ], + [ "pivotTables", "AllowUsePivotTables" ] + ].forEach(function(x) { if(ws['!protect'][x[0]]) o.push("<"+x[1]+"/>"); }); + } + if(o.length == 0) return ""; return writextag("WorksheetOptions", o.join(""), {xmlns:XLMLNS.x}); } diff --git a/bits/76_xls.js b/bits/76_xls.js index 539c850..a8318a0 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -54,12 +54,13 @@ function slurp(R, blob, length/*:number*/, opts) { function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) { if(p.t === 'z') return; - if(p.t === 'e') { p.w = p.w || BErr[p.v]; } if(!p.XF) return; try { var fmtid = p.XF.ifmt||0; if(opts.cellNF) p.z = SSF._table[fmtid]; - if(p.t === 'e'){} + } catch(e) { if(opts.WTF) throw e; } + if(!opts || opts.cellText !== false) try { + if(p.t === 'e') { p.w = p.w || BErr[p.v]; } else if(fmtid === 0) { if(p.t === 'n') { if((p.v|0) === p.v) p.w = SSF._general_int(p.v); diff --git a/bits/80_parseods.js b/bits/80_parseods.js index 3f52438..9b38b18 100644 --- a/bits/80_parseods.js +++ b/bits/80_parseods.js @@ -135,7 +135,7 @@ var parse_content_xml = (function() { isstub = textpidx == 0; } if(comments.length > 0) { q.c = comments; comments = []; } - if(textp) q.w = textp; + if(textp && opts.cellText !== false) q.w = textp; if(!isstub || opts.sheetStubs) { if(!(opts.sheetRows && opts.sheetRows < R)) { if(opts.dense) { diff --git a/book.json b/book.json index f1e3a11..dd01ccb 100644 --- a/book.json +++ b/book.json @@ -3,7 +3,7 @@ "title": "SheetJS js-xlsx", "author": "sheetjs", "gitbook": "3.2.2", - "plugins": ["anchorjs", "ga", "sidebar-ad", "-sharing", "advanced-emoji"], + "plugins": ["anchorjs", "ga", "sidebar-ad", "-sharing", "-search", "advanced-emoji"], "pluginsConfig": { "anchorjs": { "icon": "#", diff --git a/docbits/00_intro.md b/docbits/00_intro.md index eab25b2..511a855 100644 --- a/docbits/00_intro.md +++ b/docbits/00_intro.md @@ -12,6 +12,8 @@ enhancements and additional features by request. [**Commercial Support**](http://sheetjs.com/support) +[**Rendered Documentation**](https://sheetjs.gitbooks.io/docs/) + [**In-Browser Demos**](http://sheetjs.com/demos) [**Source Code**](http://git.io/xlsx) diff --git a/docbits/54_shobject.md b/docbits/54_shobject.md index a860e10..4f82199 100644 --- a/docbits/54_shobject.md +++ b/docbits/54_shobject.md @@ -14,8 +14,9 @@ In addition to the base sheet keys, worksheets also add: the first cell (upper-left) in the range is set. - `ws['protect']`: object of write sheet protection properties. The `password` - key specifies the password. The writer uses the XOR obfuscation method. The - following keys control the sheet protection (same as ECMA-376 18.3.1.85): + key specifies the password for formats that support password-protected sheets + (XLSX/XLSB/XLS). The writer uses the XOR obfuscation method. The following + keys control the sheet protection (same as ECMA-376 18.3.1.85): | key | functionality disabled if value is true | |:----------------------|:-----------------------------------------------------| @@ -39,7 +40,7 @@ In addition to the base sheet keys, worksheets also add: ```typescript type AutoFilter = { - ref:string; // A-1 based range representing the AutoFilter table range + ref:string; // A-1 based range representing the AutoFilter table range } ``` diff --git a/test.js b/test.js index f0f7d37..4883ba5 100644 --- a/test.js +++ b/test.js @@ -284,6 +284,7 @@ describe('parse options', function() { if(typeof before != 'undefined') before(bef); else it('before', bef); describe('cell', function() { + var FSTPaths = [paths.fstxls, paths.fstxml, paths.fstxlsx, paths.fstxlsb, paths.fstods]; it('XLSX should generate HTML by default', function() { var wb = X.readFile(paths.cstxlsx); var ws = wb.Sheets.Sheet1; @@ -299,7 +300,7 @@ describe('parse options', function() { }); }); it('should generate formulae by default', function() { - [paths.fstxls, paths.fstxlsb].forEach(function(p) { + FSTPaths.forEach(function(p) { var wb = X.readFile(p); var found = false; wb.SheetNames.forEach(function(s) { @@ -312,7 +313,7 @@ describe('parse options', function() { }); }); it('should not generate formulae when requested', function() { - [paths.fstxls, paths.fstxlsb].forEach(function(p) { + FSTPaths.forEach(function(p) { var wb =X.readFile(p,{cellFormula:false}); wb.SheetNames.forEach(function(s) { var ws = wb.Sheets[s]; @@ -322,6 +323,30 @@ describe('parse options', function() { }); }); }); + it('should generate formatted text by default', function() { + FSTPaths.forEach(function(p) { + var wb = X.readFile(p); + var found = false; + wb.SheetNames.forEach(function(s) { + var ws = wb.Sheets[s]; + each_cell(ws, function(cell) { + if(typeof cell.w !== 'undefined') return (found = true); + }); + }); + assert(found); + }); + }); + it('should not generate formatted text when requested', function() { + FSTPaths.forEach(function(p) { + var wb =X.readFile(p,{cellText:false}); + wb.SheetNames.forEach(function(s) { + var ws = wb.Sheets[s]; + each_cell(ws, function(cell) { + assert(typeof cell.w === 'undefined'); + }); + }); + }); + }); it('should not generate number formats by default', function() { [paths.nfxls, paths.nfxlsx, paths.nfxlsb].forEach(function(p) { var wb = X.readFile(p); @@ -1218,7 +1243,7 @@ describe('roundtrip features', function() { if(m[0].t === 'n' && m[1].t === 'n') assert.equal(m[0].v, m[1].v); else if(m[0].t === 'd' && m[1].t === 'd') assert.equal(m[0].v.toString(), m[1].v.toString()); - else if(m[1].t === 'n') assert(Math.abs(datenum(new Date(m[0].v)) - m[1].v) < 0.01); /* TODO: 1sec adjustment */ + else if(m[1].t === 'n') assert(Math.abs(datenum(new Date(m[0].v)) - m[1].v) < 0.01); }); }); }); @@ -1253,7 +1278,7 @@ describe('roundtrip features', function() { }); (fs.existsSync(paths.pmxlsx) ? describe : describe.skip)('should preserve page margins', function() {[ - //['xlml', paths.pmxml], + ['xlml', paths.pmxml], ['xlsx', paths.pmxlsx], ['xlsb', paths.pmxlsb] ].forEach(function(w) { it(w[0], function() { diff --git a/xlsx.flow.js b/xlsx.flow.js index 785100f..e4e47d9 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -3330,26 +3330,36 @@ function write_cust_props(cp, opts)/*:string*/ { } /* Common Name -> XLML Name */ var XLMLDocPropsMap = { - Category: 'Category', - ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ - Keywords: 'Keywords', - LastAuthor: 'LastAuthor', - LastPrinted: 'LastPrinted', - RevNumber: 'Revision', - Author: 'Author', - Comments: 'Description', - Identifier: 'Identifier', /* NOTE: missing from schema */ - Language: 'Language', /* NOTE: missing from schema */ - Subject: 'Subject', Title: 'Title', + Subject: 'Subject', + Author: 'Author', + Keywords: 'Keywords', + Comments: 'Description', + LastAuthor: 'LastAuthor', + RevNumber: 'Revision', + Application: 'AppName', + /* TotalTime: 'TotalTime', */ + LastPrinted: 'LastPrinted', CreatedDate: 'Created', ModifiedDate: 'LastSaved', - - Application: 'AppName', - AppVersion: 'Version', - TotalTime: 'TotalTime', + /* Pages */ + /* Words */ + /* Characters */ + Category: 'Category', + /* PresentationFormat */ Manager: 'Manager', - Company: 'Company' + Company: 'Company', + /* Guid */ + /* HyperlinkBase */ + /* Bytes */ + /* Lines */ + /* Paragraphs */ + /* CharactersWithSpaces */ + AppVersion: 'Version', + + ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ + Identifier: 'Identifier', /* NOTE: missing from schema */ + Language: 'Language' /* NOTE: missing from schema */ }; var evert_XLMLDPM = evert(XLMLDocPropsMap); @@ -3358,19 +3368,21 @@ function xlml_set_prop(Props, tag/*:string*/, val) { Props[tag] = val; } - -/* TODO: verify */ function xlml_write_docprops(Props, opts) { var o = []; - CORE_PROPS.concat(EXT_PROPS).forEach(function(p) { + keys(XLMLDocPropsMap).map(function(m) { + for(var i = 0; i < CORE_PROPS.length; ++i) if(CORE_PROPS[i][1] == m) return CORE_PROPS[i]; + for(i = 0; i < EXT_PROPS.length; ++i) if(EXT_PROPS[i][1] == m) return EXT_PROPS[i]; + throw m; + }).forEach(function(p) { if(Props[p[1]] == null) return; var m = opts && opts.Props && opts.Props[p[1]] != null ? opts.Props[p[1]] : Props[p[1]]; switch(p[2]) { - case 'date': m = new Date(m).toISOString(); break; + case 'date': m = new Date(m).toISOString().replace(/\.\d*Z/,"Z"); break; } if(typeof m == 'number') m = String(m); else if(m === true || m === false) { m = m ? "1" : "0"; } - else if(m instanceof Date) m = new Date(m).toISOString(); + else if(m instanceof Date) m = new Date(m).toISOString().replace(/\.\d*Z/,""); o.push(writetag(XLMLDocPropsMap[p[1]] || p[1], m)); }); return writextag('DocumentProperties', o.join(""), {xmlns:XLMLNS.o }); @@ -10228,8 +10240,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { /* 18.3.1.73 row CT_Row */ for(ri = 0; ri < xlen; ++ri) if(x.charCodeAt(ri) === 62) break; ++ri; tag = parsexmltag(x.substr(0,ri), true); - /* SpreadSheetGear uses implicit r/c */ - tagr = typeof tag.r !== 'undefined' ? parseInt(tag.r, 10) : tagr+1; tagc = -1; + tagr = tag.r != null ? parseInt(tag.r, 10) : tagr+1; tagc = -1; if(opts.sheetRows && opts.sheetRows < tagr) continue; if(guess.s.r > tagr - 1) guess.s.r = tagr - 1; if(guess.e.r < tagr - 1) guess.e.r = tagr - 1; @@ -10281,7 +10292,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { p.F = arrayf[i][1]; } - if(tag.t === undefined && p.v === undefined) { + if(tag.t == null && p.v === undefined) { if(!opts.sheetStubs) continue; p.t = "z"; } @@ -10319,7 +10330,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { break; /* error string in .w, number in .v */ case 'e': - if(opts && opts.cellText === false) p.w = p.v; + if(!opts || opts.cellText !== false) p.w = p.v; p.v = RBErr[p.v]; break; } /* formatting */ @@ -10881,7 +10892,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { case 'n': p.v = val[1]; break; case 's': sstr = strs[val[1]]; p.v = sstr.t; p.r = sstr.r; break; case 'b': p.v = val[1] ? true : false; break; - case 'e': p.v = val[1]; p.w = BErr[p.v]; break; + case 'e': p.v = val[1]; if(opts.cellText !== false) p.w = BErr[p.v]; break; case 'str': p.t = 's'; p.v = utf8read(val[1]); break; } if((cf = styles.CellXf[val[0].iStyleRef])) safe_format(p,cf.ifmt,null,opts, themes, styles); @@ -12145,11 +12156,11 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a if(cell.v === undefined) cell.v=+xml; if(!cell.t) cell.t = 'n'; break; - case 'Error': cell.t = 'e'; cell.v = RBErr[xml]; cell.w = xml; break; + case 'Error': cell.t = 'e'; cell.v = RBErr[xml]; if(o.cellText !== false) cell.w = xml; break; default: cell.t = 's'; cell.v = xlml_fixstr(ss||xml); break; } safe_format_xlml(cell, nf, o); - if(o.cellFormula != null) { + if(o.cellFormula !== false) { if(cell.Formula) { var fstr = unescapexml(cell.Formula); /* strictly speaking, the leading = is required but some writers omit */ @@ -12861,17 +12872,107 @@ function write_sty_xlml(wb, opts)/*:string*/ { } /* WorksheetOptions */ function write_ws_xlml_wsopts(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ { + if(!ws) return ""; var o = []; + /* NOTE: spec technically allows any order, but stick with implied order */ + + /* FitToPage */ + /* DoNotDisplayColHeaders */ + /* DoNotDisplayRowHeaders */ + /* ViewableRange */ + /* Selection */ + /* GridlineColor */ + /* Name */ + /* ExcelWorksheetType */ + /* IntlMacro */ + /* Unsynced */ + /* Selected */ + /* CodeName */ + + if(ws['!margins']) { + o.push(""); + if(ws['!margins'].header) o.push(writextag("Header", null, {'x:Margin':ws['!margins'].header})); + if(ws['!margins'].footer) o.push(writextag("Footer", null, {'x:Margin':ws['!margins'].footer})); + o.push(writextag("PageMargins", null, { + 'x:Bottom': ws['!margins'].bottom || "0.75", + 'x:Left': ws['!margins'].left || "0.7", + 'x:Right': ws['!margins'].right || "0.7", + 'x:Top': ws['!margins'].top || "0.75" + })); + o.push(""); + } + /* PageSetup */ + /* DisplayPageBreak */ + /* TransitionExpressionEvaluation */ + /* TransitionFormulaEntry */ + /* Print */ + /* Zoom */ + /* PageLayoutZoom */ + /* PageBreakZoom */ + /* ShowPageBreakZoom */ + /* DefaultRowHeight */ + /* DefaultColumnWidth */ + /* StandardWidth */ + if(wb && wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx]) { /* Visible */ - if(!!wb.Workbook.Sheets[idx].Hidden) o.push("" + (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden") + ""); + if(!!wb.Workbook.Sheets[idx].Hidden) o.push(writextag("Visible", (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden"), {})); 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(""); } } + + /* LeftColumnVisible */ + /* DisplayRightToLeft */ + /* GridlineColorIndex */ + /* DisplayFormulas */ + /* DoNotDisplayGridlines */ + /* DoNotDisplayHeadings */ + /* DoNotDisplayOutline */ + /* ApplyAutomaticOutlineStyles */ + /* NoSummaryRowsBelowDetail */ + /* NoSummaryColumnsRightDetail */ + /* DoNotDisplayZeros */ + /* ActiveRow */ + /* ActiveColumn */ + /* FilterOn */ + /* RangeSelection */ + /* TopRowVisible */ + /* TopRowBottomPane */ + /* LeftColumnRightPane */ + /* ActivePane */ + /* SplitHorizontal */ + /* SplitVertical */ + /* FreezePanes */ + /* FrozenNoSplit */ + /* TabColorIndex */ + /* Panes */ + + /* NOTE: Password not supported in XLML Format */ + if(ws['!protect']) { + o.push(writetag("ProtectContents", "True")); + if(ws['!protect'].objects) o.push(writetag("ProtectObjects", "True")); + if(ws['!protect'].scenarios) o.push(writetag("ProtectScenarios", "True")); + if(ws['!protect'].selectLockedCells != null && !ws['!protect'].selectLockedCells) o.push(writetag("EnableSelection", "NoSelection")); + else if(ws['!protect'].selectUnlockedCells != null && !ws['!protect'].selectUnlockedCells) o.push(writetag("EnableSelection", "UnlockedCells")); + [ + [ "formatColumns", "AllowFormatCells" ], + [ "formatRows", "AllowSizeCols" ], + [ "formatCells", "AllowSizeRows" ], + [ "insertColumns", "AllowInsertCols" ], + [ "insertRows", "AllowInsertRows" ], + [ "insertHyperlinks", "AllowInsertHyperlinks" ], + [ "deleteColumns", "AllowDeleteCols" ], + [ "deleteRows", "AllowDeleteRows" ], + [ "sort", "AllowSort" ], + [ "autoFilter", "AllowFilter" ], + [ "pivotTables", "AllowUsePivotTables" ] + ].forEach(function(x) { if(ws['!protect'][x[0]]) o.push("<"+x[1]+"/>"); }); + } + if(o.length == 0) return ""; return writextag("WorksheetOptions", o.join(""), {xmlns:XLMLNS.x}); } @@ -13048,12 +13149,13 @@ function slurp(R, blob, length/*:number*/, opts) { function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) { if(p.t === 'z') return; - if(p.t === 'e') { p.w = p.w || BErr[p.v]; } if(!p.XF) return; try { var fmtid = p.XF.ifmt||0; if(opts.cellNF) p.z = SSF._table[fmtid]; - if(p.t === 'e'){} + } catch(e) { if(opts.WTF) throw e; } + if(!opts || opts.cellText !== false) try { + if(p.t === 'e') { p.w = p.w || BErr[p.v]; } else if(fmtid === 0) { if(p.t === 'n') { if((p.v|0) === p.v) p.w = SSF._general_int(p.v); @@ -15436,7 +15538,7 @@ var parse_content_xml = (function() { isstub = textpidx == 0; } if(comments.length > 0) { q.c = comments; comments = []; } - if(textp) q.w = textp; + if(textp && opts.cellText !== false) q.w = textp; if(!isstub || opts.sheetStubs) { if(!(opts.sheetRows && opts.sheetRows < R)) { if(opts.dense) { diff --git a/xlsx.js b/xlsx.js index 4efc39f..08a9047 100644 --- a/xlsx.js +++ b/xlsx.js @@ -3271,26 +3271,36 @@ function write_cust_props(cp, opts) { } /* Common Name -> XLML Name */ var XLMLDocPropsMap = { - Category: 'Category', - ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ - Keywords: 'Keywords', - LastAuthor: 'LastAuthor', - LastPrinted: 'LastPrinted', - RevNumber: 'Revision', - Author: 'Author', - Comments: 'Description', - Identifier: 'Identifier', /* NOTE: missing from schema */ - Language: 'Language', /* NOTE: missing from schema */ - Subject: 'Subject', Title: 'Title', + Subject: 'Subject', + Author: 'Author', + Keywords: 'Keywords', + Comments: 'Description', + LastAuthor: 'LastAuthor', + RevNumber: 'Revision', + Application: 'AppName', + /* TotalTime: 'TotalTime', */ + LastPrinted: 'LastPrinted', CreatedDate: 'Created', ModifiedDate: 'LastSaved', - - Application: 'AppName', - AppVersion: 'Version', - TotalTime: 'TotalTime', + /* Pages */ + /* Words */ + /* Characters */ + Category: 'Category', + /* PresentationFormat */ Manager: 'Manager', - Company: 'Company' + Company: 'Company', + /* Guid */ + /* HyperlinkBase */ + /* Bytes */ + /* Lines */ + /* Paragraphs */ + /* CharactersWithSpaces */ + AppVersion: 'Version', + + ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ + Identifier: 'Identifier', /* NOTE: missing from schema */ + Language: 'Language' /* NOTE: missing from schema */ }; var evert_XLMLDPM = evert(XLMLDocPropsMap); @@ -3299,19 +3309,21 @@ function xlml_set_prop(Props, tag, val) { Props[tag] = val; } - -/* TODO: verify */ function xlml_write_docprops(Props, opts) { var o = []; - CORE_PROPS.concat(EXT_PROPS).forEach(function(p) { + keys(XLMLDocPropsMap).map(function(m) { + for(var i = 0; i < CORE_PROPS.length; ++i) if(CORE_PROPS[i][1] == m) return CORE_PROPS[i]; + for(i = 0; i < EXT_PROPS.length; ++i) if(EXT_PROPS[i][1] == m) return EXT_PROPS[i]; + throw m; + }).forEach(function(p) { if(Props[p[1]] == null) return; var m = opts && opts.Props && opts.Props[p[1]] != null ? opts.Props[p[1]] : Props[p[1]]; switch(p[2]) { - case 'date': m = new Date(m).toISOString(); break; + case 'date': m = new Date(m).toISOString().replace(/\.\d*Z/,"Z"); break; } if(typeof m == 'number') m = String(m); else if(m === true || m === false) { m = m ? "1" : "0"; } - else if(m instanceof Date) m = new Date(m).toISOString(); + else if(m instanceof Date) m = new Date(m).toISOString().replace(/\.\d*Z/,""); o.push(writetag(XLMLDocPropsMap[p[1]] || p[1], m)); }); return writextag('DocumentProperties', o.join(""), {xmlns:XLMLNS.o }); @@ -10166,8 +10178,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { /* 18.3.1.73 row CT_Row */ for(ri = 0; ri < xlen; ++ri) if(x.charCodeAt(ri) === 62) break; ++ri; tag = parsexmltag(x.substr(0,ri), true); - /* SpreadSheetGear uses implicit r/c */ - tagr = typeof tag.r !== 'undefined' ? parseInt(tag.r, 10) : tagr+1; tagc = -1; + tagr = tag.r != null ? parseInt(tag.r, 10) : tagr+1; tagc = -1; if(opts.sheetRows && opts.sheetRows < tagr) continue; if(guess.s.r > tagr - 1) guess.s.r = tagr - 1; if(guess.e.r < tagr - 1) guess.e.r = tagr - 1; @@ -10219,7 +10230,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { p.F = arrayf[i][1]; } - if(tag.t === undefined && p.v === undefined) { + if(tag.t == null && p.v === undefined) { if(!opts.sheetStubs) continue; p.t = "z"; } @@ -10257,7 +10268,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { break; /* error string in .w, number in .v */ case 'e': - if(opts && opts.cellText === false) p.w = p.v; + if(!opts || opts.cellText !== false) p.w = p.v; p.v = RBErr[p.v]; break; } /* formatting */ @@ -10819,7 +10830,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles) { case 'n': p.v = val[1]; break; case 's': sstr = strs[val[1]]; p.v = sstr.t; p.r = sstr.r; break; case 'b': p.v = val[1] ? true : false; break; - case 'e': p.v = val[1]; p.w = BErr[p.v]; break; + case 'e': p.v = val[1]; if(opts.cellText !== false) p.w = BErr[p.v]; break; case 'str': p.t = 's'; p.v = utf8read(val[1]); break; } if((cf = styles.CellXf[val[0].iStyleRef])) safe_format(p,cf.ifmt,null,opts, themes, styles); @@ -12081,11 +12092,11 @@ function parse_xlml_data(xml, ss, data, cell, base, styles, csty, row, arrayf, o if(cell.v === undefined) cell.v=+xml; if(!cell.t) cell.t = 'n'; break; - case 'Error': cell.t = 'e'; cell.v = RBErr[xml]; cell.w = xml; break; + case 'Error': cell.t = 'e'; cell.v = RBErr[xml]; if(o.cellText !== false) cell.w = xml; break; default: cell.t = 's'; cell.v = xlml_fixstr(ss||xml); break; } safe_format_xlml(cell, nf, o); - if(o.cellFormula != null) { + if(o.cellFormula !== false) { if(cell.Formula) { var fstr = unescapexml(cell.Formula); /* strictly speaking, the leading = is required but some writers omit */ @@ -12795,17 +12806,107 @@ function write_sty_xlml(wb, opts) { } /* WorksheetOptions */ function write_ws_xlml_wsopts(ws, opts, idx, wb) { + if(!ws) return ""; var o = []; + /* NOTE: spec technically allows any order, but stick with implied order */ + + /* FitToPage */ + /* DoNotDisplayColHeaders */ + /* DoNotDisplayRowHeaders */ + /* ViewableRange */ + /* Selection */ + /* GridlineColor */ + /* Name */ + /* ExcelWorksheetType */ + /* IntlMacro */ + /* Unsynced */ + /* Selected */ + /* CodeName */ + + if(ws['!margins']) { + o.push(""); + if(ws['!margins'].header) o.push(writextag("Header", null, {'x:Margin':ws['!margins'].header})); + if(ws['!margins'].footer) o.push(writextag("Footer", null, {'x:Margin':ws['!margins'].footer})); + o.push(writextag("PageMargins", null, { + 'x:Bottom': ws['!margins'].bottom || "0.75", + 'x:Left': ws['!margins'].left || "0.7", + 'x:Right': ws['!margins'].right || "0.7", + 'x:Top': ws['!margins'].top || "0.75" + })); + o.push(""); + } + /* PageSetup */ + /* DisplayPageBreak */ + /* TransitionExpressionEvaluation */ + /* TransitionFormulaEntry */ + /* Print */ + /* Zoom */ + /* PageLayoutZoom */ + /* PageBreakZoom */ + /* ShowPageBreakZoom */ + /* DefaultRowHeight */ + /* DefaultColumnWidth */ + /* StandardWidth */ + if(wb && wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx]) { /* Visible */ - if(!!wb.Workbook.Sheets[idx].Hidden) o.push("" + (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden") + ""); + if(!!wb.Workbook.Sheets[idx].Hidden) o.push(writextag("Visible", (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden"), {})); 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(""); } } + + /* LeftColumnVisible */ + /* DisplayRightToLeft */ + /* GridlineColorIndex */ + /* DisplayFormulas */ + /* DoNotDisplayGridlines */ + /* DoNotDisplayHeadings */ + /* DoNotDisplayOutline */ + /* ApplyAutomaticOutlineStyles */ + /* NoSummaryRowsBelowDetail */ + /* NoSummaryColumnsRightDetail */ + /* DoNotDisplayZeros */ + /* ActiveRow */ + /* ActiveColumn */ + /* FilterOn */ + /* RangeSelection */ + /* TopRowVisible */ + /* TopRowBottomPane */ + /* LeftColumnRightPane */ + /* ActivePane */ + /* SplitHorizontal */ + /* SplitVertical */ + /* FreezePanes */ + /* FrozenNoSplit */ + /* TabColorIndex */ + /* Panes */ + + /* NOTE: Password not supported in XLML Format */ + if(ws['!protect']) { + o.push(writetag("ProtectContents", "True")); + if(ws['!protect'].objects) o.push(writetag("ProtectObjects", "True")); + if(ws['!protect'].scenarios) o.push(writetag("ProtectScenarios", "True")); + if(ws['!protect'].selectLockedCells != null && !ws['!protect'].selectLockedCells) o.push(writetag("EnableSelection", "NoSelection")); + else if(ws['!protect'].selectUnlockedCells != null && !ws['!protect'].selectUnlockedCells) o.push(writetag("EnableSelection", "UnlockedCells")); + [ + [ "formatColumns", "AllowFormatCells" ], + [ "formatRows", "AllowSizeCols" ], + [ "formatCells", "AllowSizeRows" ], + [ "insertColumns", "AllowInsertCols" ], + [ "insertRows", "AllowInsertRows" ], + [ "insertHyperlinks", "AllowInsertHyperlinks" ], + [ "deleteColumns", "AllowDeleteCols" ], + [ "deleteRows", "AllowDeleteRows" ], + [ "sort", "AllowSort" ], + [ "autoFilter", "AllowFilter" ], + [ "pivotTables", "AllowUsePivotTables" ] + ].forEach(function(x) { if(ws['!protect'][x[0]]) o.push("<"+x[1]+"/>"); }); + } + if(o.length == 0) return ""; return writextag("WorksheetOptions", o.join(""), {xmlns:XLMLNS.x}); } @@ -12982,12 +13083,13 @@ function slurp(R, blob, length, opts) { function safe_format_xf(p, opts, date1904) { if(p.t === 'z') return; - if(p.t === 'e') { p.w = p.w || BErr[p.v]; } if(!p.XF) return; try { var fmtid = p.XF.ifmt||0; if(opts.cellNF) p.z = SSF._table[fmtid]; - if(p.t === 'e'){} + } catch(e) { if(opts.WTF) throw e; } + if(!opts || opts.cellText !== false) try { + if(p.t === 'e') { p.w = p.w || BErr[p.v]; } else if(fmtid === 0) { if(p.t === 'n') { if((p.v|0) === p.v) p.w = SSF._general_int(p.v); @@ -15370,7 +15472,7 @@ var parse_content_xml = (function() { isstub = textpidx == 0; } if(comments.length > 0) { q.c = comments; comments = []; } - if(textp) q.w = textp; + if(textp && opts.cellText !== false) q.w = textp; if(!isstub || opts.sheetStubs) { if(!(opts.sheetRows && opts.sheetRows < R)) { if(opts.dense) {