From 70c48a74b9efb61b46293ef35f6babe5022e94df Mon Sep 17 00:00:00 2001 From: SheetJS Date: Fri, 24 Mar 2017 21:36:40 -0400 Subject: [PATCH] API Improvements - `aoa_to_sheet` function (fixes #314 h/t @fonzy2013 @rvdwijngaard) - `writeFileAsync` function (fixes #396 h/t @barbalex) - `sheet_to_json` tests + docs + blankrows (fixes #602 h/t @EEaglehouse) - write number format scan now includes every index >= 50 - propagate SSF IE8 fixes (fixes #171 h/t @sheetjsdev) - update shim for extendscript (see #603 h/t @firas3d) - more flow type definitions --- README.md | 54 +++++++++++++++-- bits/10_ssf.js | 16 ++--- bits/20_jsutils.js | 12 ++++ bits/27_csfutils.js | 36 ++++++++++++ bits/47_styxml.js | 2 +- bits/62_fxls.js | 6 +- bits/75_xlml.js | 6 +- bits/76_xls.js | 2 +- bits/88_write.js | 18 +++++- bits/90_utils.js | 26 +++++---- bits/98_exports.js | 1 + docbits/10_install.md | 1 + docbits/40_interface.md | 7 +++ docbits/62_columns.md | 5 +- docbits/80_parseopts.md | 4 +- docbits/82_util.md | 36 ++++++++++++ jszip.js | 6 +- misc/flow.js | 3 + shim.js | 6 +- test.js | 107 ++++++++++++++++++++++------------ xlsx.flow.js | 125 +++++++++++++++++++++++++++++++--------- xlsx.js | 122 ++++++++++++++++++++++++++++++--------- 22 files changed, 464 insertions(+), 137 deletions(-) create mode 100644 bits/27_csfutils.js diff --git a/README.md b/README.md index 04ae9ea..2725ee9 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ with a unified JS representation, and ES3/ES5 browser compatibility back to IE6. * [Supported Output Formats](#supported-output-formats) * [Output Type](#output-type) - [Utility Functions](#utility-functions) + * [Array of Arrays Input](#array-of-arrays-input) * [Formulae Output](#formulae-output) * [CSV and general DSV Output](#csv-and-general-dsv-output) * [JSON](#json) @@ -98,6 +99,7 @@ CDNjs automatically pulls the latest version and makes all versions available at The `demos` directory includes sample projects for: +- [`angular`](demos/angular/) - [`browserify`](demos/browserify/) - [`requirejs`](demos/requirejs/) - [`systemjs`](demos/systemjs/) @@ -377,12 +379,19 @@ Parse options are described in the [Parsing Options](#parsing-options) section. `XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename` +`XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`. +If `o` is omitted, the writer will use the third argument as the callback. + Write options are described in the [Writing Options](#writing-options) section. ### Utilities Utilities are available in the `XLSX.utils` object: +**Importing:** + +- `aoa_to_sheet` converts an array of arrays of JS data to a worksheet. + **Exporting:** - `sheet_to_json` converts a worksheet object to an array of JSON objects. @@ -655,7 +664,7 @@ objects which have the following properties: ```typescript type ColInfo = { MDW?:number; // Excel's "Max Digit Width" unit, always integral - width:number; // width in Excel's "Max Digit Width", width*256 is integral + width:number; // width in Excel's "Max Digit Width", width*256 is integral wpx?:number; // width in screen pixels wch?:number; // intermediate character calculation }; @@ -666,7 +675,8 @@ follow the priority order: 1) use `width` field if available 2) use `wpx` pixel width if available -2) use `wch` character count if available +3) use `wch` character count if available + ## Parsing Options The exported `read` and `readFile` functions accept an options argument: @@ -674,7 +684,7 @@ The exported `read` and `readFile` functions accept an options argument: | Option Name | Default | Description | | :---------- | ------: | :--------------------------------------------------- | | type | | Input data encoding (see Input Type below) | -| cellFormula | true | Save formulae to the .f field ** | +| cellFormula | true | Save formulae to the .f field | | cellHTML | true | Parse rich text and save HTML to the .h field | | cellNF | false | Save number format string to the .z field | | cellStyles | false | Save style/theme info to the .s field | @@ -689,8 +699,6 @@ The exported `read` and `readFile` functions accept an options argument: | password | "" | If defined and file is encrypted, use password ** | | WTF | false | If true, throw errors on unexpected file features ** | -- `cellFormula` option only applies to formats that require extra processing to - parse formulae (XLS/XLSB). - Even if `cellNF` is false, formatted text will be generated and saved to `.w` - In some cases, sheets may be parsed even if `bookSheets` is false. - `bookSheets` and `bookProps` combine to give both sets of information @@ -794,6 +802,8 @@ The `type` argument for `write` mirrors the `type` argument for `read`: The `sheet_to_*` functions accept a worksheet and an optional options object. +The `*_to_sheet` functions accept a data object and an optional options object. + The examples are based on the following worksheet: ``` @@ -804,6 +814,30 @@ XXX| A | B | C | D | E | F | G | 3 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ``` +### Array of Arrays Input + +`XLSX.utils.aoa_to_sheet` takes an array of arrays of JS values and returns a +worksheet resembling the input data. Numbers, Booleans and Strings are stored +as the corresponding styles. Dates are stored as date or numbers. Array holes +and explicit `undefined` values are skipped. `null` values may be stubbed. All +other values are stored as strings. The function takes an options argument: + +| Option Name | Default | Description | +| :---------- | :------: | :-------------------------------------------------- | +| dateNF | fmt 14 | Use specified date format in string output | +| cellDates | false | Store dates as type `d` (default is `n`) | +| sheetStubs | false | Create cell objects of type `z` for `null` values | + +To generate the example sheet: + +```js +var ws = XLSX.utils.aoa_to_sheet([ + "SheetJS".split(""), + [1,2,3,4,5,6,7], + [2,3,4,5,6,7,8] +]); +``` + ### Formulae Output `XLSX.utils.sheet_to_formulae` generates an array of commands that represent @@ -828,8 +862,10 @@ produces CSV output. The function takes an options argument: | RS | `"\n"` | "Record Separator" delimiter between rows | | dateNF | fmt 14 | Use specified date format in string output | | strip | false | Remove trailing field separators in each record ** | +| blankrows | true | Include blank lines in the CSV output | - `strip` will remove trailing commas from each line under default `FS/RS` +- blankrows must be set to `false` to skip blank lines. For the example sheet: @@ -858,6 +894,7 @@ generate different types of JS objects. The function takes an options argument: | header | | Control output format (see table below) | | dateNF | fmt 14 | Use specified date format in string output | | defval | | Use specified value in place of null or undefined | +| blankrows | ** | Include blank lines in the output ** | - `raw` only affects cells which have a format code (`.z`) field or a formatted text (`.w`) field. @@ -869,6 +906,10 @@ generate different types of JS objects. The function takes an options argument: - `null` values are returned when `raw` is true but are skipped when false. - If `defval` is not specified, null and undefined values are skipped normally. If specified, all null and undefined points will be filled with `defval` +- When `header` is `1`, the default is to generate blank rows. `blankrows` must + be set to `false` to skip blank rows. +- When `header` is not `1`, the default is to skip blank rows. `blankrows` must + be truthy to generate blank rows `range` is expected to be one of: @@ -887,6 +928,9 @@ generate different types of JS objects. The function takes an options argument: | array of strings | Use specified strings as keys in row objects | | (default) | Read and disambiguate first row as keys | +If header is not `1`, the row object will contain the non-enumerable property +`__rowNum__` that represents the row of the sheet corresponding to the entry. + For the example sheet: ```js diff --git a/bits/10_ssf.js b/bits/10_ssf.js index 2a2f67d..a2a72d0 100644 --- a/bits/10_ssf.js +++ b/bits/10_ssf.js @@ -332,7 +332,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm(type, fmt, val); if(fmt.indexOf('%') !== -1) return write_num_pct(type, fmt, val); if(fmt.indexOf('E') !== -1) return write_num_exp(fmt, val); - if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt[1]==' '?2:1),val); + if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt.charAt(1)==' '?2:1),val); var o; var r/*:?Array*/, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : ""; if(fmt.match(/^00+$/)) return sign + pad0r(aval,fmt.length); @@ -358,7 +358,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) { o = _strrev(write_num_flt(type, fmt.replace(/[\\-]/g,""), val)); ri = 0; - return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri-1&&i===decpt?out[i].v.indexOf(".")-1:out[i].v.length-1; vv = out[i].v.substr(j+1); for(; j>=0; --j) { - if(jj>=0 && (out[i].v[j] === "0" || out[i].v[j] === "#")) vv = ostr[jj--] + vv; + if(jj>=0 && (out[i].v.charAt(j) === "0" || out[i].v.charAt(j) === "#")) vv = ostr.charAt(jj--) + vv; } out[i].v = vv; out[i].t = 't'; @@ -748,7 +748,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")+1:0; vv = out[i].v.substr(0,j); for(; j R) range.s.r = R; + if(range.s.c > C) range.s.c = C; + if(range.e.r < R) range.e.r = R; + if(range.e.c < C) range.e.c = C; + var cell_ref = encode_cell(({c:C,r:R}/*:any*/)); + if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; } + else if(typeof cell.v === 'number') cell.t = 'n'; + else if(typeof cell.v === 'boolean') cell.t = 'b'; + else if(cell.v instanceof Date) { + cell.z = o.dateNF || SSF._table[14]; + if(o.cellDates) cell.t = 'd'; + else { cell.t = 'n'; cell.v = datenum(cell.v); } + cell.w = SSF.format(cell.z, cell.v); + } + else cell.t = 's'; + ws[cell_ref] = cell; + } + } + if(range.s.c < 10000000) ws['!ref'] = encode_range(range); + return ws; +} + diff --git a/bits/47_styxml.js b/bits/47_styxml.js index fa4434f..d161995 100644 --- a/bits/47_styxml.js +++ b/bits/47_styxml.js @@ -222,7 +222,7 @@ function parse_numFmts(t, styles, opts) { function write_numFmts(NF/*:{[n:number]:string}*/, opts) { var o = [""]; - [[5,8],[23,26],[41,44],[63,66],[164,392]].forEach(function(r) { + [[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) { for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) o[o.length] = (writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])})); }); if(o.length === 1) return ""; diff --git a/bits/62_fxls.js b/bits/62_fxls.js index 3618723..0b1d9dd 100644 --- a/bits/62_fxls.js +++ b/bits/62_fxls.js @@ -806,18 +806,18 @@ function stringify_formula(formula/*Array*/, range, cell/*:any*/, supbooks, /* 2.5.198.31 TODO */ case 'PtgAreaN': type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); - stack.push(encode_range_xls(r, opts)); + stack.push(encode_range_xls((r/*:any*/), opts)); break; /* 2.5.198.27 TODO: fixed points */ case 'PtgArea': type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); - stack.push(encode_range_xls(r, opts)); + stack.push(encode_range_xls((r/*:any*/), opts)); break; /* 2.5.198.28 */ case 'PtgArea3d': // TODO: lots of stuff type = f[1][0]; ixti = /*::Number(*/f[1][1]/*::)*/; r = f[1][2]; sname = (supbooks && supbooks[1] ? supbooks[1][ixti+1] : "**MISSING**"); - stack.push(sname + "!" + encode_range(r)); + stack.push(sname + "!" + encode_range((r/*:any*/))); break; /* 2.5.198.41 */ case 'PtgAttrSum': diff --git a/bits/75_xlml.js b/bits/75_xlml.js index b5771cc..63c3ce5 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -167,6 +167,7 @@ function xlml_normalize(d)/*:string*/ { var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; //var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg; function parse_xlml_xml(d, opts)/*:Workbook*/ { + make_ssf(SSF); var str = debom(xlml_normalize(d)); if(opts && opts.type == 'binary' && typeof cptable !== 'undefined') str = cptable.utils.decode(65001, char_codes(str)); if(str.substr(0,1000).indexOf("= 0) return parse_html(str, opts); @@ -277,7 +278,10 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { break; case 'NumberFormat': - stag.nf = xlml_parsexmltag(Rn[0]).Format || "General"; + stag.nf = unescapexml(xlml_parsexmltag(Rn[0]).Format || "General"); + if(XLMLFormatMap[stag.nf]) stag.nf = XLMLFormatMap[stag.nf]; + for(var ssfidx = 0; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == stag.nf) break; + if(ssfidx == 0x188) for(ssfidx = 0x39; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == null) { SSF.load(stag.nf, ssfidx); break; } break; case 'Column': diff --git a/bits/76_xls.js b/bits/76_xls.js index bd4d2f0..e58ca8c 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -85,7 +85,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { var out = {}; var Directory = {}; var found_sheet = false; - var range = {}; + var range/*:Range*/ = ({}/*:any*/); var last_formula = null; var sst = []; var cur_sheet = ""; diff --git a/bits/88_write.js b/bits/88_write.js index 7c00861..bc14916 100644 --- a/bits/88_write.js +++ b/bits/88_write.js @@ -59,9 +59,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) { } } -function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) { - var o = opts||{}; o.type = 'file'; - o.file = filename; +function resolve_book_type(o/*?WriteFileOpts*/) { if(!o.bookType) switch(o.file.slice(-5).toLowerCase()) { case '.xlsx': o.bookType = 'xlsx'; break; case '.xlsm': o.bookType = 'xlsm'; break; @@ -74,6 +72,20 @@ function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOp case '.ods': o.bookType = 'ods'; break; case '.csv': o.bookType = 'csv'; break; }} +} + +function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) { + var o = opts||{}; o.type = 'file'; + o.file = filename; + resolve_book_type(o); return writeSync(wb, o); } +function writeFileAsync(filename/*:string*/, wb/*:Workbook*/, opts/*:?WriteFileOpts*/, cb/*:?(e?:ErrnoError)=>void*/) { + var o = opts||{}; o.type = 'file'; + o.file = filename; + resolve_book_type(o); + o.type = 'buffer'; + var _cb = cb; if(!(_cb instanceof Function)) _cb = (opts/*:any*/); + return _fs.writeFile(filename, writeSync(wb, o), _cb); +} diff --git a/bits/90_utils.js b/bits/90_utils.js index 91a27db..0e88e9d 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -14,7 +14,8 @@ function encode_cell(cell/*:CellAddress*/)/*:string*/ { return encode_col(cell.c function fix_cell(cstr/*:string*/)/*:string*/ { return fix_col(fix_row(cstr)); } function unfix_cell(cstr/*:string*/)/*:string*/ { return unfix_col(unfix_row(cstr)); } function decode_range(range/*:string*/)/*:Range*/ { var x =range.split(":").map(decode_cell); return {s:x[0],e:x[x.length-1]}; } -function encode_range(cs/*:any*/,ce/*:?any*/)/*:string*/ { +/*# if only one arg, it is assumed to be a Range. If 2 args, both are cell addresses */ +function encode_range(cs/*:CellAddrSpec|Range*/,ce/*:?CellAddrSpec*/)/*:string*/ { if(typeof ce === 'undefined' || typeof ce === 'number') { /*:: if(!(cs instanceof Range)) throw "unreachable"; */ return encode_range(cs.s, cs.e); @@ -69,8 +70,8 @@ function format_cell(cell/*:Cell*/, v/*:any*/, o/*:any*/) { if(cell == null || cell.t == null || cell.t == 'z') return ""; if(cell.w !== undefined) return cell.w; if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF; - if(v == undefined) return safe_format_cell(cell, cell.v); - return safe_format_cell(cell, v); + if(v == undefined) return safe_format_cell(cell, cell.v, o); + return safe_format_cell(cell, v, o); } function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ @@ -102,7 +103,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ case 3: hdr[C] = o.header[C - r.s.c]; break; default: if(val == null) continue; - vv = v = format_cell(val); + vv = v = format_cell(val, null, o); var counter = 0; for(var CC = 0; CC < hdr.length; ++CC) if(hdr[CC] == vv) vv = v + "_" + (++counter); hdr[C] = vv; @@ -138,19 +139,17 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ else if(raw && v === null) row[hdr[C]] = null; else continue; } else { - row[hdr[C]] = raw ? v : format_cell(val,v); + row[hdr[C]] = raw ? v : format_cell(val,v,o); } isempty = false; } } - if(isempty === false || header === 1) out[outi++] = row; + if((isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row; } out.length = outi; return out; } -function sheet_to_row_object_array(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/) { return sheet_to_json(sheet, opts != null ? opts : {}); } - function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) { var out = "", txt = "", qreg = /"/g; var o = opts == null ? {} : opts; @@ -158,27 +157,31 @@ function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) { var r = safe_decode_range(sheet["!ref"]); var FS = o.FS !== undefined ? o.FS : ",", fs = FS.charCodeAt(0); var RS = o.RS !== undefined ? o.RS : "\n", rs = RS.charCodeAt(0); - var endregex = new RegExp(FS+"+$"); + var endregex = new RegExp((FS=="|" ? "\\|" : FS)+"+$"); var row = "", rr = "", cols = []; var i = 0, cc = 0, val; var R = 0, C = 0; for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C); for(R = r.s.r; R <= r.e.r; ++R) { + var isempty = true; row = ""; rr = encode_row(R); for(C = r.s.c; C <= r.e.c; ++C) { val = sheet[cols[C] + rr]; if(val == null) txt = ""; else if(val.v != null) { - txt = ''+format_cell(val); + isempty = false; + txt = ''+format_cell(val, null, o); for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) { txt = "\"" + txt.replace(qreg, '""') + "\""; break; } } else if(val.f != null && !val.F) { + isempty = false; txt = '=' + val.f; if(txt.indexOf(",") >= 0) txt = '"' + txt.replace(qreg, '""') + '"'; } else txt = ""; /* NOTE: Excel CSV does not support array formulae */ row += (C === r.s.c ? "" : FS) + txt; } + if(o.blankrows === false && isempty) continue; if(o.strip) row = row.replace(endregex,""); out += row + RS; } @@ -236,8 +239,9 @@ var utils = { make_csv: sheet_to_csv, make_json: sheet_to_json, make_formulae: sheet_to_formulae, + aoa_to_sheet: aoa_to_sheet, sheet_to_csv: sheet_to_csv, sheet_to_json: sheet_to_json, sheet_to_formulae: sheet_to_formulae, - sheet_to_row_object_array: sheet_to_row_object_array + sheet_to_row_object_array: sheet_to_json }; diff --git a/bits/98_exports.js b/bits/98_exports.js index 8e9fd8a..7f5d4d3 100644 --- a/bits/98_exports.js +++ b/bits/98_exports.js @@ -9,6 +9,7 @@ XLSX.readFileSync = readFileSync; XLSX.write = writeSync; XLSX.writeFile = writeFileSync; XLSX.writeFileSync = writeFileSync; +XLSX.writeFileAsync = writeFileAsync; XLSX.utils = utils; XLSX.CFB = CFB; XLSX.SSF = SSF; diff --git a/docbits/10_install.md b/docbits/10_install.md index 825189b..91181d1 100644 --- a/docbits/10_install.md +++ b/docbits/10_install.md @@ -25,6 +25,7 @@ CDNjs automatically pulls the latest version and makes all versions available at The `demos` directory includes sample projects for: +- [`angular`](demos/angular/) - [`browserify`](demos/browserify/) - [`requirejs`](demos/requirejs/) - [`systemjs`](demos/systemjs/) diff --git a/docbits/40_interface.md b/docbits/40_interface.md index 44ca6c3..073ac83 100644 --- a/docbits/40_interface.md +++ b/docbits/40_interface.md @@ -20,12 +20,19 @@ Parse options are described in the [Parsing Options](#parsing-options) section. `XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename` +`XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`. +If `o` is omitted, the writer will use the third argument as the callback. + Write options are described in the [Writing Options](#writing-options) section. ### Utilities Utilities are available in the `XLSX.utils` object: +**Importing:** + +- `aoa_to_sheet` converts an array of arrays of JS data to a worksheet. + **Exporting:** - `sheet_to_json` converts a worksheet object to an array of JSON objects. diff --git a/docbits/62_columns.md b/docbits/62_columns.md index 4613e10..adce380 100644 --- a/docbits/62_columns.md +++ b/docbits/62_columns.md @@ -17,7 +17,7 @@ objects which have the following properties: ```typescript type ColInfo = { MDW?:number; // Excel's "Max Digit Width" unit, always integral - width:number; // width in Excel's "Max Digit Width", width*256 is integral + width:number; // width in Excel's "Max Digit Width", width*256 is integral wpx?:number; // width in screen pixels wch?:number; // intermediate character calculation }; @@ -28,4 +28,5 @@ follow the priority order: 1) use `width` field if available 2) use `wpx` pixel width if available -2) use `wch` character count if available +3) use `wch` character count if available + diff --git a/docbits/80_parseopts.md b/docbits/80_parseopts.md index 439cb9d..4191145 100644 --- a/docbits/80_parseopts.md +++ b/docbits/80_parseopts.md @@ -5,7 +5,7 @@ The exported `read` and `readFile` functions accept an options argument: | Option Name | Default | Description | | :---------- | ------: | :--------------------------------------------------- | | type | | Input data encoding (see Input Type below) | -| cellFormula | true | Save formulae to the .f field ** | +| cellFormula | true | Save formulae to the .f field | | cellHTML | true | Parse rich text and save HTML to the .h field | | cellNF | false | Save number format string to the .z field | | cellStyles | false | Save style/theme info to the .s field | @@ -20,8 +20,6 @@ The exported `read` and `readFile` functions accept an options argument: | password | "" | If defined and file is encrypted, use password ** | | WTF | false | If true, throw errors on unexpected file features ** | -- `cellFormula` option only applies to formats that require extra processing to - parse formulae (XLS/XLSB). - Even if `cellNF` is false, formatted text will be generated and saved to `.w` - In some cases, sheets may be parsed even if `bookSheets` is false. - `bookSheets` and `bookProps` combine to give both sets of information diff --git a/docbits/82_util.md b/docbits/82_util.md index f354114..2dd456c 100644 --- a/docbits/82_util.md +++ b/docbits/82_util.md @@ -2,6 +2,8 @@ The `sheet_to_*` functions accept a worksheet and an optional options object. +The `*_to_sheet` functions accept a data object and an optional options object. + The examples are based on the following worksheet: ``` @@ -12,6 +14,30 @@ XXX| A | B | C | D | E | F | G | 3 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ``` +### Array of Arrays Input + +`XLSX.utils.aoa_to_sheet` takes an array of arrays of JS values and returns a +worksheet resembling the input data. Numbers, Booleans and Strings are stored +as the corresponding styles. Dates are stored as date or numbers. Array holes +and explicit `undefined` values are skipped. `null` values may be stubbed. All +other values are stored as strings. The function takes an options argument: + +| Option Name | Default | Description | +| :---------- | :------: | :-------------------------------------------------- | +| dateNF | fmt 14 | Use specified date format in string output | +| cellDates | false | Store dates as type `d` (default is `n`) | +| sheetStubs | false | Create cell objects of type `z` for `null` values | + +To generate the example sheet: + +```js +var ws = XLSX.utils.aoa_to_sheet([ + "SheetJS".split(""), + [1,2,3,4,5,6,7], + [2,3,4,5,6,7,8] +]); +``` + ### Formulae Output `XLSX.utils.sheet_to_formulae` generates an array of commands that represent @@ -36,8 +62,10 @@ produces CSV output. The function takes an options argument: | RS | `"\n"` | "Record Separator" delimiter between rows | | dateNF | fmt 14 | Use specified date format in string output | | strip | false | Remove trailing field separators in each record ** | +| blankrows | true | Include blank lines in the CSV output | - `strip` will remove trailing commas from each line under default `FS/RS` +- blankrows must be set to `false` to skip blank lines. For the example sheet: @@ -66,6 +94,7 @@ generate different types of JS objects. The function takes an options argument: | header | | Control output format (see table below) | | dateNF | fmt 14 | Use specified date format in string output | | defval | | Use specified value in place of null or undefined | +| blankrows | ** | Include blank lines in the output ** | - `raw` only affects cells which have a format code (`.z`) field or a formatted text (`.w`) field. @@ -77,6 +106,10 @@ generate different types of JS objects. The function takes an options argument: - `null` values are returned when `raw` is true but are skipped when false. - If `defval` is not specified, null and undefined values are skipped normally. If specified, all null and undefined points will be filled with `defval` +- When `header` is `1`, the default is to generate blank rows. `blankrows` must + be set to `false` to skip blank rows. +- When `header` is not `1`, the default is to skip blank rows. `blankrows` must + be truthy to generate blank rows `range` is expected to be one of: @@ -95,6 +128,9 @@ generate different types of JS objects. The function takes an options argument: | array of strings | Use specified strings as keys in row objects | | (default) | Read and disambiguate first row as keys | +If header is not `1`, the row object will contain the non-enumerable property +`__rowNum__` that represents the row of the sheet corresponding to the entry. + For the example sheet: ```js diff --git a/jszip.js b/jszip.js index 7155082..9a32d7d 100644 --- a/jszip.js +++ b/jszip.js @@ -9,7 +9,7 @@ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/js JSZip uses the library pako released under the MIT license : https://github.com/nodeca/pako/blob/master/LICENSE */ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd){JSZip=e();define([],e);}else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.JSZip=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o >; */ diff --git a/shim.js b/shim.js index 730708d..2a2d626 100644 --- a/shim.js +++ b/shim.js @@ -242,7 +242,7 @@ if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) { ;(function () { - var object = typeof exports != 'undefined' ? exports : self; // #8: web workers + var object = typeof exports != 'undefined' ? exports : typeof self != 'undefined' ? self : (1,eval)("this"); var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; function InvalidCharacterError(message) { @@ -279,7 +279,7 @@ if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) { // [https://gist.github.com/1020396] by [https://github.com/atk] object.atob || ( object.atob = function (input) { - var str = String(input).replace(/=+$/, ''); + var str = String(input).replace(new RegExp("=+$"), ''); if (str.length % 4 == 1) { throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); } @@ -299,7 +299,7 @@ if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) { } return output; }); - + if(typeof app != 'undefined') { app.btoa = object.btoa; app.atob = object.atob; } }()); diff --git a/test.js b/test.js index 4ffbbb1..1c2f757 100644 --- a/test.js +++ b/test.js @@ -1041,33 +1041,6 @@ function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ { if(date1904) epoch += 1462*24*60*60*1000; return (epoch + 2209161600000) / (24 * 60 * 60 * 1000); } -function sheet_from_array_of_arrays(data, opts) { - var ws = {}; - var range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }}; - for(var R = 0; R != data.length; ++R) { - for(var C = 0; C != data[R].length; ++C) { - if(range.s.r > R) range.s.r = R; - if(range.s.c > C) range.s.c = C; - if(range.e.r < R) range.e.r = R; - if(range.e.c < C) range.e.c = C; - var cell = {v: data[R][C] }; - if(cell.v === undefined) continue; - var cell_ref = X.utils.encode_cell({c:C,r:R}); - if(cell.v === null) cell.t = 'z'; - else if(typeof cell.v === 'number') cell.t = 'n'; - else if(typeof cell.v === 'boolean') cell.t = 'b'; - else if(cell.v instanceof Date) { - cell.z = X.SSF._table[14]; - if(opts && opts.cellDates) cell.t = 'd'; - else { cell.t = 'n'; cell.v = datenum(cell.v); } - } - else cell.t = 's'; - ws[cell_ref] = cell; - } - } - if(range.s.c < 10000000) ws['!ref'] = X.utils.encode_range(range); - return ws; -} describe('json output', function() { function seeker(json, keys, val) { @@ -1085,7 +1058,7 @@ describe('json output', function() { ["foo", "bar", new Date("2014-02-19T14:30Z"), "0.3"], ["baz", undefined, "qux"] ]; - ws = sheet_from_array_of_arrays(data); + ws = X.utils.aoa_to_sheet(data); }); if(typeof before != 'undefined') before(bef); else it('before', bef); @@ -1142,8 +1115,6 @@ describe('json output', function() { }); it('should use defval if requested', function() { var json = X.utils.sheet_to_json(ws, {defval: 'jimjin'}); - console.log(json); - console.log(ws); assert.equal(json.length, data.length - 1); assert.equal(json[0][1], "TRUE"); assert.equal(json[1][2], "bar"); @@ -1153,13 +1124,11 @@ describe('json output', function() { assert.doesNotThrow(function() { seeker(json, [1,2,3], "sheetjs"); }); assert.throws(function() { seeker(json, [1,2,3], "baz"); }); var json = X.utils.sheet_to_json(ws, {raw:true}); - console.log(json); var json = X.utils.sheet_to_json(ws, {raw:true, defval: 'jimjin'}); - console.log(json); }); it('should disambiguate headers', function() { var _data = [["S","h","e","e","t","J","S"],[1,2,3,4,5,6,7],[2,3,4,5,6,7,8]]; - var _ws = sheet_from_array_of_arrays(_data); + var _ws = X.utils.aoa_to_sheet(_data); var json = X.utils.sheet_to_json(_ws); for(var i = 0; i < json.length; ++i) { assert.equal(json[i].S, 1 + i); @@ -1172,7 +1141,7 @@ describe('json output', function() { } }); it('should handle raw data if requested', function() { - var _ws = sheet_from_array_of_arrays(data, {cellDates:true}); + var _ws = X.utils.aoa_to_sheet(data, {cellDates:true}); var json = X.utils.sheet_to_json(_ws, {header:1, raw:true}); assert.equal(json.length, data.length); assert.equal(json[1][0], true); @@ -1181,6 +1150,72 @@ describe('json output', function() { assert.equal(json[2][2].getTime(), new Date("2014-02-19T14:30Z").getTime()); assert.equal(json[3][2], "qux"); }); + it('should include __rowNum__', function() { + var _data = [["S","h","e","e","t","J","S"],[1,2,3,4,5,6,7],[],[2,3,4,5,6,7,8]]; + var _ws = X.utils.aoa_to_sheet(_data); + var json = X.utils.sheet_to_json(_ws); + assert.equal(json[0].__rowNum__, 1); + assert.equal(json[1].__rowNum__, 3); + }); + it('should handle blankrows', function() { + var _data = [["S","h","e","e","t","J","S"],[1,2,3,4,5,6,7],[],[2,3,4,5,6,7,8]]; + var _ws = X.utils.aoa_to_sheet(_data); + var json1 = X.utils.sheet_to_json(_ws); + var json2 = X.utils.sheet_to_json(_ws, {header:1}); + var json3 = X.utils.sheet_to_json(_ws, {blankrows:true}); + var json4 = X.utils.sheet_to_json(_ws, {blankrows:true, header:1}); + var json5 = X.utils.sheet_to_json(_ws, {blankrows:false}); + var json6 = X.utils.sheet_to_json(_ws, {blankrows:false, header:1}); + assert.equal(json1.length, 2); // = 2 non-empty records + assert.equal(json2.length, 4); // = 4 sheet rows + assert.equal(json3.length, 3); // = 2 records + 1 blank row + assert.equal(json4.length, 4); // = 4 sheet rows + assert.equal(json5.length, 2); // = 2 records + assert.equal(json6.length, 3); // = 4 sheet rows - 1 blank row + }); +}); + +describe('csv output', function() { + var data, ws; + var bef = (function() { + data = [ + [1,2,3,null], + [true, false, null, "sheetjs"], + ["foo", "bar", new Date("2014-02-19T14:30Z"), "0.3"], + [null, null, null], + ["baz", undefined, "qux"] + ]; + ws = X.utils.aoa_to_sheet(data); + }); + if(typeof before != 'undefined') before(bef); + else it('before', bef); + it('should generate csv', function() { + var baseline = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,2/19/14,0.3\n,,,\nbaz,,qux,\n"; + assert.equal(baseline, X.utils.sheet_to_csv(ws)); + }); + it('should handle FS', function() { + assert.equal(X.utils.sheet_to_csv(ws, {FS:"|"}).replace(/[|]/g,","), X.utils.sheet_to_csv(ws)); + assert.equal(X.utils.sheet_to_csv(ws, {FS:";"}).replace(/[;]/g,","), X.utils.sheet_to_csv(ws)); + }); + it('should handle RS', function() { + assert.equal(X.utils.sheet_to_csv(ws, {RS:"|"}).replace(/[|]/g,"\n"), X.utils.sheet_to_csv(ws)); + assert.equal(X.utils.sheet_to_csv(ws, {RS:";"}).replace(/[;]/g,"\n"), X.utils.sheet_to_csv(ws)); + }); + it('should handle dateNF', function() { + var baseline = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,20140219,0.3\n,,,\nbaz,,qux,\n"; + var _ws = X.utils.aoa_to_sheet(data, {cellDates:true}); + delete _ws.C3.w; + delete _ws.C3.z; + assert.equal(baseline, X.utils.sheet_to_csv(_ws, {dateNF:"YYYYMMDD"})); + }); + it('should handle strip', function() { + var baseline = "1,2,3\nTRUE,FALSE,,sheetjs\nfoo,bar,2/19/14,0.3\n\nbaz,,qux\n"; + assert.equal(baseline, X.utils.sheet_to_csv(ws, {strip:true})); + }); + it('should handle blankrows', function() { + var baseline = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,2/19/14,0.3\nbaz,,qux,\n"; + assert.equal(baseline, X.utils.sheet_to_csv(ws, {blankrows:false})); + }); }); describe('js -> file -> js', function() { @@ -1192,7 +1227,7 @@ describe('js -> file -> js', function() { ["foo","bar",new Date("2014-02-19T14:30Z"), "0.3"], ["baz", 6.9, "qux"] ]; - ws = sheet_from_array_of_arrays(data); + ws = X.utils.aoa_to_sheet(data); wb = { SheetNames: ['Sheet1'], Sheets: {Sheet1: ws} }; }); if(typeof before != 'undefined') before(bef); @@ -1234,7 +1269,7 @@ describe('corner cases', function() { ["foo","bar",new Date("2014-02-19T14:30Z"), "0.3"], ["baz", null, "q\"ux"] ]; - var ws = sheet_from_array_of_arrays(data); + var ws = X.utils.aoa_to_sheet(data); ws.A1.f = ""; ws.A1.w = ""; delete ws.C3.w; delete ws.C3.z; ws.C3.XF = {ifmt:14}; ws.A4.t = "e"; diff --git a/xlsx.flow.js b/xlsx.flow.js index 3b2dcc8..da1a13b 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -444,7 +444,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm(type, fmt, val); if(fmt.indexOf('%') !== -1) return write_num_pct(type, fmt, val); if(fmt.indexOf('E') !== -1) return write_num_exp(fmt, val); - if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt[1]==' '?2:1),val); + if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt.charAt(1)==' '?2:1),val); var o; var r/*:?Array*/, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : ""; if(fmt.match(/^00+$/)) return sign + pad0r(aval,fmt.length); @@ -470,7 +470,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) { o = _strrev(write_num_flt(type, fmt.replace(/[\\-]/g,""), val)); ri = 0; - return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri-1&&i===decpt?out[i].v.indexOf(".")-1:out[i].v.length-1; vv = out[i].v.substr(j+1); for(; j>=0; --j) { - if(jj>=0 && (out[i].v[j] === "0" || out[i].v[j] === "#")) vv = ostr[jj--] + vv; + if(jj>=0 && (out[i].v.charAt(j) === "0" || out[i].v.charAt(j) === "#")) vv = ostr.charAt(jj--) + vv; } out[i].v = vv; out[i].t = 't'; @@ -860,7 +860,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) { j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")+1:0; vv = out[i].v.substr(0,j); for(; j R) range.s.r = R; + if(range.s.c > C) range.s.c = C; + if(range.e.r < R) range.e.r = R; + if(range.e.c < C) range.e.c = C; + var cell_ref = encode_cell(({c:C,r:R}/*:any*/)); + if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; } + else if(typeof cell.v === 'number') cell.t = 'n'; + else if(typeof cell.v === 'boolean') cell.t = 'b'; + else if(cell.v instanceof Date) { + cell.z = o.dateNF || SSF._table[14]; + if(o.cellDates) cell.t = 'd'; + else { cell.t = 'n'; cell.v = datenum(cell.v); } + cell.w = SSF.format(cell.z, cell.v); + } + else cell.t = 's'; + ws[cell_ref] = cell; + } + } + if(range.s.c < 10000000) ws['!ref'] = encode_range(range); + return ws; +} + /* [MS-XLSB] 2.5.143 */ function parse_StrRun(data, length/*:?number*/) { @@ -5389,7 +5437,7 @@ function parse_numFmts(t, styles, opts) { function write_numFmts(NF/*:{[n:number]:string}*/, opts) { var o = [""]; - [[5,8],[23,26],[41,44],[63,66],[164,392]].forEach(function(r) { + [[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) { for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) o[o.length] = (writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])})); }); if(o.length === 1) return ""; @@ -7029,18 +7077,18 @@ function stringify_formula(formula/*Array*/, range, cell/*:any*/, supbooks, /* 2.5.198.31 TODO */ case 'PtgAreaN': type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); - stack.push(encode_range_xls(r, opts)); + stack.push(encode_range_xls((r/*:any*/), opts)); break; /* 2.5.198.27 TODO: fixed points */ case 'PtgArea': type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); - stack.push(encode_range_xls(r, opts)); + stack.push(encode_range_xls((r/*:any*/), opts)); break; /* 2.5.198.28 */ case 'PtgArea3d': // TODO: lots of stuff type = f[1][0]; ixti = /*::Number(*/f[1][1]/*::)*/; r = f[1][2]; sname = (supbooks && supbooks[1] ? supbooks[1][ixti+1] : "**MISSING**"); - stack.push(sname + "!" + encode_range(r)); + stack.push(sname + "!" + encode_range((r/*:any*/))); break; /* 2.5.198.41 */ case 'PtgAttrSum': @@ -10225,6 +10273,7 @@ function xlml_normalize(d)/*:string*/ { var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; //var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg; function parse_xlml_xml(d, opts)/*:Workbook*/ { + make_ssf(SSF); var str = debom(xlml_normalize(d)); if(opts && opts.type == 'binary' && typeof cptable !== 'undefined') str = cptable.utils.decode(65001, char_codes(str)); if(str.substr(0,1000).indexOf("= 0) return parse_html(str, opts); @@ -10335,7 +10384,10 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { break; case 'NumberFormat': - stag.nf = xlml_parsexmltag(Rn[0]).Format || "General"; + stag.nf = unescapexml(xlml_parsexmltag(Rn[0]).Format || "General"); + if(XLMLFormatMap[stag.nf]) stag.nf = XLMLFormatMap[stag.nf]; + for(var ssfidx = 0; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == stag.nf) break; + if(ssfidx == 0x188) for(ssfidx = 0x39; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == null) { SSF.load(stag.nf, ssfidx); break; } break; case 'Column': @@ -11012,7 +11064,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { var out = {}; var Directory = {}; var found_sheet = false; - var range = {}; + var range/*:Range*/ = ({}/*:any*/); var last_formula = null; var sst = []; var cur_sheet = ""; @@ -13977,9 +14029,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) { } } -function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) { - var o = opts||{}; o.type = 'file'; - o.file = filename; +function resolve_book_type(o/*?WriteFileOpts*/) { if(!o.bookType) switch(o.file.slice(-5).toLowerCase()) { case '.xlsx': o.bookType = 'xlsx'; break; case '.xlsm': o.bookType = 'xlsm'; break; @@ -13992,9 +14042,23 @@ function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOp case '.ods': o.bookType = 'ods'; break; case '.csv': o.bookType = 'csv'; break; }} +} + +function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) { + var o = opts||{}; o.type = 'file'; + o.file = filename; + resolve_book_type(o); return writeSync(wb, o); } +function writeFileAsync(filename/*:string*/, wb/*:Workbook*/, opts/*:?WriteFileOpts*/, cb/*:?(e?:ErrnoError)=>void*/) { + var o = opts||{}; o.type = 'file'; + o.file = filename; + resolve_book_type(o); + o.type = 'buffer'; + var _cb = cb; if(!(_cb instanceof Function)) _cb = (opts/*:any*/); + return _fs.writeFile(filename, writeSync(wb, o), _cb); +} function decode_row(rowstr/*:string*/)/*:number*/ { return parseInt(unfix_row(rowstr),10) - 1; } function encode_row(row/*:number*/)/*:string*/ { return "" + (row + 1); } function fix_row(cstr/*:string*/)/*:string*/ { return cstr.replace(/([A-Z]|^)(\d+)$/,"$1$$$2"); } @@ -14011,7 +14075,8 @@ function encode_cell(cell/*:CellAddress*/)/*:string*/ { return encode_col(cell.c function fix_cell(cstr/*:string*/)/*:string*/ { return fix_col(fix_row(cstr)); } function unfix_cell(cstr/*:string*/)/*:string*/ { return unfix_col(unfix_row(cstr)); } function decode_range(range/*:string*/)/*:Range*/ { var x =range.split(":").map(decode_cell); return {s:x[0],e:x[x.length-1]}; } -function encode_range(cs/*:any*/,ce/*:?any*/)/*:string*/ { +/*# if only one arg, it is assumed to be a Range. If 2 args, both are cell addresses */ +function encode_range(cs/*:CellAddrSpec|Range*/,ce/*:?CellAddrSpec*/)/*:string*/ { if(typeof ce === 'undefined' || typeof ce === 'number') { /*:: if(!(cs instanceof Range)) throw "unreachable"; */ return encode_range(cs.s, cs.e); @@ -14066,8 +14131,8 @@ function format_cell(cell/*:Cell*/, v/*:any*/, o/*:any*/) { if(cell == null || cell.t == null || cell.t == 'z') return ""; if(cell.w !== undefined) return cell.w; if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF; - if(v == undefined) return safe_format_cell(cell, cell.v); - return safe_format_cell(cell, v); + if(v == undefined) return safe_format_cell(cell, cell.v, o); + return safe_format_cell(cell, v, o); } function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ @@ -14099,7 +14164,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ case 3: hdr[C] = o.header[C - r.s.c]; break; default: if(val == null) continue; - vv = v = format_cell(val); + vv = v = format_cell(val, null, o); var counter = 0; for(var CC = 0; CC < hdr.length; ++CC) if(hdr[CC] == vv) vv = v + "_" + (++counter); hdr[C] = vv; @@ -14135,19 +14200,17 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){ else if(raw && v === null) row[hdr[C]] = null; else continue; } else { - row[hdr[C]] = raw ? v : format_cell(val,v); + row[hdr[C]] = raw ? v : format_cell(val,v,o); } isempty = false; } } - if(isempty === false || header === 1) out[outi++] = row; + if((isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row; } out.length = outi; return out; } -function sheet_to_row_object_array(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/) { return sheet_to_json(sheet, opts != null ? opts : {}); } - function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) { var out = "", txt = "", qreg = /"/g; var o = opts == null ? {} : opts; @@ -14155,27 +14218,31 @@ function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) { var r = safe_decode_range(sheet["!ref"]); var FS = o.FS !== undefined ? o.FS : ",", fs = FS.charCodeAt(0); var RS = o.RS !== undefined ? o.RS : "\n", rs = RS.charCodeAt(0); - var endregex = new RegExp(FS+"+$"); + var endregex = new RegExp((FS=="|" ? "\\|" : FS)+"+$"); var row = "", rr = "", cols = []; var i = 0, cc = 0, val; var R = 0, C = 0; for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C); for(R = r.s.r; R <= r.e.r; ++R) { + var isempty = true; row = ""; rr = encode_row(R); for(C = r.s.c; C <= r.e.c; ++C) { val = sheet[cols[C] + rr]; if(val == null) txt = ""; else if(val.v != null) { - txt = ''+format_cell(val); + isempty = false; + txt = ''+format_cell(val, null, o); for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) { txt = "\"" + txt.replace(qreg, '""') + "\""; break; } } else if(val.f != null && !val.F) { + isempty = false; txt = '=' + val.f; if(txt.indexOf(",") >= 0) txt = '"' + txt.replace(qreg, '""') + '"'; } else txt = ""; /* NOTE: Excel CSV does not support array formulae */ row += (C === r.s.c ? "" : FS) + txt; } + if(o.blankrows === false && isempty) continue; if(o.strip) row = row.replace(endregex,""); out += row + RS; } @@ -14233,10 +14300,11 @@ var utils = { make_csv: sheet_to_csv, make_json: sheet_to_json, make_formulae: sheet_to_formulae, + aoa_to_sheet: aoa_to_sheet, sheet_to_csv: sheet_to_csv, sheet_to_json: sheet_to_json, sheet_to_formulae: sheet_to_formulae, - sheet_to_row_object_array: sheet_to_row_object_array + sheet_to_row_object_array: sheet_to_json }; XLSX.parse_xlscfb = parse_xlscfb; XLSX.parse_ods = parse_ods; @@ -14249,6 +14317,7 @@ XLSX.readFileSync = readFileSync; XLSX.write = writeSync; XLSX.writeFile = writeFileSync; XLSX.writeFileSync = writeFileSync; +XLSX.writeFileAsync = writeFileAsync; XLSX.utils = utils; XLSX.CFB = CFB; XLSX.SSF = SSF; diff --git a/xlsx.js b/xlsx.js index 0b2d30b..4adcc7b 100644 --- a/xlsx.js +++ b/xlsx.js @@ -425,7 +425,7 @@ function write_num_flt(type, fmt, val) { if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm(type, fmt, val); if(fmt.indexOf('%') !== -1) return write_num_pct(type, fmt, val); if(fmt.indexOf('E') !== -1) return write_num_exp(fmt, val); - if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt[1]==' '?2:1),val); + if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt.charAt(1)==' '?2:1),val); var o; var r, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : ""; if(fmt.match(/^00+$/)) return sign + pad0r(aval,fmt.length); @@ -451,7 +451,7 @@ function write_num_flt(type, fmt, val) { if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) { o = _strrev(write_num_flt(type, fmt.replace(/[\\-]/g,""), val)); ri = 0; - return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri-1&&i===decpt?out[i].v.indexOf(".")-1:out[i].v.length-1; vv = out[i].v.substr(j+1); for(; j>=0; --j) { - if(jj>=0 && (out[i].v[j] === "0" || out[i].v[j] === "#")) vv = ostr[jj--] + vv; + if(jj>=0 && (out[i].v.charAt(j) === "0" || out[i].v.charAt(j) === "#")) vv = ostr.charAt(jj--) + vv; } out[i].v = vv; out[i].t = 't'; @@ -836,7 +836,7 @@ out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0); j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")+1:0; vv = out[i].v.substr(0,j); for(; j R) range.s.r = R; + if(range.s.c > C) range.s.c = C; + if(range.e.r < R) range.e.r = R; + if(range.e.c < C) range.e.c = C; + var cell_ref = encode_cell(({c:C,r:R})); + if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; } + else if(typeof cell.v === 'number') cell.t = 'n'; + else if(typeof cell.v === 'boolean') cell.t = 'b'; + else if(cell.v instanceof Date) { + cell.z = o.dateNF || SSF._table[14]; + if(o.cellDates) cell.t = 'd'; + else { cell.t = 'n'; cell.v = datenum(cell.v); } + cell.w = SSF.format(cell.z, cell.v); + } + else cell.t = 's'; + ws[cell_ref] = cell; + } + } + if(range.s.c < 10000000) ws['!ref'] = encode_range(range); + return ws; +} + /* [MS-XLSB] 2.5.143 */ function parse_StrRun(data, length) { @@ -5335,7 +5383,7 @@ function parse_numFmts(t, styles, opts) { function write_numFmts(NF, opts) { var o = [""]; - [[5,8],[23,26],[41,44],[63,66],[164,392]].forEach(function(r) { + [[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) { for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) o[o.length] = (writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])})); }); if(o.length === 1) return ""; @@ -6974,18 +7022,18 @@ function stringify_formula(formula/*Array*/, range, cell, supbooks, opts) { /* 2.5.198.31 TODO */ case 'PtgAreaN': type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); - stack.push(encode_range_xls(r, opts)); + stack.push(encode_range_xls((r), opts)); break; /* 2.5.198.27 TODO: fixed points */ case 'PtgArea': type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts); - stack.push(encode_range_xls(r, opts)); + stack.push(encode_range_xls((r), opts)); break; /* 2.5.198.28 */ case 'PtgArea3d': // TODO: lots of stuff type = f[1][0]; ixti = f[1][1]; r = f[1][2]; sname = (supbooks && supbooks[1] ? supbooks[1][ixti+1] : "**MISSING**"); - stack.push(sname + "!" + encode_range(r)); + stack.push(sname + "!" + encode_range((r))); break; /* 2.5.198.41 */ case 'PtgAttrSum': @@ -10168,6 +10216,7 @@ function xlml_normalize(d) { var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; //var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg; function parse_xlml_xml(d, opts) { + make_ssf(SSF); var str = debom(xlml_normalize(d)); if(opts && opts.type == 'binary' && typeof cptable !== 'undefined') str = cptable.utils.decode(65001, char_codes(str)); if(str.substr(0,1000).indexOf("= 0) return parse_html(str, opts); @@ -10277,7 +10326,10 @@ for(var cma = c; cma <= cc; ++cma) { break; case 'NumberFormat': - stag.nf = xlml_parsexmltag(Rn[0]).Format || "General"; + stag.nf = unescapexml(xlml_parsexmltag(Rn[0]).Format || "General"); + if(XLMLFormatMap[stag.nf]) stag.nf = XLMLFormatMap[stag.nf]; + for(var ssfidx = 0; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == stag.nf) break; + if(ssfidx == 0x188) for(ssfidx = 0x39; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == null) { SSF.load(stag.nf, ssfidx); break; } break; case 'Column': @@ -10953,7 +11005,7 @@ function parse_workbook(blob, options) { var out = {}; var Directory = {}; var found_sheet = false; - var range = {}; + var range = ({}); var last_formula = null; var sst = []; var cur_sheet = ""; @@ -13914,9 +13966,7 @@ function writeSync(wb, opts) { } } -function writeFileSync(wb, filename, opts) { - var o = opts||{}; o.type = 'file'; - o.file = filename; +function resolve_book_type(o/*?WriteFileOpts*/) { if(!o.bookType) switch(o.file.slice(-5).toLowerCase()) { case '.xlsx': o.bookType = 'xlsx'; break; case '.xlsm': o.bookType = 'xlsm'; break; @@ -13929,9 +13979,23 @@ function writeFileSync(wb, filename, opts) { case '.ods': o.bookType = 'ods'; break; case '.csv': o.bookType = 'csv'; break; }} +} + +function writeFileSync(wb, filename, opts) { + var o = opts||{}; o.type = 'file'; + o.file = filename; + resolve_book_type(o); return writeSync(wb, o); } +function writeFileAsync(filename, wb, opts, cb) { + var o = opts||{}; o.type = 'file'; + o.file = filename; + resolve_book_type(o); + o.type = 'buffer'; + var _cb = cb; if(!(_cb instanceof Function)) _cb = (opts); + return _fs.writeFile(filename, writeSync(wb, o), _cb); +} function decode_row(rowstr) { return parseInt(unfix_row(rowstr),10) - 1; } function encode_row(row) { return "" + (row + 1); } function fix_row(cstr) { return cstr.replace(/([A-Z]|^)(\d+)$/,"$1$$$2"); } @@ -13999,8 +14063,8 @@ function format_cell(cell, v, o) { if(cell == null || cell.t == null || cell.t == 'z') return ""; if(cell.w !== undefined) return cell.w; if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF; - if(v == undefined) return safe_format_cell(cell, cell.v); - return safe_format_cell(cell, v); + if(v == undefined) return safe_format_cell(cell, cell.v, o); + return safe_format_cell(cell, v, o); } function sheet_to_json(sheet, opts){ @@ -14032,7 +14096,7 @@ function sheet_to_json(sheet, opts){ case 3: hdr[C] = o.header[C - r.s.c]; break; default: if(val == null) continue; - vv = v = format_cell(val); + vv = v = format_cell(val, null, o); var counter = 0; for(var CC = 0; CC < hdr.length; ++CC) if(hdr[CC] == vv) vv = v + "_" + (++counter); hdr[C] = vv; @@ -14068,19 +14132,17 @@ function sheet_to_json(sheet, opts){ else if(raw && v === null) row[hdr[C]] = null; else continue; } else { - row[hdr[C]] = raw ? v : format_cell(val,v); + row[hdr[C]] = raw ? v : format_cell(val,v,o); } isempty = false; } } - if(isempty === false || header === 1) out[outi++] = row; + if((isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row; } out.length = outi; return out; } -function sheet_to_row_object_array(sheet, opts) { return sheet_to_json(sheet, opts != null ? opts : {}); } - function sheet_to_csv(sheet, opts) { var out = "", txt = "", qreg = /"/g; var o = opts == null ? {} : opts; @@ -14088,27 +14150,31 @@ function sheet_to_csv(sheet, opts) { var r = safe_decode_range(sheet["!ref"]); var FS = o.FS !== undefined ? o.FS : ",", fs = FS.charCodeAt(0); var RS = o.RS !== undefined ? o.RS : "\n", rs = RS.charCodeAt(0); - var endregex = new RegExp(FS+"+$"); + var endregex = new RegExp((FS=="|" ? "\\|" : FS)+"+$"); var row = "", rr = "", cols = []; var i = 0, cc = 0, val; var R = 0, C = 0; for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C); for(R = r.s.r; R <= r.e.r; ++R) { + var isempty = true; row = ""; rr = encode_row(R); for(C = r.s.c; C <= r.e.c; ++C) { val = sheet[cols[C] + rr]; if(val == null) txt = ""; else if(val.v != null) { - txt = ''+format_cell(val); + isempty = false; + txt = ''+format_cell(val, null, o); for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) { txt = "\"" + txt.replace(qreg, '""') + "\""; break; } } else if(val.f != null && !val.F) { + isempty = false; txt = '=' + val.f; if(txt.indexOf(",") >= 0) txt = '"' + txt.replace(qreg, '""') + '"'; } else txt = ""; /* NOTE: Excel CSV does not support array formulae */ row += (C === r.s.c ? "" : FS) + txt; } + if(o.blankrows === false && isempty) continue; if(o.strip) row = row.replace(endregex,""); out += row + RS; } @@ -14166,10 +14232,11 @@ var utils = { make_csv: sheet_to_csv, make_json: sheet_to_json, make_formulae: sheet_to_formulae, + aoa_to_sheet: aoa_to_sheet, sheet_to_csv: sheet_to_csv, sheet_to_json: sheet_to_json, sheet_to_formulae: sheet_to_formulae, - sheet_to_row_object_array: sheet_to_row_object_array + sheet_to_row_object_array: sheet_to_json }; XLSX.parse_xlscfb = parse_xlscfb; XLSX.parse_ods = parse_ods; @@ -14182,6 +14249,7 @@ XLSX.readFileSync = readFileSync; XLSX.write = writeSync; XLSX.writeFile = writeFileSync; XLSX.writeFileSync = writeFileSync; +XLSX.writeFileAsync = writeFileAsync; XLSX.utils = utils; XLSX.CFB = CFB; XLSX.SSF = SSF;