From 947a5178bd0b718d34eb539658cc9d72d863d6af Mon Sep 17 00:00:00 2001 From: SheetJS Date: Sat, 27 Apr 2024 19:49:12 -0400 Subject: [PATCH] ssf format NaN and Infinity values --- CONTRIBUTING.md | 15 +++++++-------- bits/10_ssf.js | 4 ++++ bits/27_csfutils.js | 8 +++++--- packages/ssf/bits/02_utilities.js | 2 +- packages/ssf/bits/25_table.js | 2 ++ packages/ssf/bits/60_number.js | 2 +- packages/ssf/bits/90_main.js | 4 ++++ packages/ssf/package.json | 2 +- packages/ssf/ssf.flow.js | 10 ++++++++-- packages/ssf/ssf.js | 8 +++++++- packages/ssf/test/oddities.js | 8 ++++++++ types/index.d.ts | 8 ++++---- 12 files changed, 52 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27c34c3..c79ca02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,8 +4,8 @@ The SheetJS Libraries should be free and clear to use in your projects. In order to maintain that, every contributor must be vigilant. There have been many projects in the past that have been very lax regarding -licensing, and we are of the opinion that those are ticking timebombs and that -no commercial product should depend on them. +licensing. We are of the opinion that those are ticking timebombs and that no +commercial product should depend on them. # Required Reading @@ -30,10 +30,9 @@ inbox is self-hosted. # Opening Pull Requests -Before opening a pull request, [squash all commits into -one](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History). If the pull -request addresses documentation or demos, add `[ci skip]` in the body or title -of your commit message to skip Travis checks. +[Squash commits](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) +before opening a pull request, If the pull request addresses documentation or +demos, add `[ci skip]` in the body or title of the commit message to skip tests. # Pre-Contribution Checklist @@ -57,8 +56,8 @@ issue. If it is a particularly high-priority issue, please drop an email to Keep these in mind as you work: - Your contributions are your original work. Take note of any resources you - consult in the process (and be extra careful not to use unlicensed code on - the internet. + consult in the process. Be extra careful not to use unlicensed code on the + Internet or code generated by a large language model or other AI tool. - You are working on your own time. Unless they explicitly grant permission, your employer may be the ultimate owner of your IP diff --git a/bits/10_ssf.js b/bits/10_ssf.js index 8b47f1c..f016a65 100644 --- a/bits/10_ssf.js +++ b/bits/10_ssf.js @@ -940,6 +940,8 @@ function choose_fmt(f/*:string*/, v/*:any*/) { if(l<4 && lat>-1) --l; if(fmt.length > 4) throw new Error("cannot find right format for |" + fmt.join("|") + "|"); if(typeof v !== "number") return [4, fmt.length === 4 || lat>-1?fmt[fmt.length-1]:"@"]; + /* NOTE: most spreadsheet software do not support NaN or infinities */ + if(typeof v === "number" && !isFinite(v)) v = 0; switch(fmt.length) { case 1: fmt = lat>-1 ? ["General", "General", "General", fmt[0]] : [fmt[0], fmt[0], fmt[0], "@"]; break; case 2: fmt = lat>-1 ? [fmt[0], fmt[0], fmt[0], fmt[1]] : [fmt[0], fmt[1], fmt[0], "@"]; break; @@ -976,6 +978,8 @@ function SSF_format(fmt/*:string|number*/,v/*:any*/,o/*:?any*/) { if(SSF_isgeneral(f[1])) return SSF_general(v, o); if(v === true) v = "TRUE"; else if(v === false) v = "FALSE"; else if(v === "" || v == null) return ""; + else if(isNaN(v) && f[1].indexOf("0") > -1) return "#VALUE!"; + else if(!isFinite(v) && f[1].indexOf("0") > -1) return "#DIV/0!"; return eval_fmt(f[1], v, o, f[0]); } function SSF_load(fmt/*:string*/, idx/*:?number*/)/*:number*/ { diff --git a/bits/27_csfutils.js b/bits/27_csfutils.js index 8f58af2..67841c5 100644 --- a/bits/27_csfutils.js +++ b/bits/27_csfutils.js @@ -145,7 +145,6 @@ function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksh if(!data[R]) continue; if(!Array.isArray(data[R])) throw new Error("aoa_to_sheet expects an array of arrays"); var __R = _R + R, __Rstr = "" + (__R + 1); - //console.log("!!", R, _R, __R); if(dense) { if(!ws["!data"][__R]) ws["!data"][__R] = []; row = ws["!data"][__R]; @@ -168,7 +167,11 @@ function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksh else if(!o.sheetStubs) continue; else cell.t = 'z'; } - else if(typeof cell.v === 'number') cell.t = 'n'; + else if(typeof cell.v === 'number') { + if(isFinite(cell.v)) cell.t = 'n'; + else if(isNaN(cell.v)) { cell.t = 'e'; cell.v = 0x0F; /* #VALUE! */ } + else { cell.t = 'e'; cell.v = 0x07; /*# DIV/0 */ } + } else if(typeof cell.v === 'boolean') cell.t = 'b'; else if(cell.v instanceof Date) { cell.z = o.dateNF || table_fmt[14]; @@ -192,4 +195,3 @@ function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksh return ws; } function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ { return sheet_add_aoa(null, data, opts); } - diff --git a/packages/ssf/bits/02_utilities.js b/packages/ssf/bits/02_utilities.js index f9d7b62..a4a4d03 100644 --- a/packages/ssf/bits/02_utilities.js +++ b/packages/ssf/bits/02_utilities.js @@ -5,6 +5,6 @@ function pad_(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v;return t.length>=d?t function rpad_(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>=d?t:t+fill(' ',d-t.length);} function pad0r1(v/*:any*/,d/*:number*/)/*:string*/{var t=""+Math.round(v); return t.length>=d?t:fill('0',d-t.length)+t;} function pad0r2(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>=d?t:fill('0',d-t.length)+t;} -var p2_32 = Math.pow(2,32); +var p2_32 = /*#__PURE__*/Math.pow(2,32); function pad0r(v/*:any*/,d/*:number*/)/*:string*/{if(v>p2_32||v<-p2_32) return pad0r1(v,d); var i = Math.round(v); return pad0r2(i,d); } function isgeneral(s/*:string*/, i/*:?number*/)/*:boolean*/ { i = i || 0; return s.length >= 7 + i && (s.charCodeAt(i)|32) === 103 && (s.charCodeAt(i+1)|32) === 101 && (s.charCodeAt(i+2)|32) === 110 && (s.charCodeAt(i+3)|32) === 101 && (s.charCodeAt(i+4)|32) === 114 && (s.charCodeAt(i+5)|32) === 97 && (s.charCodeAt(i+6)|32) === 108; } diff --git a/packages/ssf/bits/25_table.js b/packages/ssf/bits/25_table.js index b1f3247..52ae982 100644 --- a/packages/ssf/bits/25_table.js +++ b/packages/ssf/bits/25_table.js @@ -1,4 +1,5 @@ function init_table(t/*:any*/) { + if(!t) t = {}; t[0]= 'General'; t[1]= '0'; t[2]= '0.00'; @@ -28,6 +29,7 @@ function init_table(t/*:any*/) { t[48]= '##0.0E+0'; t[49]= '@'; t[56]= '"上午/下午 "hh"時"mm"分"ss"秒 "'; + return t; } var table_fmt = {}; diff --git a/packages/ssf/bits/60_number.js b/packages/ssf/bits/60_number.js index 435a5f4..fa66bf9 100644 --- a/packages/ssf/bits/60_number.js +++ b/packages/ssf/bits/60_number.js @@ -39,7 +39,7 @@ function write_num_f2(r/*:Array*/, aval/*:number*/, sign/*:string*/)/*:s return sign + (aval === 0 ? "" : ""+aval) + fill(" ", r[1].length + 2 + r[4].length); } var dec1 = /^#*0*\.([0#]+)/; -var closeparen = /\).*[0#]/; +var closeparen = /\)[^)]*[0#]/; var phone = /\(###\) ###\\?-####/; function hashq(str/*:string*/)/*:string*/ { var o = "", cc; diff --git a/packages/ssf/bits/90_main.js b/packages/ssf/bits/90_main.js index 5637775..2e8d64d 100644 --- a/packages/ssf/bits/90_main.js +++ b/packages/ssf/bits/90_main.js @@ -4,6 +4,8 @@ function choose_fmt(f/*:string*/, v/*:any*/) { if(l<4 && lat>-1) --l; if(fmt.length > 4) throw new Error("cannot find right format for |" + fmt.join("|") + "|"); if(typeof v !== "number") return [4, fmt.length === 4 || lat>-1?fmt[fmt.length-1]:"@"]; + /* NOTE: most spreadsheet software do not support NaN or infinities */ + if(typeof v === "number" && !isFinite(v)) v = 0; switch(fmt.length) { case 1: fmt = lat>-1 ? ["General", "General", "General", fmt[0]] : [fmt[0], fmt[0], fmt[0], "@"]; break; case 2: fmt = lat>-1 ? [fmt[0], fmt[0], fmt[0], fmt[1]] : [fmt[0], fmt[1], fmt[0], "@"]; break; @@ -40,5 +42,7 @@ function format(fmt/*:string|number*/,v/*:any*/,o/*:?any*/) { if(isgeneral(f[1])) return general_fmt(v, o); if(v === true) v = "TRUE"; else if(v === false) v = "FALSE"; else if(v === "" || v == null) return ""; + else if(isNaN(v) && f[1].indexOf("0") > -1) return "#VALUE!"; + else if(!isFinite(v) && f[1].indexOf("0") > -1) return "#DIV/0!"; return eval_fmt(f[1], v, o, f[0]); } diff --git a/packages/ssf/package.json b/packages/ssf/package.json index e3815fe..06039f9 100644 --- a/packages/ssf/package.json +++ b/packages/ssf/package.json @@ -20,7 +20,7 @@ "dtslint": "^0.1.2", "mocha": "~2.5.3", "typescript": "2.2.0", - "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz" + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz" }, "repository": { "type": "git", diff --git a/packages/ssf/ssf.flow.js b/packages/ssf/ssf.flow.js index 9aa3d61..50f8cc6 100644 --- a/packages/ssf/ssf.flow.js +++ b/packages/ssf/ssf.flow.js @@ -12,7 +12,7 @@ function pad_(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v;return t.length>=d?t function rpad_(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>=d?t:t+fill(' ',d-t.length);} function pad0r1(v/*:any*/,d/*:number*/)/*:string*/{var t=""+Math.round(v); return t.length>=d?t:fill('0',d-t.length)+t;} function pad0r2(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>=d?t:fill('0',d-t.length)+t;} -var p2_32 = Math.pow(2,32); +var p2_32 = /*#__PURE__*/Math.pow(2,32); function pad0r(v/*:any*/,d/*:number*/)/*:string*/{if(v>p2_32||v<-p2_32) return pad0r1(v,d); var i = Math.round(v); return pad0r2(i,d); } function isgeneral(s/*:string*/, i/*:?number*/)/*:boolean*/ { i = i || 0; return s.length >= 7 + i && (s.charCodeAt(i)|32) === 103 && (s.charCodeAt(i+1)|32) === 101 && (s.charCodeAt(i+2)|32) === 110 && (s.charCodeAt(i+3)|32) === 101 && (s.charCodeAt(i+4)|32) === 114 && (s.charCodeAt(i+5)|32) === 97 && (s.charCodeAt(i+6)|32) === 108; } /*:: @@ -42,6 +42,7 @@ var months/*:Array >*/ = [ ['D', 'Dec', 'December'] ]; function init_table(t/*:any*/) { + if(!t) t = {}; t[0]= 'General'; t[1]= '0'; t[2]= '0.00'; @@ -71,6 +72,7 @@ function init_table(t/*:any*/) { t[48]= '##0.0E+0'; t[49]= '@'; t[56]= '"上午/下午 "hh"時"mm"分"ss"秒 "'; + return t; } var table_fmt = {}; @@ -417,7 +419,7 @@ function write_num_f2(r/*:Array*/, aval/*:number*/, sign/*:string*/)/*:s return sign + (aval === 0 ? "" : ""+aval) + fill(" ", r[1].length + 2 + r[4].length); } var dec1 = /^#*0*\.([0#]+)/; -var closeparen = /\).*[0#]/; +var closeparen = /\)[^)]*[0#]/; var phone = /\(###\) ###\\?-####/; function hashq(str/*:string*/)/*:string*/ { var o = "", cc; @@ -951,6 +953,8 @@ function choose_fmt(f/*:string*/, v/*:any*/) { if(l<4 && lat>-1) --l; if(fmt.length > 4) throw new Error("cannot find right format for |" + fmt.join("|") + "|"); if(typeof v !== "number") return [4, fmt.length === 4 || lat>-1?fmt[fmt.length-1]:"@"]; + /* NOTE: most spreadsheet software do not support NaN or infinities */ + if(typeof v === "number" && !isFinite(v)) v = 0; switch(fmt.length) { case 1: fmt = lat>-1 ? ["General", "General", "General", fmt[0]] : [fmt[0], fmt[0], fmt[0], "@"]; break; case 2: fmt = lat>-1 ? [fmt[0], fmt[0], fmt[0], fmt[1]] : [fmt[0], fmt[1], fmt[0], "@"]; break; @@ -987,6 +991,8 @@ function format(fmt/*:string|number*/,v/*:any*/,o/*:?any*/) { if(isgeneral(f[1])) return general_fmt(v, o); if(v === true) v = "TRUE"; else if(v === false) v = "FALSE"; else if(v === "" || v == null) return ""; + else if(isNaN(v) && f[1].indexOf("0") > -1) return "#VALUE!"; + else if(!isFinite(v) && f[1].indexOf("0") > -1) return "#DIV/0!"; return eval_fmt(f[1], v, o, f[0]); } function load_entry(fmt/*:string*/, idx/*:?number*/)/*:number*/ { diff --git a/packages/ssf/ssf.js b/packages/ssf/ssf.js index 4f04117..7cd7109 100644 --- a/packages/ssf/ssf.js +++ b/packages/ssf/ssf.js @@ -38,6 +38,7 @@ var months = [ ['D', 'Dec', 'December'] ]; function init_table(t) { + if(!t) t = {}; t[0]= 'General'; t[1]= '0'; t[2]= '0.00'; @@ -67,6 +68,7 @@ function init_table(t) { t[48]= '##0.0E+0'; t[49]= '@'; t[56]= '"上午/下午 "hh"時"mm"分"ss"秒 "'; + return t; } var table_fmt = {}; @@ -412,7 +414,7 @@ function write_num_f2(r, aval, sign) { return sign + (aval === 0 ? "" : ""+aval) + fill(" ", r[1].length + 2 + r[4].length); } var dec1 = /^#*0*\.([0#]+)/; -var closeparen = /\).*[0#]/; +var closeparen = /\)[^)]*[0#]/; var phone = /\(###\) ###\\?-####/; function hashq(str) { var o = "", cc; @@ -943,6 +945,8 @@ function choose_fmt(f, v) { if(l<4 && lat>-1) --l; if(fmt.length > 4) throw new Error("cannot find right format for |" + fmt.join("|") + "|"); if(typeof v !== "number") return [4, fmt.length === 4 || lat>-1?fmt[fmt.length-1]:"@"]; + /* NOTE: most spreadsheet software do not support NaN or infinities */ + if(typeof v === "number" && !isFinite(v)) v = 0; switch(fmt.length) { case 1: fmt = lat>-1 ? ["General", "General", "General", fmt[0]] : [fmt[0], fmt[0], fmt[0], "@"]; break; case 2: fmt = lat>-1 ? [fmt[0], fmt[0], fmt[0], fmt[1]] : [fmt[0], fmt[1], fmt[0], "@"]; break; @@ -979,6 +983,8 @@ function format(fmt,v,o) { if(isgeneral(f[1])) return general_fmt(v, o); if(v === true) v = "TRUE"; else if(v === false) v = "FALSE"; else if(v === "" || v == null) return ""; + else if(isNaN(v) && f[1].indexOf("0") > -1) return "#VALUE!"; + else if(!isFinite(v) && f[1].indexOf("0") > -1) return "#DIV/0!"; return eval_fmt(f[1], v, o, f[0]); } function load_entry(fmt, idx) { diff --git a/packages/ssf/test/oddities.js b/packages/ssf/test/oddities.js index fb59c85..cbe8a07 100644 --- a/packages/ssf/test/oddities.js +++ b/packages/ssf/test/oddities.js @@ -19,4 +19,12 @@ describe('oddities', function() { var chk = function(fmt){ return function(){ SSF.format(fmt,0); }; }; bad.forEach(function(fmt){assert.throws(chk(fmt));}); }); + it('should handle NaN values and infinities', function() { + assert.equal(SSF.format('#,##0.0; (#,##0.0); "-"', NaN), " -"); + assert.equal(SSF.format('#,##0.0; (#,##0.0); "-"', Infinity), " -"); + assert.equal(SSF.format('#,##0.0; (#,##0.0); "-"', -Infinity), " -"); + assert.equal(SSF.format('0.00', NaN), "#VALUE!"); + assert.equal(SSF.format('0.00', Infinity), "#DIV/0!"); + assert.equal(SSF.format('0.00', -Infinity), "#DIV/0!"); + }); }); diff --git a/types/index.d.ts b/types/index.d.ts index 25b8d24..af68434 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -24,12 +24,12 @@ export function readFile(filename: string, opts?: ParsingOptions): WorkBook; /** Attempts to parse data */ export function read(data: any, opts?: ParsingOptions): WorkBook; /** Attempts to write or download workbook data to file */ -export function writeFile(data: WorkBook, filename: string, opts?: WritingOptions): any; +export function writeFile(data: WorkBook, filename: string, opts?: WritingOptions): void; /** Attempts to write or download workbook data to XLSX file */ -export function writeFileXLSX(data: WorkBook, filename: string, opts?: WritingOptions): any; +export function writeFileXLSX(data: WorkBook, filename: string, opts?: WritingOptions): void; /** Attempts to write or download workbook data to file asynchronously */ type CBFunc = () => void; -export function writeFileAsync(filename: string, data: WorkBook, opts: WritingOptions | CBFunc, cb?: CBFunc): any; +export function writeFileAsync(filename: string, data: WorkBook, opts: WritingOptions | CBFunc, cb?: CBFunc): void; /** Attempts to write the workbook data */ export function write(data: WorkBook, opts: WritingOptions): any; /** Attempts to write the workbook data as XLSX */ @@ -277,7 +277,7 @@ export interface WritingOptions extends CommonOptions { */ compression?: boolean; - /** Overwride theme XML when exporting to XLSX/XLSM/XLSB */ + /** Override theme XML when exporting to XLSX/XLSM/XLSB */ themeXLSX?: string; /**