From c0a8bef07ca517e83fe7d112ba10a2920735158b Mon Sep 17 00:00:00 2001 From: SheetJS Date: Sat, 15 Feb 2014 00:08:18 -0500 Subject: [PATCH] version bump 0.5.7: WTF-ification - opts.bookDeps parses calc chain; now chains are not parsed by default - getzipdata safe option obviates some try-catch blocks - parsing cell type 'd' (h/t @malphettes, yet again :) - opts.WTF throws on errors --- README.md | 2 + bin/xlsx2csv.njs | 11 ++- bits/31_version.js | 2 +- bits/35_ziputils.js | 6 ++ bits/65_comments.js | 3 +- bits/72_wsxml.js | 13 +-- bits/73_wsbin.js | 2 +- bits/84_defaults.js | 3 +- bits/85_parsezip.js | 42 ++++---- package.json | 3 +- test.js | 236 ++++++++++++++++++++++++-------------------- tests/files | 2 +- xlsx.js | 71 ++++++------- 13 files changed, 214 insertions(+), 182 deletions(-) diff --git a/README.md b/README.md index e6e3cca..bd35219 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ The exported `read` and `readFile` functions accept an options argument: | cellHTML | true | Parse rich text and save HTML to the .h field | | cellNF | false | Save number format string to the .z field | | sheetStubs | false | Create cell objects for stub cells | +| bookDeps | false | If true, parse calculation chains | | bookProps | false | If true, only parse enough to get book metadata ** | | bookSheets | false | If true, only parse enough to get the sheet names | @@ -87,6 +88,7 @@ The exported `read` and `readFile` functions accept an options argument: - Even if `cellNF` is false, formatted text (.w) will be generated - In some cases, sheets may be parsed even if `bookSheets` is false. - `bookSheets` and `bookProps` combine to give both sets of information +- `Deps` will be an empty object if `bookDeps` is falsy The defaults are enumerated in bits/84_defaults.js diff --git a/bin/xlsx2csv.njs b/bin/xlsx2csv.njs index c1949d7..de34e91 100755 --- a/bin/xlsx2csv.njs +++ b/bin/xlsx2csv.njs @@ -44,13 +44,14 @@ if(!fs.existsSync(filename)) { process.exit(2); } -if(program.dev) X.verbose = 2; - -var opts = {}; +var opts = {}, wb; if(program.listSheets) opts.bookSheets = true; -var wb; -if(program.dev) wb = X.readFile(filename, opts); +if(program.dev) { + X.verbose = 2; + opts.WTF = true; + wb = X.readFile(filename, opts); +} else try { wb = X.readFile(filename, opts); } catch(e) { diff --git a/bits/31_version.js b/bits/31_version.js index 70a5ece..4eda6dc 100644 --- a/bits/31_version.js +++ b/bits/31_version.js @@ -1 +1 @@ -XLSX.version = '0.5.6'; +XLSX.version = '0.5.7'; diff --git a/bits/35_ziputils.js b/bits/35_ziputils.js index f514a57..328a112 100644 --- a/bits/35_ziputils.js +++ b/bits/35_ziputils.js @@ -18,6 +18,12 @@ function getzipfile(zip, file) { throw new Error("Cannot find file " + file + " in zip"); } +function getzipdata(zip, file, safe) { + if(!safe) return getdata(getzipfile(zip, file)); + if(!file) return null; + try { return getzipdata(zip, file); } catch(e) { return null; } +} + var _fs, jszip; if(typeof JSZip !== 'undefined') jszip = JSZip; if (typeof exports !== 'undefined') { diff --git a/bits/65_comments.js b/bits/65_comments.js index ef4d01a..83df24e 100644 --- a/bits/65_comments.js +++ b/bits/65_comments.js @@ -25,7 +25,8 @@ function parse_comments_xml(data, opts) { function parse_comments(zip, dirComments, sheets, sheetRels, opts) { for(var i = 0; i != dirComments.length; ++i) { var canonicalpath=dirComments[i]; - var comments=parse_comments_xml(getdata(getzipfile(zip, canonicalpath.replace(/^\//,''))), opts); + var comments=parse_comments_xml(getzipdata(zip, canonicalpath.replace(/^\//,''), true), opts); + if(!comments || !comments.length) return; // find the sheets targeted by these comments var sheetNames = Object.keys(sheets); for(var j = 0; j != sheetNames.length; ++j) { diff --git a/bits/72_wsxml.js b/bits/72_wsxml.js index 18e8343..dcc342b 100644 --- a/bits/72_wsxml.js +++ b/bits/72_wsxml.js @@ -43,6 +43,7 @@ function parse_ws_xml(data, opts) { else p.t = (cell.t ? cell.t : "n"); // default is "n" in schema if(refguess.s.c > idx) refguess.s.c = idx; if(refguess.e.c < idx) refguess.e.c = idx; + /* 18.18.11 t ST_CellType */ switch(p.t) { case 'n': p.v = parseFloat(p.v); break; case 's': { @@ -57,20 +58,14 @@ function parse_ws_xml(data, opts) { is = is ? parse_si(is[1]) : {t:"",r:""}; p.t = 'str'; p.v = is.t; break; // inline string - case 'b': - switch(p.v) { - case '0': case 'FALSE': case "false": case false: p.v=false; break; - case '1': case 'TRUE': case "true": case true: p.v=true; break; - default: throw "Unrecognized boolean: " + p.v; - } break; - case 'd': + case 'b': if(typeof p.v !== 'boolean') p.v = parsexmlbool(p.v); break; + case 'd': /* TODO: date1904 logic */ var epoch = Date.parse(p.v); p.v = (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); p.t = 'n'; break; /* in case of error, stick value in .raw */ case 'e': p.raw = RBErr[p.v]; break; - default: throw "Unrecognized cell type: " + p.t; } /* formatting */ @@ -82,7 +77,7 @@ function parse_ws_xml(data, opts) { try { p.w = SSF.format(fmtid,p.v,_ssfopts); if(opts.cellNF) p.z = SSF._table[fmtid]; - } catch(e) { } + } catch(e) { if(opts.WTF) throw e; } s[cell.r] = p; }); }); diff --git a/bits/73_wsbin.js b/bits/73_wsbin.js index b3ee0f9..17bdf92 100644 --- a/bits/73_wsbin.js +++ b/bits/73_wsbin.js @@ -152,7 +152,7 @@ var parse_ws_bin = function(data, opts) { if((cf = styles.CellXf[val[0].iStyleRef])) try { p.w = SSF.format(cf.ifmt,p.v,_ssfopts); if(opts.cellNF) p.z = SSF._table[cf.ifmt]; - } catch(e) { } + } catch(e) { if(opts.WTF) throw e; } s[encode_cell({c:val[0].c,r:row.r})] = p; break; // TODO diff --git a/bits/84_defaults.js b/bits/84_defaults.js index ff9c5ea..66945f4 100644 --- a/bits/84_defaults.js +++ b/bits/84_defaults.js @@ -6,10 +6,11 @@ function fixopts(opts) { ['sheetStubs', false], /* emit empty cells */ + ['bookDeps', false], /* parse calculation chains */ ['bookSheets', false], /* only try to get sheet names (no Sheets) */ ['bookProps', false], /* only try to get properties (no Sheets) */ - ['WTF', false] /* WTF mode (do not use) */ + ['WTF', false] /* WTF mode (throws errors) */ ]; defaults.forEach(function(d) { if(typeof opts[d[0]] === 'undefined') opts[d[0]] = d[1]; }); } diff --git a/bits/85_parsezip.js b/bits/85_parsezip.js index bb57abd..fe5e16f 100644 --- a/bits/85_parsezip.js +++ b/bits/85_parsezip.js @@ -4,8 +4,9 @@ function parseZip(zip, opts) { reset_cp(); var entries = Object.keys(zip.files); var keys = entries.filter(function(x){return x.substr(-1) != '/';}).sort(); - var dir = parseCT(getdata(getzipfile(zip, '[Content_Types].xml'))); + var dir = parseCT(getzipdata(zip, '[Content_Types].xml')); var xlsb = false; + var sheets; if(dir.workbooks.length === 0) { var binname = "xl/workbook.bin"; if(!getzipfile(zip,binname)) throw new Error("Could not find workbook entry"); @@ -15,42 +16,43 @@ function parseZip(zip, opts) { if(!opts.bookSheets && !opts.bookProps) { strs = {}; - if(dir.sst) strs=parse_sst(getdata(getzipfile(zip, dir.sst.replace(/^\//,''))), dir.sst, opts); + if(dir.sst) strs=parse_sst(getzipdata(zip, dir.sst.replace(/^\//,'')), dir.sst, opts); styles = {}; - if(dir.style) styles = parse_sty(getdata(getzipfile(zip, dir.style.replace(/^\//,''))),dir.style); + if(dir.style) styles = parse_sty(getzipdata(zip, dir.style.replace(/^\//,'')),dir.style); } - var wb = parse_wb(getdata(getzipfile(zip, dir.workbooks[0].replace(/^\//,''))), dir.workbooks[0], opts); + var wb = parse_wb(getzipdata(zip, dir.workbooks[0].replace(/^\//,'')), dir.workbooks[0], opts); var props = {}, propdata = ""; try { - propdata = dir.coreprops.length !== 0 ? getdata(getzipfile(zip, dir.coreprops[0].replace(/^\//,''))) : ""; - propdata += dir.extprops.length !== 0 ? getdata(getzipfile(zip, dir.extprops[0].replace(/^\//,''))) : ""; + propdata = dir.coreprops.length !== 0 ? getzipdata(zip, dir.coreprops[0].replace(/^\//,'')) : ""; + propdata += dir.extprops.length !== 0 ? getzipdata(zip, dir.extprops[0].replace(/^\//,'')) : ""; props = propdata !== "" ? parseProps(propdata) : {}; } catch(e) { } var custprops = {}; if(!opts.bookSheets || opts.bookProps) { - if (dir.custprops.length !== 0) try { - propdata = getdata(getzipfile(zip, dir.custprops[0].replace(/^\//,''))); - custprops = parseCustomProps(propdata); - } catch(e) {/*console.error(e);*/} + if (dir.custprops.length !== 0) { + propdata = getzipdata(zip, dir.custprops[0].replace(/^\//,''), true); + if(propdata) custprops = parseCustomProps(propdata); + } } var out = {}; if(opts.bookSheets || opts.bookProps) { - var sheets; if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames; else if(wb.Sheets) sheets = wb.Sheets.map(function(x){ return x.name; }); if(opts.bookProps) { out.Props = props; out.Custprops = custprops; } if(typeof sheets !== 'undefined') out.SheetNames = sheets; if(opts.bookSheets ? out.SheetNames : opts.bookProps) return out; } + sheets = {}; var deps = {}; - if(dir.calcchain) deps=parseDeps(getdata(getzipfile(zip, dir.calcchain.replace(/^\//,'')))); - var sheets = {}, i=0; + if(opts.bookDeps && dir.calcchain) deps=parseDeps(getzipdata(zip, dir.calcchain.replace(/^\//,''))); + + var i=0; var sheetRels = {}; var path, relsPath; if(!props.Worksheets) { @@ -62,12 +64,12 @@ function parseZip(zip, opts) { props.SheetNames[j] = wbsheets[j].name; } for(i = 0; i != props.Worksheets; ++i) { - try { /* TODO: remove these guards */ + try { path = 'xl/worksheets/sheet' + (i+1) + (xlsb?'.bin':'.xml'); relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels"); - sheets[props.SheetNames[i]]=parse_ws(getdata(getzipfile(zip, path)),path,opts); - sheetRels[props.SheetNames[i]]=parseRels(getdata(getzipfile(zip, relsPath)), path); - } catch(e) {} + sheets[props.SheetNames[i]]=parse_ws(getzipdata(zip, path),path,opts); + sheetRels[props.SheetNames[i]]=parseRels(getzipdata(zip, relsPath, true), path); + } catch(e) { if(opts.WTF) throw e; } } } else { for(i = 0; i != props.Worksheets; ++i) { @@ -75,9 +77,9 @@ function parseZip(zip, opts) { //var path = dir.sheets[i].replace(/^\//,''); path = 'xl/worksheets/sheet' + (i+1) + (xlsb?'.bin':'.xml'); relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels"); - sheets[props.SheetNames[i]]=parse_ws(getdata(getzipfile(zip, path)),path,opts); - sheetRels[props.SheetNames[i]]=parseRels(getdata(getzipfile(zip, relsPath)), path); - } catch(e) {/*console.error(e);*/} + sheets[props.SheetNames[i]]=parse_ws(getzipdata(zip, path),path,opts); + sheetRels[props.SheetNames[i]]=parseRels(getzipdata(zip, relsPath, true), path); + } catch(e) { if(opts.WTF) throw e; } } } diff --git a/package.json b/package.json index 3c4d11b..197621e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xlsx", - "version": "0.5.6", + "version": "0.5.7", "author": "sheetjs", "description": "XLSB / XLSX / XLSM parser", "keywords": [ "xlsx", "xlsb", "xlsm", "office", "excel", "spreadsheet" ], @@ -11,6 +11,7 @@ "dependencies": { "ssf":"~0.5.6", "codepage":"", + "cfb":"", "jszip":"~2.1.0", "commander":"" }, diff --git a/test.js b/test.js index 87d5da7..e43ec86 100644 --- a/test.js +++ b/test.js @@ -1,7 +1,10 @@ /* vim: set ts=2: */ var XLSX; var fs = require('fs'), assert = require('assert'); -describe('source',function(){ it('should load', function(){ XLSX = require('./'); });}); +describe('source',function(){it('should load',function(){XLSX=require('./');});}); + +var opts = {}; +if(process.env.WTF) opts.WTF = true; var ex = [".xlsb", ".xlsm", ".xlsx"]; if(process.env.FMTS) ex=process.env.FMTS.split(":").map(function(x){return x[0]==="."?x:"."+x;}); @@ -15,9 +18,11 @@ function fixsheetname(x) { return x.substr(0,31); } function normalizecsv(x) { return x.replace(/\t/g,",").replace(/#{255}/g,"").replace(/"/g,"").replace(/[\n\r]+/g,"\n").replace(/\n*$/,""); } +var dir = "./test_files/"; + function parsetest(x, wb) { describe(x + ' should have all bits', function() { - var sname = './test_files/2011/' + x + '.sheetnames'; + var sname = dir + '2011/' + x + '.sheetnames'; it('should have all sheets', function() { wb.SheetNames.forEach(function(y) { assert(wb.Sheets[y], 'bad sheet ' + y); }); }); @@ -50,7 +55,7 @@ function parsetest(x, wb) { }); describe(x + ' should generate correct output', function() { wb.SheetNames.forEach(function(ws, i) { - var name = ('./test_files/' + x + '.' + i + '.csv'); + var name = (dir + x + '.' + i + '.csv'); it('#' + i + ' (' + ws + ')', fs.existsSync(name) ? function() { var file = fs.readFileSync(name, 'utf-8'); var csv = XLSX.utils.make_csv(wb.Sheets[ws]); @@ -63,156 +68,169 @@ function parsetest(x, wb) { describe('should parse test files', function() { files.forEach(function(x) { it(x, x.substr(-8) == ".pending" ? null : function() { - var wb = XLSX.readFile('./test_files/' + x); + var wb = XLSX.readFile(dir + x, opts); parsetest(x, wb); }); }); }); -describe('should have comment as part of cell\'s properties', function(){ - var ws; - before(function() { - XLSX = require('./'); - var wb = XLSX.readFile('./test_files/apachepoi_SimpleWithComments.xlsx'); - var sheetName = 'Sheet1'; - ws = wb.Sheets[sheetName]; - }); - it('Parse comments.xml and insert into cell',function(){ - assert.equal(ws.B1.c.length, 1,"must have 1 comment"); - assert.equal(ws.B1.c[0].t, "Yegor Kozlov:\r\nfirst cell", "must have the concatenated texts"); - assert.equal(ws.B1.c[0].h, 'Yegor Kozlov:
first cell
', "must have the html representation"); - assert.equal(ws.B1.c[0].r, 'Yegor Kozlov:\r\nfirst cell', "must have the rich text representation"); - assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author"); - }); -}); - describe('options', function() { var html_cell_types = ['s']; before(function() { XLSX = require('./'); }); - it('should generate HTML by default', function() { - var wb = XLSX.readFile('./test_files/comments_stress_test.xlsx'); - var ws = wb.Sheets.Sheet1; - Object.keys(ws).forEach(function(addr) { - if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; - assert(html_cell_types.indexOf(ws[addr].t) === -1 || ws[addr].h); - }); - }); - it('should not generate HTML when requested', function() { - var wb = XLSX.readFile('./test_files/comments_stress_test.xlsx', {cellHTML: false}); - var ws = wb.Sheets.Sheet1; - Object.keys(ws).forEach(function(addr) { - if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; - assert(typeof ws[addr].h === 'undefined'); - }); - }); - it('should generate formulae by default', function() { - var wb = XLSX.readFile('./test_files/formula_stress_test.xlsb'); - var found = false; - wb.SheetNames.forEach(function(s) { - var ws = wb.Sheets[s]; + describe('cell', function() { + it('should generate HTML by default', function() { + var wb = XLSX.readFile(dir + 'comments_stress_test.xlsx'); + var ws = wb.Sheets.Sheet1; Object.keys(ws).forEach(function(addr) { if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; - if(typeof ws[addr].f !== 'undefined') return found = true; + assert(html_cell_types.indexOf(ws[addr].t) === -1 || ws[addr].h); }); }); - assert(found); - }); - it('should not generate formulae when requested', function() { - var wb = XLSX.readFile('./test_files/formula_stress_test.xlsb', {cellFormula: false}); - wb.SheetNames.forEach(function(s) { - var ws = wb.Sheets[s]; + it('should not generate HTML when requested', function() { + var wb = XLSX.readFile(dir+'comments_stress_test.xlsx', {cellHTML:false}); + var ws = wb.Sheets.Sheet1; Object.keys(ws).forEach(function(addr) { if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; - assert(typeof ws[addr].f === 'undefined'); + assert(typeof ws[addr].h === 'undefined'); + }); + }); + it('should generate formulae by default', function() { + var wb = XLSX.readFile(dir + 'formula_stress_test.xlsb'); + var found = false; + wb.SheetNames.forEach(function(s) { + var ws = wb.Sheets[s]; + Object.keys(ws).forEach(function(addr) { + if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; + if(typeof ws[addr].f !== 'undefined') return found = true; + }); + }); + assert(found); + }); + it('should not generate formulae when requested', function() { + var wb =XLSX.readFile(dir+'formula_stress_test.xlsb',{cellFormula:false}); + wb.SheetNames.forEach(function(s) { + var ws = wb.Sheets[s]; + Object.keys(ws).forEach(function(addr) { + if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; + assert(typeof ws[addr].f === 'undefined'); + }); + }); + }); + it('should not generate number formats by default', function() { + var wb = XLSX.readFile(dir+'number_format.xlsm'); + wb.SheetNames.forEach(function(s) { + var ws = wb.Sheets[s]; + Object.keys(ws).forEach(function(addr) { + if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; + assert(typeof ws[addr].z === 'undefined'); + }); + }); + }); + it('should generate number formats when requested', function() { + var wb = XLSX.readFile(dir+'number_format.xlsm', {cellNF: true}); + wb.SheetNames.forEach(function(s) { + var ws = wb.Sheets[s]; + Object.keys(ws).forEach(function(addr) { + if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; + assert(typeof ws[addr].t!== 'n' || typeof ws[addr].z !== 'undefined'); + }); }); }); }); - it('should not generate sheet stubs by default', function() { - var wb = XLSX.readFile('./test_files/merge_cells.xlsx'); - assert.throws(function() { wb.Sheets.Merge.A2.v; }); - }); - it('should generate sheet stubs when requested', function() { - var wb = XLSX.readFile('./test_files/merge_cells.xlsx', {sheetStubs:true}); - assert(typeof wb.Sheets.Merge.A2.t !== 'undefined'); - }); - it('should not generate number formats by default', function() { - var wb = XLSX.readFile('./test_files/number_format.xlsm'); - wb.SheetNames.forEach(function(s) { - var ws = wb.Sheets[s]; - Object.keys(ws).forEach(function(addr) { - if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; - assert(typeof ws[addr].z === 'undefined'); - }); + describe('sheet', function() { + it('should not generate sheet stubs by default', function() { + var wb = XLSX.readFile(dir+'merge_cells.xlsx'); + assert.throws(function() { wb.Sheets.Merge.A2.v; }); }); - }); - it('should generate number formats when requested', function() { - var wb = XLSX.readFile('./test_files/number_format.xlsm', {cellNF: true}); - wb.SheetNames.forEach(function(s) { - var ws = wb.Sheets[s]; - Object.keys(ws).forEach(function(addr) { - if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; - assert(typeof ws[addr].t !== 'n' || typeof ws[addr].z !== 'undefined'); - }); + it('should generate sheet stubs when requested', function() { + var wb = XLSX.readFile(dir+'merge_cells.xlsx', {sheetStubs:true}); + assert(typeof wb.Sheets.Merge.A2.t !== 'undefined'); }); }); describe('book', function() { it('bookSheets should not generate sheets', function() { - var wb = XLSX.readFile('./test_files/merge_cells.xlsx', {bookSheets:true}); + var wb = XLSX.readFile(dir+'merge_cells.xlsx', {bookSheets:true}); assert(typeof wb.Sheets === 'undefined'); }); it('bookProps should not generate sheets', function() { - var wb = XLSX.readFile('./test_files/number_format.xlsb', {bookProps:true}); + var wb = XLSX.readFile(dir+'number_format.xlsb', {bookProps:true}); assert(typeof wb.Sheets === 'undefined'); }); it('bookProps && bookSheets should not generate sheets', function() { - var wb = XLSX.readFile('./test_files/LONumbers.xlsx', {bookProps:true, bookSheets:true}); + var wb = XLSX.readFile(dir+'LONumbers.xlsx', {bookProps:true, bookSheets:true}); assert(typeof wb.Sheets === 'undefined'); }); + it('should not generate deps by default', function() { + var wb = XLSX.readFile(dir+'formula_stress_test.xlsx', {bookDeps:true}); + assert(typeof wb.Deps !== 'undefined' && wb.Deps.length > 0); + }); + it('bookDeps should generate deps', function() { + var wb = XLSX.readFile(dir+'formula_stress_test.xlsx'); + assert(typeof wb.Deps === 'undefined' || !(wb.Deps.length>0)); + }); }); }); describe('input formats', function() { it('should read binary strings', function() { - XLSX.read(fs.readFileSync('./test_files/comments_stress_test.xlsb', 'binary'), {type: 'binary'}); - XLSX.read(fs.readFileSync('./test_files/comments_stress_test.xlsx', 'binary'), {type: 'binary'}); + XLSX.read(fs.readFileSync(dir+'comments_stress_test.xlsb', 'binary'), {type: 'binary'}); + XLSX.read(fs.readFileSync(dir+'comments_stress_test.xlsx', 'binary'), {type: 'binary'}); }); it('should read base64 strings', function() { - XLSX.read(fs.readFileSync('./test_files/comments_stress_test.xlsb', 'base64'), {type: 'base64'}); - XLSX.read(fs.readFileSync('./test_files/comments_stress_test.xlsx', 'base64'), {type: 'base64'}); + XLSX.read(fs.readFileSync(dir+'comments_stress_test.xlsb', 'base64'), {type: 'base64'}); + XLSX.read(fs.readFileSync(dir+'comments_stress_test.xlsx', 'base64'), {type: 'base64'}); }); }); -describe('should have core properties and custom properties parsed', function() { - var wb; - before(function() { - XLSX = require('./'); - wb = XLSX.readFile('./test_files/custom_properties.xlsx'); +describe('features', function() { + describe('should have comment as part of cell properties', function(){ + var ws; + before(function() { + XLSX = require('./'); + var wb = XLSX.readFile(dir+'apachepoi_SimpleWithComments.xlsx'); + var sheetName = 'Sheet1'; + ws = wb.Sheets[sheetName]; + }); + it('Parse comments.xml and insert into cell',function(){ + assert.equal(ws.B1.c.length, 1,"must have 1 comment"); + assert.equal(ws.B1.c[0].t, "Yegor Kozlov:\r\nfirst cell", "must have the concatenated texts"); + assert.equal(ws.B1.c[0].h, 'Yegor Kozlov:
first cell
', "must have the html representation"); + assert.equal(ws.B1.c[0].r, 'Yegor Kozlov:\r\nfirst cell', "must have the rich text representation"); + assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author"); + }); }); - it('Must have read the core properties', function() { - assert.equal(wb.Props.Company, 'Vector Inc'); - assert.equal(wb.Props.Creator, 'Pony Foo'); - }); - it('Must have read the custom properties', function() { - assert.equal(wb.Custprops['I am a boolean'], true); - assert.equal(wb.Custprops['Date completed'], '1967-03-09T16:30:00Z'); - assert.equal(wb.Custprops.Status, 2); - assert.equal(wb.Custprops.Counter, -3.14); - }); -}); -describe.skip('should parse a sheet with a d date cell', function() { - var wb, ws; - before(function() { - XLSX = require('./'); - wb = XLSX.readFile('./test_files/xlsx-stream-d-date-cell.xlsx'); - // wb = XLSX.readFile('./test_files/xlsx-stream-array.xlsx'); - var sheetName = 'Sheet1'; - ws = wb.Sheets[sheetName]; + describe('should have core properties and custom properties parsed', function() { + var wb; + before(function() { + XLSX = require('./'); + wb = XLSX.readFile(dir+'custom_properties.xlsx'); + }); + it('Must have read the core properties', function() { + assert.equal(wb.Props.Company, 'Vector Inc'); + assert.equal(wb.Props.Creator, 'Pony Foo'); + }); + it('Must have read the custom properties', function() { + assert.equal(wb.Custprops['I am a boolean'], true); + assert.equal(wb.Custprops['Date completed'], '1967-03-09T16:30:00Z'); + assert.equal(wb.Custprops.Status, 2); + assert.equal(wb.Custprops.Counter, -3.14); + }); }); - it('Must have read the date', function() { - var sheet = XLSX.utils.sheet_to_row_object_array(ws); - assert.equal(sheet[3]['てすと'], '2/14/14'); + + describe('should parse cells with date type', function() { + var wb, ws; + before(function() { + XLSX = require('./'); + wb = XLSX.readFile(dir+'xlsx-stream-d-date-cell.xlsx'); + var sheetName = 'Sheet1'; + ws = wb.Sheets[sheetName]; + }); + it('Must have read the date', function() { + var sheet = XLSX.utils.sheet_to_row_object_array(ws); + assert.equal(sheet[3]['てすと'], '2/14/14'); + }); }); }); diff --git a/tests/files b/tests/files index 255a962..09abbe1 160000 --- a/tests/files +++ b/tests/files @@ -1 +1 @@ -Subproject commit 255a962b8f9a0f07ac75281bded02fea93e00b16 +Subproject commit 09abbe1b28237722d93a8510f42c71094c1244fd diff --git a/xlsx.js b/xlsx.js index db44b8f..93808f9 100644 --- a/xlsx.js +++ b/xlsx.js @@ -424,7 +424,7 @@ SSF.load_table = function(tbl) { for(var i=0; i!=0x0188; ++i) if(tbl[i]) SSF.loa make_ssf(SSF); var XLSX = {}; (function(XLSX){ -XLSX.version = '0.5.6'; +XLSX.version = '0.5.7'; var current_codepage, current_cptable, cptable; if(typeof module !== "undefined" && typeof require !== 'undefined') { if(typeof cptable === 'undefined') cptable = require('codepage'); @@ -455,6 +455,12 @@ function getzipfile(zip, file) { throw new Error("Cannot find file " + file + " in zip"); } +function getzipdata(zip, file, safe) { + if(!safe) return getdata(getzipfile(zip, file)); + if(!file) return null; + try { return getzipdata(zip, file); } catch(e) { return null; } +} + var _fs, jszip; if(typeof JSZip !== 'undefined') jszip = JSZip; if (typeof exports !== 'undefined') { @@ -1239,7 +1245,8 @@ function parse_comments_xml(data, opts) { function parse_comments(zip, dirComments, sheets, sheetRels, opts) { for(var i = 0; i != dirComments.length; ++i) { var canonicalpath=dirComments[i]; - var comments=parse_comments_xml(getdata(getzipfile(zip, canonicalpath.replace(/^\//,''))), opts); + var comments=parse_comments_xml(getzipdata(zip, canonicalpath.replace(/^\//,''), true), opts); + if(!comments || !comments.length) return; // find the sheets targeted by these comments var sheetNames = Object.keys(sheets); for(var j = 0; j != sheetNames.length; ++j) { @@ -1329,6 +1336,7 @@ function parse_ws_xml(data, opts) { else p.t = (cell.t ? cell.t : "n"); // default is "n" in schema if(refguess.s.c > idx) refguess.s.c = idx; if(refguess.e.c < idx) refguess.e.c = idx; + /* 18.18.11 t ST_CellType */ switch(p.t) { case 'n': p.v = parseFloat(p.v); break; case 's': { @@ -1343,20 +1351,14 @@ function parse_ws_xml(data, opts) { is = is ? parse_si(is[1]) : {t:"",r:""}; p.t = 'str'; p.v = is.t; break; // inline string - case 'b': - switch(p.v) { - case '0': case 'FALSE': case "false": case false: p.v=false; break; - case '1': case 'TRUE': case "true": case true: p.v=true; break; - default: throw "Unrecognized boolean: " + p.v; - } break; - case 'd': + case 'b': if(typeof p.v !== 'boolean') p.v = parsexmlbool(p.v); break; + case 'd': /* TODO: date1904 logic */ var epoch = Date.parse(p.v); p.v = (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); p.t = 'n'; break; /* in case of error, stick value in .raw */ case 'e': p.raw = RBErr[p.v]; break; - default: throw "Unrecognized cell type: " + p.t; } /* formatting */ @@ -1368,7 +1370,7 @@ function parse_ws_xml(data, opts) { try { p.w = SSF.format(fmtid,p.v,_ssfopts); if(opts.cellNF) p.z = SSF._table[fmtid]; - } catch(e) { } + } catch(e) { if(opts.WTF) throw e; } s[cell.r] = p; }); }); @@ -1530,7 +1532,7 @@ var parse_ws_bin = function(data, opts) { if((cf = styles.CellXf[val[0].iStyleRef])) try { p.w = SSF.format(cf.ifmt,p.v,_ssfopts); if(opts.cellNF) p.z = SSF._table[cf.ifmt]; - } catch(e) { } + } catch(e) { if(opts.WTF) throw e; } s[encode_cell({c:val[0].c,r:row.r})] = p; break; // TODO @@ -2674,10 +2676,11 @@ function fixopts(opts) { ['sheetStubs', false], /* emit empty cells */ + ['bookDeps', false], /* parse calculation chains */ ['bookSheets', false], /* only try to get sheet names (no Sheets) */ ['bookProps', false], /* only try to get properties (no Sheets) */ - ['WTF', false] /* WTF mode (do not use) */ + ['WTF', false] /* WTF mode (throws errors) */ ]; defaults.forEach(function(d) { if(typeof opts[d[0]] === 'undefined') opts[d[0]] = d[1]; }); } @@ -2687,8 +2690,9 @@ function parseZip(zip, opts) { reset_cp(); var entries = Object.keys(zip.files); var keys = entries.filter(function(x){return x.substr(-1) != '/';}).sort(); - var dir = parseCT(getdata(getzipfile(zip, '[Content_Types].xml'))); + var dir = parseCT(getzipdata(zip, '[Content_Types].xml')); var xlsb = false; + var sheets; if(dir.workbooks.length === 0) { var binname = "xl/workbook.bin"; if(!getzipfile(zip,binname)) throw new Error("Could not find workbook entry"); @@ -2698,42 +2702,43 @@ function parseZip(zip, opts) { if(!opts.bookSheets && !opts.bookProps) { strs = {}; - if(dir.sst) strs=parse_sst(getdata(getzipfile(zip, dir.sst.replace(/^\//,''))), dir.sst, opts); + if(dir.sst) strs=parse_sst(getzipdata(zip, dir.sst.replace(/^\//,'')), dir.sst, opts); styles = {}; - if(dir.style) styles = parse_sty(getdata(getzipfile(zip, dir.style.replace(/^\//,''))),dir.style); + if(dir.style) styles = parse_sty(getzipdata(zip, dir.style.replace(/^\//,'')),dir.style); } - var wb = parse_wb(getdata(getzipfile(zip, dir.workbooks[0].replace(/^\//,''))), dir.workbooks[0], opts); + var wb = parse_wb(getzipdata(zip, dir.workbooks[0].replace(/^\//,'')), dir.workbooks[0], opts); var props = {}, propdata = ""; try { - propdata = dir.coreprops.length !== 0 ? getdata(getzipfile(zip, dir.coreprops[0].replace(/^\//,''))) : ""; - propdata += dir.extprops.length !== 0 ? getdata(getzipfile(zip, dir.extprops[0].replace(/^\//,''))) : ""; + propdata = dir.coreprops.length !== 0 ? getzipdata(zip, dir.coreprops[0].replace(/^\//,'')) : ""; + propdata += dir.extprops.length !== 0 ? getzipdata(zip, dir.extprops[0].replace(/^\//,'')) : ""; props = propdata !== "" ? parseProps(propdata) : {}; } catch(e) { } var custprops = {}; if(!opts.bookSheets || opts.bookProps) { - if (dir.custprops.length !== 0) try { - propdata = getdata(getzipfile(zip, dir.custprops[0].replace(/^\//,''))); - custprops = parseCustomProps(propdata); - } catch(e) {/*console.error(e);*/} + if (dir.custprops.length !== 0) { + propdata = getzipdata(zip, dir.custprops[0].replace(/^\//,''), true); + if(propdata) custprops = parseCustomProps(propdata); + } } var out = {}; if(opts.bookSheets || opts.bookProps) { - var sheets; if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames; else if(wb.Sheets) sheets = wb.Sheets.map(function(x){ return x.name; }); if(opts.bookProps) { out.Props = props; out.Custprops = custprops; } if(typeof sheets !== 'undefined') out.SheetNames = sheets; if(opts.bookSheets ? out.SheetNames : opts.bookProps) return out; } + sheets = {}; var deps = {}; - if(dir.calcchain) deps=parseDeps(getdata(getzipfile(zip, dir.calcchain.replace(/^\//,'')))); - var sheets = {}, i=0; + if(opts.bookDeps && dir.calcchain) deps=parseDeps(getzipdata(zip, dir.calcchain.replace(/^\//,''))); + + var i=0; var sheetRels = {}; var path, relsPath; if(!props.Worksheets) { @@ -2745,12 +2750,12 @@ function parseZip(zip, opts) { props.SheetNames[j] = wbsheets[j].name; } for(i = 0; i != props.Worksheets; ++i) { - try { /* TODO: remove these guards */ + try { path = 'xl/worksheets/sheet' + (i+1) + (xlsb?'.bin':'.xml'); relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels"); - sheets[props.SheetNames[i]]=parse_ws(getdata(getzipfile(zip, path)),path,opts); - sheetRels[props.SheetNames[i]]=parseRels(getdata(getzipfile(zip, relsPath)), path); - } catch(e) {} + sheets[props.SheetNames[i]]=parse_ws(getzipdata(zip, path),path,opts); + sheetRels[props.SheetNames[i]]=parseRels(getzipdata(zip, relsPath, true), path); + } catch(e) { if(opts.WTF) throw e; } } } else { for(i = 0; i != props.Worksheets; ++i) { @@ -2758,9 +2763,9 @@ function parseZip(zip, opts) { //var path = dir.sheets[i].replace(/^\//,''); path = 'xl/worksheets/sheet' + (i+1) + (xlsb?'.bin':'.xml'); relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels"); - sheets[props.SheetNames[i]]=parse_ws(getdata(getzipfile(zip, path)),path,opts); - sheetRels[props.SheetNames[i]]=parseRels(getdata(getzipfile(zip, relsPath)), path); - } catch(e) {/*console.error(e);*/} + sheets[props.SheetNames[i]]=parse_ws(getzipdata(zip, path),path,opts); + sheetRels[props.SheetNames[i]]=parseRels(getzipdata(zip, relsPath, true), path); + } catch(e) { if(opts.WTF) throw e; } } }