From e59de73f99e63624a6447434db9ddcdec3a6a408 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Thu, 30 Sep 2021 03:28:03 -0400 Subject: [PATCH] Hyperlink cleanup [ci skip] - XLS normalize URLs and use moniker (fixes #2385) - ODS row heights and column widths (fixes #2378) - DSV parse of files with leading <, deviating from Excel (fixes #1238) - read `ArrayBuffer` with no type (fixes #2316) - removed invalid test files that throw errors due to string length - AOA/JSON null optional map to #NULL! (fixes #1196) --- bits/27_csfutils.js | 7 ++++- bits/32_odmanrdf.js | 2 +- bits/38_xlstypes.js | 33 +++++++++++++------- bits/40_harb.js | 2 +- bits/67_wsxml.js | 4 +-- bits/75_xlml.js | 3 +- bits/80_parseods.js | 6 ++-- bits/81_writeods.js | 68 +++++++++++++++++++++++++++++++++++------ bits/87_read.js | 4 +-- bits/90_utils.js | 6 ++-- docbits/64_cellprops.md | 52 +++++++++++++++++++++++++++++-- docbits/82_util.md | 42 ++++++++++++++----------- package.json | 2 +- tests.lst | 8 ++--- 14 files changed, 181 insertions(+), 58 deletions(-) diff --git a/bits/27_csfutils.js b/bits/27_csfutils.js index a31de3d..5eaf3fe 100644 --- a/bits/27_csfutils.js +++ b/bits/27_csfutils.js @@ -135,7 +135,12 @@ function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksh if(data[R][C] && typeof data[R][C] === 'object' && !Array.isArray(data[R][C]) && !(data[R][C] instanceof Date)) cell = data[R][C]; else { if(Array.isArray(cell.v)) { cell.f = data[R][C][1]; cell.v = cell.v[0]; } - if(cell.v === null) { if(cell.f) cell.t = 'n'; else if(!o.sheetStubs) continue; else cell.t = 'z'; } + if(cell.v === null) { + if(cell.f) cell.t = 'n'; + else if(o.nullError) { cell.t = 'e'; cell.v = 0; } + else if(!o.sheetStubs) continue; + else 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) { diff --git a/bits/32_odmanrdf.js b/bits/32_odmanrdf.js index 1902d90..ab910cb 100644 --- a/bits/32_odmanrdf.js +++ b/bits/32_odmanrdf.js @@ -57,7 +57,7 @@ function write_rdf(rdf) { } /* TODO: pull properties */ var write_meta_ods/*:{(wb:Workbook, opts:any):string}*/ = (function() { - var payload = 'Sheet' + 'JS ' + XLSX.version + ''; + var payload = 'Sheet' + 'JS ' + XLSX.version + ''; return function wmo(/*:: wb: Workbook, opts: any*/)/*:string*/ { return payload; }; diff --git a/bits/38_xlstypes.js b/bits/38_xlstypes.js index 5008570..32e3cbc 100644 --- a/bits/38_xlstypes.js +++ b/bits/38_xlstypes.js @@ -523,16 +523,17 @@ function parse_URLMoniker(blob/*::, length, opts*/) { /* [MS-OSHARED] 2.3.7.8 FileMoniker TODO: all fields */ function parse_FileMoniker(blob/*::, length*/) { - blob.l += 2; //var cAnti = blob.read_shift(2); + var cAnti = blob.read_shift(2); + var preamble = ""; while(cAnti-- > 0) preamble += "../"; var ansiPath = blob.read_shift(0, 'lpstr-ansi'); blob.l += 2; //var endServer = blob.read_shift(2); if(blob.read_shift(2) != 0xDEAD) throw new Error("Bad FileMoniker"); var sz = blob.read_shift(4); - if(sz === 0) return ansiPath.replace(/\\/g,"/"); + if(sz === 0) return preamble + ansiPath.replace(/\\/g,"/"); var bytes = blob.read_shift(4); if(blob.read_shift(2) != 3) throw new Error("Bad FileMoniker"); var unicodePath = blob.read_shift(bytes>>1, 'utf16le').replace(chr0,""); - return unicodePath; + return preamble + unicodePath; } /* [MS-OSHARED] 2.3.7.2 HyperlinkMoniker TODO: all the monikers */ @@ -551,6 +552,13 @@ function parse_HyperlinkString(blob/*::, length*/) { var o = len > 0 ? blob.read_shift(len, 'utf16le').replace(chr0, "") : ""; return o; } +function write_HyperlinkString(str/*:string*/, o) { + if(!o) o = new_buf(6 + str.length * 2); + o.write_shift(4, 1 + str.length); + for(var i = 0; i < str.length; ++i) o.write_shift(2, str.charCodeAt(i)); + o.write_shift(2, 0); + return o; +} /* [MS-OSHARED] 2.3.7.1 Hyperlink Object */ function parse_Hyperlink(blob, length)/*:Hyperlink*/ { @@ -571,6 +579,7 @@ function parse_Hyperlink(blob, length)/*:Hyperlink*/ { var target = targetFrameName||moniker||oleMoniker||""; if(target && Loc) target+="#"+Loc; if(!target) target = "#" + Loc; + if((flags & 0x0002) && target.charAt(0) == "/" && target.charAt(1) != "/") target = "file://" + target; var out = ({Target:target}/*:any*/); if(guid) out.guid = guid; if(fileTime) out.time = fileTime; @@ -580,29 +589,31 @@ function parse_Hyperlink(blob, length)/*:Hyperlink*/ { function write_Hyperlink(hl) { var out = new_buf(512), i = 0; var Target = hl.Target; - var F = Target.indexOf("#") > -1 ? 0x1f : 0x17; + if(Target.slice(0,7) == "file://") Target = Target.slice(7); + var hashidx = Target.indexOf("#"); + var F = hashidx > -1 ? 0x1f : 0x17; switch(Target.charAt(0)) { case "#": F=0x1c; break; case ".": F&=~2; break; } out.write_shift(4,2); out.write_shift(4, F); var data = [8,6815827,6619237,4849780,83]; for(i = 0; i < data.length; ++i) out.write_shift(4, data[i]); if(F == 0x1C) { Target = Target.slice(1); - out.write_shift(4, Target.length + 1); - for(i = 0; i < Target.length; ++i) out.write_shift(2, Target.charCodeAt(i)); - out.write_shift(2, 0); + write_HyperlinkString(Target, out); } else if(F & 0x02) { data = "e0 c9 ea 79 f9 ba ce 11 8c 82 00 aa 00 4b a9 0b".split(" "); for(i = 0; i < data.length; ++i) out.write_shift(1, parseInt(data[i], 16)); - out.write_shift(4, 2*(Target.length + 1)); - for(i = 0; i < Target.length; ++i) out.write_shift(2, Target.charCodeAt(i)); + var Pretarget = hashidx > -1 ? Target.slice(0, hashidx) : Target; + out.write_shift(4, 2*(Pretarget.length + 1)); + for(i = 0; i < Pretarget.length; ++i) out.write_shift(2, Pretarget.charCodeAt(i)); out.write_shift(2, 0); + if(F & 0x08) write_HyperlinkString(hashidx > -1 ? Target.slice(hashidx+1): "", out); } else { data = "03 03 00 00 00 00 00 00 c0 00 00 00 00 00 00 46".split(" "); for(i = 0; i < data.length; ++i) out.write_shift(1, parseInt(data[i], 16)); var P = 0; while(Target.slice(P*3,P*3+3)=="../"||Target.slice(P*3,P*3+3)=="..\\") ++P; out.write_shift(2, P); - out.write_shift(4, Target.length + 1); - for(i = 0; i < Target.length; ++i) out.write_shift(1, Target.charCodeAt(i) & 0xFF); + out.write_shift(4, Target.length - 3 * P + 1); + for(i = 0; i < Target.length - 3 * P; ++i) out.write_shift(1, Target.charCodeAt(i + 3 * P) & 0xFF); out.write_shift(1, 0); out.write_shift(2, 0xFFFF); out.write_shift(2, 0xDEAD); diff --git a/bits/40_harb.js b/bits/40_harb.js index 6edce7c..a823197 100644 --- a/bits/40_harb.js +++ b/bits/40_harb.js @@ -352,7 +352,7 @@ var SYLK = (function() { Au:'ù', Bu:'ú', Cu:'û', Hu:'ü', KC:'Ç', Kc:'ç', q:'æ', z:'œ', a:'Æ', j:'Œ', DN:209, Dn:241, Hy:255, - S:169, c:170, R:174, B:180, + S:169, c:170, R:174, "B ":180, /*::[*/0/*::]*/:176, /*::[*/1/*::]*/:177, /*::[*/2/*::]*/:178, /*::[*/3/*::]*/:179, /*::[*/5/*::]*/:181, /*::[*/6/*::]*/:182, /*::[*/7/*::]*/:183, Q:185, k:186, b:208, i:216, l:222, s:240, y:248, diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js index beed368..23f8a3c 100644 --- a/bits/67_wsxml.js +++ b/bits/67_wsxml.js @@ -156,9 +156,9 @@ function parse_ws_xml_hlinks(s, data/*:Array*/, rels) { var rel = ((rels || {})['!id']||[])[val.id]; if(rel) { val.Target = rel.Target; - if(val.location) val.Target += "#"+val.location; + if(val.location) val.Target += "#"+unescapexml(val.location); } else { - val.Target = "#" + val.location; + val.Target = "#" + unescapexml(val.location); rel = {Target: val.Target, TargetMode: 'Internal'}; } val.Rel = rel; diff --git a/bits/75_xlml.js b/bits/75_xlml.js index 08aac77..9fec98b 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -183,6 +183,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ { else str = utf8read(str); } var opening = str.slice(0, 1024).toLowerCase(), ishtml = false; + if((opening.indexOf(">") & 1023) > Math.min((opening.indexOf(",") & 1023), (opening.indexOf(";")&1023))) { var _o = dup(opts); _o.type = "string"; return PRN.to_workbook(str, _o); } if(opening.indexOf("= 0) ishtml = true; }); if(ishtml) return HTML_.to_workbook(str, opts); var Rn; @@ -226,7 +227,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ { } else cursheet[encode_col(c) + encode_row(r)] = cell; } if(cell.HRef) { - cell.l = ({Target:cell.HRef}/*:any*/); + cell.l = ({Target:unescapexml(cell.HRef)}/*:any*/); if(cell.HRefScreenTip) cell.l.Tooltip = cell.HRefScreenTip; delete cell.HRef; delete cell.HRefScreenTip; } diff --git a/bits/80_parseods.js b/bits/80_parseods.js index c539a75..fb04786 100644 --- a/bits/80_parseods.js +++ b/bits/80_parseods.js @@ -221,7 +221,7 @@ var parse_content_xml = (function() { case 'scripts': // 3.12 case 'styles': // TODO case 'font-face-decls': // 3.14 - case 'master-styles': //3.15.4 -- relevant for FODS + case 'master-styles': // 3.15.4 -- relevant for FODS if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;} else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], true]); break; @@ -515,11 +515,11 @@ var parse_content_xml = (function() { if(Rn[1]!== '/') { atag = parsexmltag(Rn[0], false); if(!atag.href) break; - atag.Target = atag.href; delete atag.href; + atag.Target = unescapexml(atag.href); delete atag.href; if(atag.Target.charAt(0) == "#" && atag.Target.indexOf(".") > -1) { _Ref = ods_to_csf_3D(atag.Target.slice(1)); atag.Target = "#" + _Ref[0] + "!" + _Ref[1]; - } + } else if(atag.Target.match(/^\.\.[\\\/]/)) atag.Target = atag.Target.slice(3); } break; diff --git a/bits/81_writeods.js b/bits/81_writeods.js index b8ececb..773b761 100644 --- a/bits/81_writeods.js +++ b/bits/81_writeods.js @@ -37,9 +37,17 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = (function() { var R=0,C=0, range = decode_range(ws['!ref']||"A1"); var marr/*:Array*/ = ws['!merges'] || [], mi = 0; var dense = Array.isArray(ws); - for(R = 0; R < range.s.r; ++R) o.push(' \n'); + if(ws["!cols"]) { + for(C = 0; C <= range.e.c; ++C) o.push(' \n'); + } + var H = "", ROWS = ws["!rows"]||[]; + for(R = 0; R < range.s.r; ++R) { + H = ROWS[R] ? ' table:style-name="ro' + ROWS[R].ods + '"' : ""; + o.push(' \n'); + } for(; R <= range.e.r; ++R) { - o.push(' \n'); + H = ROWS[R] ? ' table:style-name="ro' + ROWS[R].ods + '"' : ""; + o.push(' \n'); for(C=0; C < range.s.c; ++C) o.push(null_cell_xml); for(; C <= range.e.c; ++C) { var skip = false, ct = {}, textp = ""; @@ -92,8 +100,11 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = (function() { } var text_p = write_text_p(textp); if(cell.l && cell.l.Target) { - var _tgt = cell.l.Target; _tgt = _tgt.charAt(0) == "#" ? "#" + csf_to_ods_3D(_tgt.slice(1)) : _tgt; - text_p = writextag('text:a', text_p, {'xlink:href': _tgt}); + var _tgt = cell.l.Target; + _tgt = _tgt.charAt(0) == "#" ? "#" + csf_to_ods_3D(_tgt.slice(1)) : _tgt; + // TODO: choose correct parent path format based on link delimiters + if(_tgt.charAt(0) != "#" && !_tgt.match(/^\w+:/)) _tgt = '../' + _tgt; + text_p = writextag('text:a', text_p, {'xlink:href': _tgt.replace(/&/g, "&")}); } o.push(' ' + writextag('table:table-cell', writextag('text:p', text_p, {}), ct) + '\n'); } @@ -103,7 +114,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = (function() { return o.join(""); }; - var write_automatic_styles_ods = function(o/*:Array*/) { + var write_automatic_styles_ods = function(o/*:Array*/, wb) { o.push(' \n'); o.push(' \n'); @@ -114,6 +125,41 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = (function() { o.push(' \n'); o.push(' \n'); + /* column styles */ + var cidx = 0; + wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) { + if(!ws) return; + if(ws["!cols"]) { + for(var C = 0; C < ws["!cols"].length; ++C) if(ws["!cols"][C]) { + var colobj = ws["!cols"][C]; + if(colobj.width == null && colobj.wpx == null && colobj.wch == null) continue; + process_col(colobj); + colobj.ods = cidx; + var w = ws["!cols"][C].wpx + "px"; + o.push(' \n'); + o.push(' \n'); + o.push(' \n'); + ++cidx; + } + } + }); + + /* row styles */ + var ridx = 0; + wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) { + if(!ws) return; + if(ws["!rows"]) { + for(var R = 0; R < ws["!rows"].length; ++R) if(ws["!rows"][R]) { + ws["!rows"][R].ods = ridx; + var h = ws["!rows"][R].hpx + "px"; + o.push(' \n'); + o.push(' \n'); + o.push(' \n'); + ++ridx; + } + } + }); + /* table */ o.push(' \n'); // style:master-page-name="mp1">\n'); o.push(' \n'); @@ -175,9 +221,13 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = (function() { 'office:mimetype': "application/vnd.oasis.opendocument.spreadsheet" }); - if(opts.bookType == "fods") o.push('\n'); - else o.push('\n'); - write_automatic_styles_ods(o); + if(opts.bookType == "fods") { + o.push('\n'); + o.push(write_meta_ods().replace(/office:document-meta/g, "office:meta")); + // TODO: settings (equiv of settings.xml for ODS) + } else o.push('\n'); + // o.push(' \n'); + write_automatic_styles_ods(o, wb); o.push(' \n'); o.push(' \n'); for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts)); @@ -217,7 +267,7 @@ function write_ods(wb/*:any*/, opts/*:any*/) { /* TODO: this is hard-coded to satiate excel */ f = "meta.xml"; - zip_add_file(zip, f, write_meta_ods(/*::wb, opts*/)); + zip_add_file(zip, f, XML_HEADER + write_meta_ods(/*::wb, opts*/)); manifest.push([f, "text/xml"]); rdf.push([f, "MetadataFile"]); diff --git a/bits/87_read.js b/bits/87_read.js index c15b0d7..e866cfd 100644 --- a/bits/87_read.js +++ b/bits/87_read.js @@ -66,9 +66,9 @@ function read_prn(data, d, o, str) { function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ { reset_cp(); - if(typeof ArrayBuffer !== 'undefined' && data instanceof ArrayBuffer) return readSync(new Uint8Array(data), opts); - var d = data, n = [0,0,0,0], str = false; var o = opts||{}; + if(typeof ArrayBuffer !== 'undefined' && data instanceof ArrayBuffer) return readSync(new Uint8Array(data), (o = dup(o), o.type = "array", o)); + var d = data, n = [0,0,0,0], str = false; if(o.cellStyles) { o.cellNF = true; o.sheetStubs = true; } _ssfopts = {}; if(o.dateNF) _ssfopts.dateNF = o.dateNF; diff --git a/bits/90_utils.js b/bits/90_utils.js index efb1ddf..af0d998 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -23,13 +23,14 @@ function make_json_row(sheet/*:Worksheet*/, r/*:Range*/, R/*:number*/, cols/*:Ar var v = val.v; switch(val.t){ case 'z': if(v == null) break; continue; - case 'e': v = void 0; break; + case 'e': v = (v == 0 ? null : void 0); break; case 's': case 'd': case 'b': case 'n': break; default: throw new Error('unrecognized type ' + val.t); } if(hdr[C] != null) { if(v == null) { - if(defval !== undefined) row[hdr[C]] = defval; + if(val.t == "e" && v === null) row[hdr[C]] = null; + else if(defval !== undefined) row[hdr[C]] = defval; else if(raw && v === null) row[hdr[C]] = null; else continue; } else { @@ -221,6 +222,7 @@ function sheet_add_json(_ws/*:?Worksheet*/, js/*:Array*/, opts)/*:Worksheet if(!o.cellDates) { t = 'n'; v = datenum(v); } z = (o.dateNF || SSF._table[14]); } + else if(v === null && o.nullError) { t = 'e'; v = 0; } if(!cell) ws[ref] = cell = ({t:t, v:v}/*:any*/); else { cell.t = t; cell.v = v; diff --git a/docbits/64_cellprops.md b/docbits/64_cellprops.md index d0b48fb..f4cb805 100644 --- a/docbits/64_cellprops.md +++ b/docbits/64_cellprops.md @@ -1,5 +1,14 @@ #### Hyperlinks +
+ Format Support (click to show) + +**Cell Hyperlinks**: XLSX/M, XLSB, BIFF8 XLS, XLML, ODS + +**Tooltips**: XLSX/M, XLSB, BIFF8 XLS, XLML + +
+ Hyperlinks are stored in the `l` key of cell objects. The `Target` field of the hyperlink object is the target of the link, including the URI fragment. Tooltips are stored in the `Tooltip` field and are displayed when you move your mouse @@ -9,16 +18,55 @@ For example, the following snippet creates a link from cell `A3` to with the tip `"Find us @ SheetJS.com!"`: ```js -ws['A3'].l = { Target:"https://sheetjs.com", Tooltip:"Find us @ SheetJS.com!" }; +ws['A1'].l = { Target:"https://sheetjs.com", Tooltip:"Find us @ SheetJS.com!" }; ``` Note that Excel does not automatically style hyperlinks -- they will generally be displayed as normal text. +_Remote Links_ + +HTTP / HTTPS links can be used directly: + +```js +ws['A2'].l = { Target:"https://docs.sheetjs.com/#hyperlinks" }; +ws['A3'].l = { Target:"http://localhost:7262/yes_localhost_works" }; +``` + +Excel also supports `mailto` email links with subject line: + +```js +ws['A4'].l = { Target:"mailto:ignored@dev.null" }; +ws['A5'].l = { Target:"mailto:ignored@dev.null?subject=Test Subject" }; +``` + +_Local Links_ + +Links to absolute paths should use the `file://` URI scheme: + +```js +ws['B1'].l = { Target:"file:///SheetJS/t.xlsx" }; /* Link to /SheetJS/t.xlsx */ +ws['B2'].l = { Target:"file:///c:/SheetJS.xlsx" }; /* Link to c:\SheetJS.xlsx */ +``` + +Links to relative paths can be specified without a scheme: + +```js +ws['B3'].l = { Target:"SheetJS.xlsb" }; /* Link to SheetJS.xlsb */ +ws['B4'].l = { Target:"../SheetJS.xlsm" }; /* Link to ../SheetJS.xlsm */ +``` + +Relative Paths have undefined behavior in the SpreadsheetML 2003 format. Excel +2019 will treat a `..\` parent mark as two levels up. + +_Internal Links_ + Links where the target is a cell or range or defined name in the same workbook ("Internal Links") are marked with a leading hash character: ```js -ws['A2'].l = { Target:"#E2" }; /* link to cell E2 */ +ws['C1'].l = { Target:"#E2" }; /* Link to cell E2 */ +ws['C2'].l = { Target:"#Sheet2!E2" }; /* Link to cell E2 in sheet Sheet2 */ +ws['C3'].l = { Target:"#SomeDefinedName" }; /* Link to Defined Name */ ``` diff --git a/docbits/82_util.md b/docbits/82_util.md index e770fb6..46fb680 100644 --- a/docbits/82_util.md +++ b/docbits/82_util.md @@ -22,11 +22,12 @@ 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 | +| 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 | +|`nullError` | false | If true, emit `#NULL!` error cells for `null` values |
Examples (click to show) @@ -46,12 +47,13 @@ var ws = XLSX.utils.aoa_to_sheet([ existing worksheet object. It follows the same process as `aoa_to_sheet` and accepts 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 | -|`origin` | | Use specified cell as starting point (see below) | +| 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 | +|`nullError` | false | If true, emit `#NULL!` error cells for `null` values | +|`origin` | | Use specified cell as starting point (see below) | `origin` is expected to be one of: @@ -110,11 +112,14 @@ default column order is determined by the first appearance of the field using |`dateNF` | FMT 14 | Use specified date format in string output | |`cellDates` | false | Store dates as type `d` (default is `n`) | |`skipHeader` | false | If true, do not include header row in output | +|`nullError` | false | If true, emit `#NULL!` error cells for `null` values | - All fields from each row will be written. If `header` is an array and it does not contain a particular field, the key will be appended to the array. - Cell types are deduced from the type of each value. For example, a `Date` object will generate a Date cell, while a string will generate a Text cell. +- Null values will be skipped by default. If `nullError` is true, an error cell + corresponding to `#NULL!` will be written to the worksheet.
Examples (click to show) @@ -145,13 +150,14 @@ var ws = XLSX.utils.json_to_sheet([ worksheet object. It follows the same process as `json_to_sheet` and accepts an options argument: -| Option Name | Default | Description | -| :---------- | :------: | :-------------------------------------------------- | -|`header` | | Use specified column order (default `Object.keys`) | -|`dateNF` | FMT 14 | Use specified date format in string output | -|`cellDates` | false | Store dates as type `d` (default is `n`) | -|`skipHeader` | false | If true, do not include header row in output | -|`origin` | | Use specified cell as starting point (see below) | +| Option Name | Default | Description | +| :---------- | :-----: | :--------------------------------------------------- | +|`header` | | Use specified column order (default `Object.keys`) | +|`dateNF` | FMT 14 | Use specified date format in string output | +|`cellDates` | false | Store dates as type `d` (default is `n`) | +|`skipHeader` | false | If true, do not include header row in output | +|`nullError` | false | If true, emit `#NULL!` error cells for `null` values | +|`origin` | | Use specified cell as starting point (see below) | `origin` is expected to be one of: diff --git a/package.json b/package.json index 082d2fc..1c1af17 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "fflate": "^0.3.8", "ssf": "~0.11.2", "wmf": "~1.0.1", - "word": "~0.4.0" + "word": "~0.3.0" }, "devDependencies": { "@sheetjs/uglify-js": "~2.7.3", diff --git a/tests.lst b/tests.lst index b5c793f..19468e7 100644 --- a/tests.lst +++ b/tests.lst @@ -277,9 +277,9 @@ apachepoi_evaluate_formula_with_structured_table_references.xlsx apachepoi_headerFooterTest.xlsx apachepoi_noSharedStringTable.xlsx apachepoi_picture.xlsx -apachepoi_poc-shared-strings.xlsx -apachepoi_poc-xmlbomb-empty.xlsx -apachepoi_poc-xmlbomb.xlsx +# apachepoi_poc-shared-strings.xlsx # string length exceeds 32767 chars +# apachepoi_poc-xmlbomb-empty.xlsx # string length exceeds 32767 chars +# apachepoi_poc-xmlbomb.xlsx # string length exceeds 32767 chars # apachepoi_protected_passtika.xlsx # password apachepoi_ref-56737.xlsx apachepoi_ref2-56737.xlsx @@ -777,7 +777,7 @@ apachepoi_46136-NoWarnings.xls apachepoi_46136-WithWarnings.xls apachepoi_46137.xls apachepoi_46250.xls -apachepoi_46368.xls +# apachepoi_46368.xls # string length exceeds 32767 chars apachepoi_46445.xls apachepoi_46515.xls apachepoi_46670_http.xls