roundtrip 1904 date setting

This commit is contained in:
SheetJS 2022-06-06 19:05:27 -04:00
parent 83ddb4c120
commit 4cc0412154
22 changed files with 3554 additions and 552 deletions

@ -20,7 +20,7 @@ jobs:
- run: sudo chmod a+x /usr/bin/rooster
- run: make init
- run: 'cd test_files; make all; cd -'
- run: deno test --allow-env --allow-read --allow-write test.ts
- run: deno test --allow-env --allow-read --allow-write --config misc/test.deno.jsonc test.ts
# full test
full:
runs-on: ubuntu-latest
@ -36,4 +36,4 @@ jobs:
- run: sudo chmod a+x /usr/bin/rooster
- run: make init
- run: 'cd test_files; make all; cd -'
- run: deno test --allow-env --allow-read --allow-write test.ts
- run: deno test --allow-env --allow-read --allow-write --config misc/test.deno.jsonc test.ts

@ -141,9 +141,12 @@ test mocha: test.js ## Run test suite
test-esm: test.mjs ## Run Node ESM test suite
npx -y mocha@9 -R spec -t 30000 $<
test.ts: test.mts
node -pe 'var data = fs.readFileSync("'$<'", "utf8"); data.split("\n").map(function(l) { return l.replace(/^describe\((.*?)function\(\)/, "Deno.test($$1async function(t)").replace(/\b(?:it|describe)\((.*?)function\(\)/g, "await t.step($$1async function(t)").replace("assert.ok", "assert.assert"); }).join("\n")' > $@
.PHONY: test-deno
test-deno: test.ts ## Run Deno test suite
deno test --allow-env --allow-read --allow-write $<
deno test --allow-env --allow-read --allow-write --config misc/test.deno.jsonc $<
#* To run tests for one format, make test_<fmt>
#* To run the core test suite, make test_misc

@ -12,7 +12,9 @@ port calculations to web apps; automate common spreadsheet tasks, and much more!
![License](https://img.shields.io/github/license/SheetJS/sheetjs)
[![Build Status](https://img.shields.io/github/workflow/status/sheetjs/sheetjs/Tests:%20node.js)](https://github.com/SheetJS/sheetjs/actions)
[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/SheetJS/sheetjs)](https://snyk.io/test/github/SheetJS/sheetjs)
[![npm Downloads](https://img.shields.io/npm/dm/xlsx.svg)](https://npmjs.org/package/xlsx)
[![npm Downloads](https://img.shields.io/npm/dm/xlsx.svg)](https://cdn.sheetjs.com/)
[![GitHub Repo stars](https://img.shields.io/github/stars/SheetJS/sheetjs?style=social)](https://github.com/SheetJS/sheetjs)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs)
[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs)
@ -25,13 +27,13 @@ port calculations to web apps; automate common spreadsheet tasks, and much more!
## Related Projects
- <https://oss.sheetjs.com/notes/> file format notes
- <https://oss.sheetjs.com/notes/>: File Format Notes
- [`ssf`](packages/ssf): Format data using ECMA-376 spreadsheet format codes
- [`xlsx-cli`](packages/xlsx-cli/): NodeJS command-line tool for processing files
- [`test_files`](https://github.com/SheetJS/test_files): test files repo
- [`test_files`](https://github.com/SheetJS/test_files): Sample spreadsheets
- [`cfb`](https://github.com/SheetJS/js-cfb): Container (OLE/ZIP) format library

@ -157,8 +157,8 @@ function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksh
else if(typeof cell.v === 'boolean') cell.t = 'b';
else if(cell.v instanceof Date) {
cell.z = o.dateNF || table_fmt[14];
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v)); }
else { cell.t = 'n'; cell.v = datenum(cell.v); cell.w = SSF_format(cell.z, cell.v); }
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v, o.date1904)); }
else { cell.t = 'n'; cell.v = datenum(cell.v, o.date1904); cell.w = SSF_format(cell.z, cell.v); }
}
else cell.t = 's';
}

@ -440,7 +440,7 @@ var SYLK = /*#__PURE__*/(function() {
wb.Workbook.Names.push(nn);
} break;
case 'C': /* cell */
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1;
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1, formula = "", cell_t = "z";
for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'A': break; // TODO: comment
case 'X': C = parseInt(record[rj].slice(1), 10)-1; C_seen_X = true; break;
@ -450,26 +450,24 @@ var SYLK = /*#__PURE__*/(function() {
break;
case 'K':
val = record[rj].slice(1);
if(val.charAt(0) === '"') val = val.slice(1,val.length - 1);
else if(val === 'TRUE') val = true;
else if(val === 'FALSE') val = false;
if(val.charAt(0) === '"') { val = val.slice(1,val.length - 1); cell_t = "s"; }
else if(val === 'TRUE' || val === 'FALSE') { val = val === 'TRUE'; cell_t = "b"; }
else if(!isNaN(fuzzynum(val))) {
val = fuzzynum(val);
if(next_cell_format !== null && fmt_is_date(next_cell_format)) val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val);
val = fuzzynum(val); cell_t = "n";
if(next_cell_format !== null && fmt_is_date(next_cell_format) && opts.cellDates) { val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val); cell_t = "d"; }
} else if(!isNaN(fuzzydate(val).getDate())) {
val = parseDate(val);
val = parseDate(val); cell_t = "d";
if(!opts.cellDates) { cell_t = "n"; val = datenum(val, wb.Workbook.WBProps.date1904); }
}
if(typeof $cptable !== 'undefined' && typeof val == "string" && ((opts||{}).type != "string") && (opts||{}).codepage) val = $cptable.utils.decode(opts.codepage, val);
C_seen_K = true;
break;
case 'E':
C_seen_E = true;
var formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
arr[R][C] = [arr[R][C], formula];
formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
break;
case 'S':
C_seen_S = true;
arr[R][C] = [arr[R][C], "S5S"];
break;
case 'G': break; // unknown
case 'R': _R = parseInt(record[rj].slice(1), 10)-1; break;
@ -477,15 +475,21 @@ var SYLK = /*#__PURE__*/(function() {
default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
}
if(C_seen_K) {
if(arr[R][C] && arr[R][C].length == 2) arr[R][C][0] = val;
else arr[R][C] = val;
if(!arr[R][C]) arr[R][C] = { t: cell_t, v: val };
else { arr[R][C].t = cell_t; arr[R][C].v = val; }
if(next_cell_format) arr[R][C].z = next_cell_format;
if(opts.cellText !== false && next_cell_format) arr[R][C].w = SSF_format(arr[R][C].z, arr[R][C].v, { date1904: wb.Workbook.WBProps.date1904 });
next_cell_format = null;
}
if(C_seen_S) {
if(C_seen_E) throw new Error("SYLK shared formula cannot have own formula");
var shrbase = _R > -1 && arr[_R][_C];
if(!shrbase || !shrbase[1]) throw new Error("SYLK shared formula cannot find base");
arr[R][C][1] = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
formula = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
}
if(formula) {
if(!arr[R][C]) arr[R][C] = { t: 'n', f: formula };
else arr[R][C].f = formula;
}
break;
case 'F': /* Format */
@ -537,7 +541,8 @@ var SYLK = /*#__PURE__*/(function() {
function sylk_to_workbook(d/*:RawData*/, opts)/*:Workbook*/ {
var aoasht = sylk_to_aoa(d, opts);
var aoa = aoasht[0], ws = aoasht[1], wb = aoasht[2];
var o = aoa_to_sheet(aoa, opts);
var _opts = dup(opts); _opts.date1904 = (((wb||{}).Workbook || {}).WBProps || {}).date1904;
var o = aoa_to_sheet(aoa, _opts);
keys(ws).forEach(function(k) { o[k] = ws[k]; });
var outwb = sheet_to_workbook(o, opts);
keys(wb).forEach(function(k) { outwb[k] = wb[k]; });
@ -581,11 +586,12 @@ var SYLK = /*#__PURE__*/(function() {
});
}
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/, wb/*:?WorkBook*/)/*:string*/ {
var preamble/*:Array<string>*/ = ["ID;PSheetJS;N;E"], o/*:Array<string>*/ = [];
var r = safe_decode_range(ws['!ref']), cell/*:Cell*/;
var dense = Array.isArray(ws);
var RS = "\r\n";
var d1904 = (((wb||{}).Workbook||{}).WBProps||{}).date1904;
preamble.push("P;PGeneral");
preamble.push("F;P0;DG0G8;M255");
@ -593,12 +599,13 @@ var SYLK = /*#__PURE__*/(function() {
if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']);
preamble.push("B;Y" + (r.e.r - r.s.r + 1) + ";X" + (r.e.c - r.s.c + 1) + ";D" + [r.s.c,r.s.r,r.e.c,r.e.r].join(" "));
preamble.push("O;L;D;B" + (d1904 ? ";V4" : "") + ";K47;G100 0.001");
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
cell = dense ? (ws[R]||[])[C]: ws[coord];
if(!cell || (cell.v == null && (!cell.f || cell.F))) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); // TODO: pass date1904 info
}
}
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;

@ -45,6 +45,7 @@ function write_BrtWbProp(data/*:?WBProps*/, o) {
var flags = 0;
if(data) {
/* TODO: mirror parse_BrtWbProp fields */
if(data.date1904) flags |= 0x01;
if(data.filterPrivacy) flags |= 0x08;
}
o.write_shift(4, flags);

@ -942,9 +942,10 @@ function write_props_xlml(wb/*:Workbook*/, opts)/*:string*/ {
return o.join("");
}
/* TODO */
function write_wb_xlml(/*::wb, opts*/)/*:string*/ {
function write_wb_xlml(wb/*::, opts*/)/*:string*/ {
/* OfficeDocumentSettings */
/* ExcelWorkbook */
if((((wb||{}).Workbook||{}).WBProps||{}).date1904) return '<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"><Date1904/></ExcelWorkbook>';
return "";
}
/* TODO */

@ -49,13 +49,14 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
var merges/*:Array<Range>*/ = [], mrange = {}, mR = 0, mC = 0;
var rowinfo/*:Array<RowInfo>*/ = [], rowpeat = 1, colpeat = 1;
var arrayf/*:Array<[Range, string]>*/ = [];
var WB = {Names:[]};
var WB = {Names:[], WBProps:{}};
var atag = ({}/*:any*/);
var _Ref/*:[string, string]*/ = ["", ""];
var comments/*:Array<Comment>*/ = [], comment/*:Comment*/ = ({}/*:any*/);
var creator = "", creatoridx = 0;
var isstub = false, intable = false;
var i = 0;
var baddate = 1;
xlmlregex.lastIndex = 0;
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
@ -167,7 +168,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v); }
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
q.z = 'm/d/yy'; break;
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
@ -368,7 +369,14 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
case 'table-header-columns': break; // 9.1.11 <table:table-header-columns>
case 'table-columns': break; // 9.1.12 <table:table-columns>
case 'null-date': break; // 9.4.2 <table:null-date> TODO: date1904
case 'null-date': // 9.4.2 <table:null-date>
tag = parsexmltag(Rn[0], false);
switch(tag["date-value"]) {
case "1904-01-01": WB.WBProps.date1904 = true;
/* falls through */
case "1900-01-01": baddate = 0;
}
break;
case 'graphic-properties': break; // 17.21 <style:graphic-properties>
case 'calculation-settings': break; // 9.4.1 <table:calculation-settings>

@ -255,6 +255,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
write_automatic_styles_ods(o, wb);
o.push(' <office:body>\n');
o.push(' <office:spreadsheet>\n');
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
o.push(' </office:spreadsheet>\n');

@ -134,7 +134,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
case 'xml':
case 'xlml': return write_string_type(write_xlml(wb, o), o);
case 'slk':
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb), o);
case 'htm':
case 'html': return write_string_type(sheet_to_html(wb.Sheets[wb.SheetNames[idx]], o), o);
case 'txt': return write_stxt_type(sheet_to_txt(wb.Sheets[wb.SheetNames[idx]], o), o);

@ -45,6 +45,7 @@ can be installed with Bash on Windows or with `cygwin`.
- [`Chrome / Chromium extensions`](chrome/)
- [`Google Sheets API`](https://docs.sheetjs.com/docs/getting-started/demos/gsheet)
- [`Adobe Apps`](https://docs.sheetjs.com/docs/getting-started/demos/extendscript)
- [`Excel JavaScript API`](https://docs.sheetjs.com/docs/getting-started/demos/excel)
- [`Headless Browsers`](headless/)
- [`canvas-datagrid`](datagrid/)
- [`x-spreadsheet`](xspreadsheet/)

5
misc/import_map.json Normal file

@ -0,0 +1,5 @@
{
"imports": {
"jsdom": "https://esm.sh/jsdom"
}
}

11
misc/test.deno.jsonc Normal file

@ -0,0 +1,11 @@
{
"compilerOptions": {
"lib": [
"deno.ns",
"dom",
"dom.iterable",
"dom.asynciterable"
]
},
"importMap": "./import_map.json"
}

473
test.js

File diff suppressed because it is too large Load Diff

336
test.mjs generated

@ -342,7 +342,7 @@ function parsetest(x/*:string*/, wb/*:Workbook*/, full/*:boolean*/, ext/*:?strin
var wbtable = {};
(browser ? describe.skip : describe)('should parse test files', function() {
if(!browser) describe('should parse test files', function() {
files.forEach(function(x) {
if(x.slice(-8) == ".pending" || !fs.existsSync(dir + x)) return;
it(x, function() {
@ -383,13 +383,13 @@ function each_sheet(wb, f) { wb.SheetNames.forEach(function(n, i) { f(wb.Sheets[
/* comments_stress_test family */
function check_comments(wb) {
var ws0 = wb.Sheets.Sheet2;
var ws0 = wb.Sheets["Sheet2"];
assert.equal(get_cell(ws0,"A1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"A1").c[0].t, 'Author:\nGod thinks this is good');
assert.equal(get_cell(ws0,"C1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"C1").c[0].t, 'I really hope that xlsx decides not to use magic like rPr');
var ws3 = wb.Sheets.Sheet4;
var ws3 = wb.Sheets["Sheet4"];
assert.equal(get_cell(ws3,"B1").c[0].a, 'Author');
assert.equal(get_cell(ws3,"B1").c[0].t, 'The next comment is empty');
assert.equal(get_cell(ws3,"B2").c[0].a, 'Author');
@ -401,14 +401,14 @@ describe('parse options', function() {
describe('cell', function() {
it('XLSX should generate HTML by default', function() {
var wb = X.read(fs.readFileSync(paths.cstxlsx), {type:TYPE});
var ws = wb.Sheets.Sheet1;
var ws = wb.Sheets["Sheet1"];
each_cell(ws, function(cell) {
assert.ok(html_cell_types.indexOf(cell.t) === -1 || cell.h);
});
});
it('XLSX should not generate HTML when requested', function() {
var wb = X.read(fs.readFileSync(paths.cstxlsx), {type:TYPE, cellHTML:false});
var ws = wb.Sheets.Sheet1;
var ws = wb.Sheets["Sheet1"];
each_cell(ws, function(cell) {
assert.ok(typeof cell.h === 'undefined');
});
@ -483,7 +483,7 @@ describe('parse options', function() {
});
it('should not generate cell styles by default', function() {
CSSPaths.forEach(function(p) {
var wb = X.read(fs.readFileSync(p), {type:TYPE, WTF:1});
var wb = X.read(fs.readFileSync(p), {type:TYPE, WTF:true});
wb.SheetNames.forEach(function(s) {
var ws = wb.Sheets[s];
each_cell(ws, function(cell) {
@ -513,7 +513,7 @@ describe('parse options', function() {
});
it('should generate cell dates when requested', function() {
DTPaths.forEach(function(p) {
var wb = X.read(fs.readFileSync(p), {type:TYPE, cellDates: true, WTF:1});
var wb = X.read(fs.readFileSync(p), {type:TYPE, cellDates: true, WTF:true});
var found = false;
each_sheet(wb, function(ws/*::, i*/) { /*:: void i; */each_cell(ws, function(cell) {
if(cell.t === 'd') return (found = true);
@ -529,9 +529,9 @@ describe('parse options', function() {
var str = X.write(wb, {bookType: "xlsx", type: "binary"});
var wb2 = X.read(str, {type: "binary"});
/*jshint -W069 */
assert.equal(wb2.Sheets.Sheet1["A1"].f, "IFS(2>3,1,3>2,2)");
assert.equal(wb2.Sheets["Sheet1"]["A1"].f, "IFS(2>3,1,3>2,2)");
var wb3 = X.read(str, {type: "binary", xlfn: true});
assert.equal(wb3.Sheets.Sheet1["A1"].f, "_xlfn.IFS(2>3,1,3>2,2)");
assert.equal(wb3.Sheets["Sheet1"]["A1"].f, "_xlfn.IFS(2>3,1,3>2,2)");
/*jshint +W069 */
});
});
@ -539,27 +539,27 @@ describe('parse options', function() {
it('should not generate sheet stubs by default', function() {
MCPaths.forEach(function(p) {
var wb = X.read(fs.readFileSync(p), {type:TYPE});
assert.throws(function() { return get_cell(wb.Sheets.Merge, "A2").v; });
assert.throws(function() { return get_cell(wb.Sheets["Merge"], "A2").v; });
});
});
it('should generate sheet stubs when requested', function() {
MCPaths.forEach(function(p) {
var wb = X.read(fs.readFileSync(p), {type:TYPE, sheetStubs:true});
assert.ok(get_cell(wb.Sheets.Merge, "A2").t == 'z');
assert.ok(get_cell(wb.Sheets["Merge"], "A2").t == 'z');
});
});
it('should handle stub cells', function() {
MCPaths.forEach(function(p) {
var wb = X.read(fs.readFileSync(p), {type:TYPE, sheetStubs:true});
X.utils.sheet_to_csv(wb.Sheets.Merge);
X.utils.sheet_to_json(wb.Sheets.Merge);
X.utils.sheet_to_formulae(wb.Sheets.Merge);
X.utils.sheet_to_csv(wb.Sheets["Merge"]);
X.utils.sheet_to_json(wb.Sheets["Merge"]);
X.utils.sheet_to_formulae(wb.Sheets["Merge"]);
ofmt.forEach(function(f) { if(f != "dbf") X.write(wb, {type:TYPE, bookType:f}); });
});
});
function checkcells(wb, A46, B26, C16, D2) {
[ ["A46", A46], ["B26", B26], ["C16", C16], ["D2", D2] ].forEach(function(r) {
assert.ok((typeof get_cell(wb.Sheets.Text, r[0]) !== 'undefined') == r[1]);
assert.ok((typeof get_cell(wb.Sheets["Text"], r[0]) !== 'undefined') == r[1]);
});
}
it('should read all cells by default', function() { FSTPaths.forEach(function(p) {
@ -581,35 +581,35 @@ describe('parse options', function() {
assert.ok(ws['!ref'] === "A1:B3");
var wb = X.utils.book_new();
X.utils.book_append_sheet(wb, ws, "Sheet1");
var bs = X.write(wb, { bookType: fmt, type: ot, WTF: 1 });
var bs = X.write(wb, { bookType: fmt, type: ot, WTF:true });
var wb0 = X.read(bs, { type: ot, WTF: 1 });
var ws0 = wb0.Sheets.Sheet1;
var wb0 = X.read(bs, { type: ot, WTF:true });
var ws0 = wb0.Sheets["Sheet1"];
assert.equal(ws0['!ref'], "A1:B3");
assert.equal(get_cell(ws0, "A1").v, 1);
assert.equal(get_cell(ws0, "A1").v, fmt == "dbf" ? "1" : 1);
assert.equal(get_cell(ws0, "B2").v, 4);
assert.equal(get_cell(ws0, "A3").v, 5);
var wb1 = X.read(bs, { type: ot, sheetRows: 1 });
var ws1 = wb1.Sheets.Sheet1;
var ws1 = wb1.Sheets["Sheet1"];
assert.equal(ws1['!ref'], "A1:B1");
assert.equal(get_cell(ws1, "A1").v, 1);
assert.equal(get_cell(ws1, "A1").v, fmt == "dbf" ? "1" : 1);
assert.ok(!get_cell(ws1, "B2"));
assert.ok(!get_cell(ws1, "A3"));
if(ws1['!fullref']) assert.equal(ws1['!fullref'], "A1:B3");
var wb2 = X.read(bs, { type: ot, sheetRows: 2 });
var ws2 = wb2.Sheets.Sheet1;
var ws2 = wb2.Sheets["Sheet1"];
assert.equal(ws2['!ref'], "A1:B2");
assert.equal(get_cell(ws2, "A1").v, 1);
assert.equal(get_cell(ws2, "A1").v, fmt == "dbf" ? "1" : 1);
assert.equal(get_cell(ws2, "B2").v, 4);
assert.ok(!get_cell(ws2, "A3"));
if(ws2['!fullref']) assert.equal(ws2['!fullref'], "A1:B3");
var wb3 = X.read(bs, { type: ot, sheetRows: 3 });
var ws3 = wb3.Sheets.Sheet1;
var ws3 = wb3.Sheets["Sheet1"];
assert.equal(ws3['!ref'], "A1:B3");
assert.equal(get_cell(ws3, "A1").v, 1);
assert.equal(get_cell(ws3, "A1").v, fmt == "dbf" ? "1" : 1);
assert.equal(get_cell(ws3, "B2").v, 4);
assert.equal(get_cell(ws3, "A3").v, 5);
if(ws3['!fullref']) assert.equal(ws3['!fullref'], "A1:B3");
@ -685,13 +685,13 @@ describe('input formats', function() {
it('should read base64 strings', function() { artifax.forEach(function(p) {
X.read(fs.readFileSync(p, 'base64'), {type: 'base64'});
}); });
(typeof Uint8Array !== 'undefined' ? it : it.skip)('should read array', function() { artifax.forEach(function(p) {
if(typeof Uint8Array !== 'undefined') it('should read array', function() { artifax.forEach(function(p) {
X.read(fs.readFileSync(p, 'binary').split("").map(function(x) { return x.charCodeAt(0); }), {type:'array'});
}); });
((browser || typeof Buffer === 'undefined') ? it.skip : it)('should read Buffers', function() { artifax.forEach(function(p) {
X.read(fs.readFileSync(p), {type: 'buffer'});
}); });
(typeof Uint8Array !== 'undefined' ? it : it.skip)('should read ArrayBuffer / Uint8Array', function() { artifax.forEach(function(p) {
if(typeof Uint8Array !== 'undefined') it('should read ArrayBuffer / Uint8Array', function() { artifax.forEach(function(p) {
var payload = fs.readFileSync(p, browser ? 'buffer' : null);
var ab = new ArrayBuffer(payload.length), vu = new Uint8Array(ab);
for(var i = 0; i < payload.length; ++i) vu[i] = payload[i];
@ -731,9 +731,9 @@ describe('output formats', function() {
fmts.forEach(function(fmt) {
var wb = X.utils.book_new();
X.utils.book_append_sheet(wb, X.utils.aoa_to_sheet([['R',"\u2603"],["\u0BEE",2]]), "Sheet1");
if(T == 'string' && !fmt[2]) return assert.throws(function() {X.write(wb, {type: T, bookType:fmt[0], WTF:1});});
var out = X.write(wb, {type: T, bookType:fmt[0], WTF:1});
var nwb = X.read(out, {type: T, PRN: fmt[0] == 'prn', WTF:1});
if(T == 'string' && !fmt[2]) return assert.throws(function() {X.write(wb, {type: T, bookType:fmt[0], WTF:true});});
var out = X.write(wb, {type: T, bookType:fmt[0], WTF:true});
var nwb = X.read(out, {type: T, PRN: fmt[0] == 'prn', WTF:true});
var nws = nwb.Sheets[nwb.SheetNames[0]];
assert.equal(get_cell(nws, "B2").v, 2);
assert.equal(get_cell(nws, "A1").v, "R");
@ -930,7 +930,7 @@ describe('parse features', function() {
].forEach(function(m) { it(m[0] + ' stress test', function() {
var wb = X.read(fs.readFileSync(m[1]), {type:TYPE});
check_comments(wb);
var ws0 = wb.Sheets.Sheet2;
var ws0 = wb.Sheets["Sheet2"];
assert.equal(get_cell(ws0,"A1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"A1").c[0].t, 'Author:\nGod thinks this is good');
assert.equal(get_cell(ws0,"C1").c[0].a, 'Author');
@ -942,10 +942,10 @@ describe('parse features', function() {
var wbs=[];
var bef = (function() {
wbs = [
X.read(fs.readFileSync(paths.cpxlsx), {type:TYPE, WTF:1}),
X.read(fs.readFileSync(paths.cpxlsb), {type:TYPE, WTF:1}),
X.read(fs.readFileSync(paths.cpxls), {type:TYPE, WTF:1}),
X.read(fs.readFileSync(paths.cpxml), {type:TYPE, WTF:1})
X.read(fs.readFileSync(paths.cpxlsx), {type:TYPE, WTF:true}),
X.read(fs.readFileSync(paths.cpxlsb), {type:TYPE, WTF:true}),
X.read(fs.readFileSync(paths.cpxls), {type:TYPE, WTF:true}),
X.read(fs.readFileSync(paths.cpxml), {type:TYPE, WTF:true})
];
});
if(typeof before != 'undefined') before(bef);
@ -984,7 +984,7 @@ describe('parse features', function() {
it('should use original range if not set', function() {
var opts = {type:TYPE};
FSTPaths.map(function(p) { return X.read(fs.readFileSync(p), opts); }).forEach(function(wb) {
assert.equal(wb.Sheets.Text["!ref"],"A1:F49");
assert.equal(wb.Sheets["Text"]["!ref"],"A1:F49");
});
});
it('should adjust range if set', function() {
@ -992,8 +992,8 @@ describe('parse features', function() {
var wbs = FSTPaths.map(function(p) { return X.read(fs.readFileSync(p), opts); });
/* TODO: XLS, XML, ODS */
wbs.slice(0,2).forEach(function(wb) {
assert.equal(wb.Sheets.Text["!fullref"],"A1:F49");
assert.equal(wb.Sheets.Text["!ref"],"A1:F10");
assert.equal(wb.Sheets["Text"]["!fullref"],"A1:F49");
assert.equal(wb.Sheets["Text"]["!ref"],"A1:F10");
});
});
it('should not generate comment cells', function() {
@ -1001,8 +1001,8 @@ describe('parse features', function() {
var wbs = CSTPaths.map(function(p) { return X.read(fs.readFileSync(p), opts); });
/* TODO: XLS, XML, ODS */
wbs.slice(0,2).forEach(function(wb) {
assert.equal(wb.Sheets.Sheet7["!fullref"],"A1:N34");
assert.equal(wb.Sheets.Sheet7["!ref"],"A1");
assert.equal(wb.Sheets["Sheet7"]["!fullref"],"A1:N34");
assert.equal(wb.Sheets["Sheet7"]["!ref"],"A1");
});
});
});
@ -1016,16 +1016,16 @@ describe('parse features', function() {
if(typeof before != 'undefined') before(bef);
else it('before', bef);
it('should have "!cols"', function() {
wbs.forEach(function(wb) { assert.ok(wb.Sheets.Sheet1['!cols']); });
wbs.forEach(function(wb) { assert.ok(wb.Sheets["Sheet1"]['!cols']); });
});
it('should have correct widths', function() {
/* SYLK rounds wch so skip non-integral */
wbs_no_slk.map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) {
wbs_no_slk.map(function(x) { return x.Sheets["Sheet1"]['!cols']; }).forEach(function(x) {
assert.equal(x[1].width, 0.1640625);
assert.equal(x[2].width, 16.6640625);
assert.equal(x[3].width, 1.6640625);
});
wbs.map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) {
wbs.map(function(x) { return x.Sheets["Sheet1"]['!cols']; }).forEach(function(x) {
assert.equal(x[4].width, 4.83203125);
assert.equal(x[5].width, 8.83203125);
assert.equal(x[6].width, 12.83203125);
@ -1034,12 +1034,12 @@ describe('parse features', function() {
});
it('should have correct pixels', function() {
/* SYLK rounds wch so skip non-integral */
wbs_no_slk.map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) {
wbs_no_slk.map(function(x) { return x.Sheets["Sheet1"]['!cols']; }).forEach(function(x) {
assert.equal(x[1].wpx, 1);
assert.equal(x[2].wpx, 100);
assert.equal(x[3].wpx, 10);
});
wbs.map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) {
wbs.map(function(x) { return x.Sheets["Sheet1"]['!cols']; }).forEach(function(x) {
assert.equal(x[4].wpx, 29);
assert.equal(x[5].wpx, 53);
assert.equal(x[6].wpx, 77);
@ -1060,25 +1060,25 @@ describe('parse features', function() {
if(typeof before != 'undefined') before(bef);
else it('before', bef);
it('should have "!rows"', function() {
wbs.forEach(function(wb) { assert.ok(wb.Sheets.Sheet1['!rows']); });
wbs.forEach(function(wb) { assert.ok(wb.Sheets["Sheet1"]['!rows']); });
});
it('should have correct points', function() {
wbs.map(function(x) { return x.Sheets.Sheet1['!rows']; }).forEach(function(x) {
wbs.map(function(x) { return x.Sheets["Sheet1"]['!rows']; }).forEach(function(x) {
assert.equal(x[1].hpt, 1);
assert.equal(x[2].hpt, 10);
assert.equal(x[3].hpt, 100);
});
});
it('should have correct pixels', function() {
wbs.map(function(x) { return x.Sheets.Sheet1['!rows']; }).forEach(function(x) {
wbs.map(function(x) { return x.Sheets["Sheet1"]['!rows']; }).forEach(function(x) {
/* note: at 96 PPI hpt == hpx */
assert.equal(x[1].hpx, 1);
assert.equal(x[2].hpx, 10);
assert.equal(x[3].hpx, 100);
});
});
(ol ? it : it.skip)('should have correct outline levels', function() {
ols.map(function(x) { return x.Sheets.Sheet1; }).forEach(function(ws) {
it('should have correct outline levels', function() {
ols.map(function(x) { return x.Sheets["Sheet1"]; }).forEach(function(ws) {
var rows = ws['!rows'];
for(var i = 0; i < 29; ++i) {
var cell = get_cell(ws, "A" + X.utils.encode_row(i));
@ -1103,9 +1103,9 @@ describe('parse features', function() {
else it('before', bef);
it('should have !merges', function() {
wbs.forEach(function(wb) {
assert.ok(wb.Sheets.Merge['!merges']);
assert.ok(wb.Sheets["Merge"]['!merges']);
});
var m = wbs.map(function(x) { return x.Sheets.Merge['!merges'].map(function(y) { return X.utils.encode_range(y); });});
var m = wbs.map(function(x) { return x.Sheets["Merge"]['!merges'].map(function(y) { return X.utils.encode_range(y); });});
m.slice(1).forEach(function(x) {
assert.deepEqual(m[0].sort(),x.sort());
});
@ -1115,17 +1115,17 @@ describe('parse features', function() {
describe('should find hyperlinks', function() {
var wb1, wb2;
var bef = (function() {
wb1 = HLPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE, WTF:1}); });
wb2 = ILPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE, WTF:1}); });
wb1 = HLPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE, WTF:true}); });
wb2 = ILPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE, WTF:true}); });
});
if(typeof before != 'undefined') before(bef);
else it('before', bef);
['xlsx', 'xlsb', 'xls', 'xml'].forEach(function(x, i) {
it(x + " external", function() { hlink1(wb1[i].Sheets.Sheet1); });
it(x + " external", function() { hlink1(wb1[i].Sheets["Sheet1"]); });
});
['xlsx', 'xlsb', 'xls', 'xml', 'ods'].forEach(function(x, i) {
it(x + " internal", function() { hlink2(wb2[i].Sheets.Sheet1); });
it(x + " internal", function() { hlink2(wb2[i].Sheets["Sheet1"]); });
});
});
@ -1275,10 +1275,10 @@ describe('parse features', function() {
else it('before', bef);
['xlsx'].forEach(function(m) { it(m, function() {
var wb2 = X.read(X.write(wb, {bookType:m, type:TYPE}),{type:TYPE, cellHTML:true});
assert.equal(get_cell(wb2.Sheets.Sheet1, "A2").h, "&amp;");
assert.equal(get_cell(wb2.Sheets.Sheet1, "B2").h, "&lt;");
assert.equal(get_cell(wb2.Sheets.Sheet1, "C2").h, "&gt;");
var h = get_cell(wb2.Sheets.Sheet1, "D2").h;
assert.equal(get_cell(wb2.Sheets["Sheet1"], "A2").h, "&amp;");
assert.equal(get_cell(wb2.Sheets["Sheet1"], "B2").h, "&lt;");
assert.equal(get_cell(wb2.Sheets["Sheet1"], "C2").h, "&gt;");
var h = get_cell(wb2.Sheets["Sheet1"], "D2").h;
assert.ok(h == "&#x000a;" || h == "<br/>");
}); });
});
@ -1287,7 +1287,7 @@ describe('parse features', function() {
var wbs=[];
var bef = (function() {
if(!fs.existsSync(paths.pmxls)) return;
wbs = PMPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE, WTF:1}); });
wbs = PMPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE, WTF:true}); });
});
if(typeof before != 'undefined') before(bef);
else it('before', bef);
@ -1306,8 +1306,8 @@ describe('parse features', function() {
describe('should correctly handle styles', function() {
var wsxls, wsxlsx, rn, rn2;
var bef = (function() {
wsxls=X.read(fs.readFileSync(paths.cssxls), {type:TYPE,cellStyles:true,WTF:1}).Sheets.Sheet1;
wsxlsx=X.read(fs.readFileSync(paths.cssxlsx), {type:TYPE,cellStyles:true,WTF:1}).Sheets.Sheet1;
wsxls=X.read(fs.readFileSync(paths.cssxls), {type:TYPE,cellStyles:true,WTF:true}).Sheets["Sheet1"];
wsxlsx=X.read(fs.readFileSync(paths.cssxlsx), {type:TYPE,cellStyles:true,WTF:true}).Sheets["Sheet1"];
rn = function(range) {
var r = X.utils.decode_range(range);
var out = [];
@ -1395,12 +1395,36 @@ describe('parse features', function() {
assert.equal(data[4][1], '456.00');
assert.equal(data[5][1], '7,890');
}); }); });
it('date system', function() {[
"biff5", "ods", "slk", "xls", "xlsb", "xlsx", "xml"
].forEach(function(ext) {
// TODO: verify actual date values
var wb0 = X.read(fs.readFileSync("./test_files/1904/1900." + ext), {type: TYPE, cellNF: true});
assert.ok(!wb0.Workbook || !wb0.Workbook.WBProps || !wb0.Workbook.WBProps.date1904);
assert.equal(X.utils.sheet_to_csv(wb0.Sheets[wb0.SheetNames[0]]), [
"1,1900-01-01,1900-01-01,1900-01-01",
"11,1900-01-11,1900-01-11,1900-01-11",
"111,1900-04-20,1900-04-20,1900-04-20",
"1111,1903-01-15,1903-01-15,1903-01-15",
"11111,1930-06-02,1930-06-02,1930-06-02"
].join("\n"));
var wb4 = X.read(fs.readFileSync("./test_files/1904/1904." + ext), {type: TYPE, cellNF: true});
assert.ok(wb4.Workbook.WBProps.date1904);
assert.equal(X.utils.sheet_to_csv(wb4.Sheets[wb4.SheetNames[0]]), [
"1,1904-01-02,1904-01-02,1904-01-02",
"11,1904-01-12,1904-01-12,1904-01-12",
"111,1904-04-21,1904-04-21,1904-04-21",
"1111,1907-01-16,1907-01-16,1907-01-16",
"11111,1934-06-03,1934-06-03,1934-06-03"
].join("\n"));
}); });
});
describe('write features', function() {
describe('props', function() {
describe('core', function() {
var ws;
var baseprops = {
Category: "Newspaper",
ContentStatus: "Published",
@ -1416,6 +1440,7 @@ describe('write features', function() {
Subject: "Superman",
Title: "Man of Steel"
};
var ws;
var bef = (function() {
ws = X.utils.aoa_to_sheet([["a","b","c"],[1,2,3]]);
});
@ -1452,17 +1477,17 @@ describe('write features', function() {
["xlsb", "XFD1048576"]
].forEach(function(r) { it(r[0], function() {
var C = X.utils.decode_cell(r[1]);
var wopts = {bookType:r[0], type:'binary', WTF:1};
var wopts = {bookType:r[0], type:'binary', WTF:true};
var wb = { SheetNames: ["Sheet1"], Sheets: { Sheet1: {} } };
wb.Sheets.Sheet1['!ref'] = "A1:" + X.utils.encode_cell({r:0, c:C.c});
wb.Sheets["Sheet1"]['!ref'] = "A1:" + X.utils.encode_cell({r:0, c:C.c});
X.write(wb, wopts);
wb.Sheets.Sheet1['!ref'] = "A" + X.utils.encode_row(C.r - 5) + ":" + X.utils.encode_cell({r:C.r, c:0});
wb.Sheets["Sheet1"]['!ref'] = "A" + X.utils.encode_row(C.r - 5) + ":" + X.utils.encode_cell({r:C.r, c:0});
X.write(wb, wopts);
wb.Sheets.Sheet1['!ref'] = "A1:" + X.utils.encode_cell({r:0, c:C.c+1});
wb.Sheets["Sheet1"]['!ref'] = "A1:" + X.utils.encode_cell({r:0, c:C.c+1});
assert.throws(function() { X.write(wb, wopts); });
wb.Sheets.Sheet1['!ref'] = "A" + X.utils.encode_row(C.r - 5) + ":" + X.utils.encode_cell({r:C.r+1, c:0});
wb.Sheets["Sheet1"]['!ref'] = "A" + X.utils.encode_row(C.r - 5) + ":" + X.utils.encode_cell({r:C.r+1, c:0});
assert.throws(function() { X.write(wb, wopts); });
}); }); });
it('single worksheet formats', function() {
@ -1485,8 +1510,8 @@ describe('write features', function() {
((wb2.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert.ok(!!Name, "Could not find _xlnm._FilterDatabases name WR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$2");
X.utils.sheet_add_aoa(wb2.Sheets.Sheet1, [[4,5,6]], { origin: -1 });
wb2.Sheets.Sheet1["!autofilter"].ref = wb2.Sheets.Sheet1["!ref"];
X.utils.sheet_add_aoa(wb2.Sheets["Sheet1"], [[4,5,6]], { origin: -1 });
wb2.Sheets["Sheet1"]["!autofilter"].ref = wb2.Sheets["Sheet1"]["!ref"];
var wb3 = X.read(X.write(wb2, {bookType:fmt, type:TYPE}), {type:TYPE});
Name = void 0;
((wb3.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
@ -1562,8 +1587,8 @@ describe('roundtrip features', function() {
["xlsx", "xlsb", "xlml", "ods", "biff8", "numbers"].forEach(function(f) { it(f, function() {
var wb1 = X.read(fs.readFileSync(paths.mcxlsx), {type:TYPE});
var wb2 = X.read(X.write(wb1,{bookType:f,type:'binary',numbers:XLSX_ZAHL}),{type:'binary'});
var m1 = wb1.Sheets.Merge['!merges'].map(X.utils.encode_range);
var m2 = wb2.Sheets.Merge['!merges'].map(X.utils.encode_range);
var m1 = wb1.Sheets["Merge"]['!merges'].map(X.utils.encode_range);
var m2 = wb2.Sheets["Merge"]['!merges'].map(X.utils.encode_range);
assert.equal(m1.length, m2.length);
for(var i = 0; i < m1.length; ++i) assert.ok(m1.indexOf(m2[i]) > -1);
}); });
@ -1595,13 +1620,13 @@ describe('roundtrip features', function() {
});
});
describe('should preserve formulae', function() { [
describe('should preserve formulae', function() { var ff = [
['xlml', paths.fstxml],
['xlsx', paths.fstxlsx],
['ods', paths.fstods]
].forEach(function(w) { it(w[0], function() {
var wb1 = X.read(fs.readFileSync(w[1]), {type:TYPE, cellFormula:true, WTF:1});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:TYPE}), {cellFormula:true, type:TYPE, WTF:1});
]; ff.forEach(function(w) { it(w[0], function() {
var wb1 = X.read(fs.readFileSync(w[1]), {type:TYPE, cellFormula:true, WTF:true});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:TYPE}), {cellFormula:true, type:TYPE, WTF:true});
wb1.SheetNames.forEach(function(n) {
assert.equal(
X.utils.sheet_to_formulae(wb1.Sheets[n]).sort().join("\n"),
@ -1613,17 +1638,17 @@ describe('roundtrip features', function() {
describe('should preserve dynamic array formulae', function() { [
['xlsx', paths.m19xlsx]
].forEach(function(w) { it(w[0], function() {
var wb1 = X.read(fs.readFileSync(w[1]), {xlfn: true, type:TYPE, cellFormula:true, WTF:1});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:TYPE}), {cellFormula:true, xlfn: true, type:TYPE, WTF:1});
assert.equal(!!get_cell(wb2.Sheets.Sheet1, "B3").D, true);
assert.equal(!!get_cell(wb2.Sheets.Sheet1, "B13").D, true);
assert.equal(!!get_cell(wb2.Sheets.Sheet1, "C13").D, true);
var wb1 = X.read(fs.readFileSync(w[1]), {xlfn: true, type:TYPE, cellFormula:true, WTF:true});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:TYPE}), {cellFormula:true, xlfn: true, type:TYPE, WTF:true});
assert.equal(!!get_cell(wb2.Sheets["Sheet1"], "B3").D, true);
assert.equal(!!get_cell(wb2.Sheets["Sheet1"], "B13").D, true);
assert.equal(!!get_cell(wb2.Sheets["Sheet1"], "C13").D, true);
get_cell(wb2.Sheets.Sheet1, "B3").D = false;
var wb3 = X.read(X.write(wb2, {bookType:w[0], type:TYPE}), {cellFormula:true, xlfn: true, type:TYPE, WTF:1});
assert.equal(!!get_cell(wb3.Sheets.Sheet1, "B3").D, false);
assert.equal(!!get_cell(wb3.Sheets.Sheet1, "B13").D, true);
assert.equal(!!get_cell(wb3.Sheets.Sheet1, "C13").D, true);
get_cell(wb2.Sheets["Sheet1"], "B3").D = false;
var wb3 = X.read(X.write(wb2, {bookType:w[0], type:TYPE}), {cellFormula:true, xlfn: true, type:TYPE, WTF:true});
assert.equal(!!get_cell(wb3.Sheets["Sheet1"], "B3").D, false);
assert.equal(!!get_cell(wb3.Sheets["Sheet1"], "B13").D, true);
assert.equal(!!get_cell(wb3.Sheets["Sheet1"], "C13").D, true);
}); }); });
describe('should preserve hyperlink', function() { [
@ -1638,12 +1663,12 @@ describe('roundtrip features', function() {
['ods', paths.ilods, false]
].forEach(function(w) { it(w[0]+" "+(w[2]?"ex":"in")+ "ternal", function() {
var wb = X.read(fs.readFileSync(w[1]), {type:TYPE, WTF:opts.WTF});
var hlink = (w[2] ? hlink1 : hlink2); hlink(wb.Sheets.Sheet1);
var hlink = (w[2] ? hlink1 : hlink2); hlink(wb.Sheets["Sheet1"]);
wb = X.read(X.write(wb, {bookType:w[0], type:TYPE, WTF:opts.WTF}), {type:TYPE, WTF:opts.WTF});
hlink(wb.Sheets.Sheet1);
hlink(wb.Sheets["Sheet1"]);
}); }); });
(fs.existsSync(paths.pmxlsx) ? describe : describe.skip)('should preserve page margins', function() {[
describe('should preserve page margins', function() {[
['xlml', paths.pmxml],
['xlsx', paths.pmxlsx],
['xlsb', paths.pmxlsb]
@ -1691,7 +1716,7 @@ describe('roundtrip features', function() {
ws1['!cols'] = [{wch:9},{wpx:100},{width:80},{hidden:true}];
var wb1 = {SheetNames:["Sheet1"], Sheets:{Sheet1:ws1}};
var wb2 = X.read(X.write(wb1, {bookType:w, type:TYPE}), {type:TYPE, cellStyles:true});
var ws2 = wb2.Sheets.Sheet1;
var ws2 = wb2.Sheets["Sheet1"];
assert.equal(ws2['!cols'][3].hidden, true);
assert.equal(ws2['!cols'][0].wch, 9);
if(w == 'slk') return;
@ -1711,7 +1736,7 @@ describe('roundtrip features', function() {
for(var i = 0; i <= 7; ++i) ws1['!rows'].push({level:i});
var wb1 = {SheetNames:["Sheet1"], Sheets:{Sheet1:ws1}};
var wb2 = X.read(X.write(wb1, {bookType:w, type:TYPE, cellStyles:true}), {type:TYPE, cellStyles:true});
var ws2 = wb2.Sheets.Sheet1;
var ws2 = wb2.Sheets["Sheet1"];
assert.equal(ws2['!rows'][0].hpx, 12);
assert.equal(ws2['!rows'][1].hpt, 24);
assert.equal(ws2['!rows'][2].hpx, 48);
@ -1767,6 +1792,7 @@ describe('roundtrip features', function() {
assert.equal(wb2.Sheets[wb2.SheetNames[i]]['!autofilter'].ref,"A1:E22");
}
}); });
});
//function password_file(x){return x.match(/^password.*\.xls$/); }
@ -2002,7 +2028,7 @@ describe('json output', function() {
seq(8).forEach(function(n) {
var opts = {};
if(n & 1) opts.header = 1;
if(n & 2) opts.raw = 1;
if(n & 2) opts.raw = true;
if(n & 4) opts.defval = null;
var J = X.utils.sheet_to_json(ws, opts);
for(var i = 0; i < 3; ++i) {
@ -2015,7 +2041,7 @@ describe('json output', function() {
var codes = [["あ 1", "\u00E3\u0081\u0082 1"]];
var plaintext_val = [
var plaintext_val = ([
["A1", 'n', -0.08, "-0.08"],
["B1", 'n', 4001, "4,001"],
["C1", 's', "あ 1", "あ 1"],
@ -2025,12 +2051,12 @@ var plaintext_val = [
["D3", 'b', false, "FALSE"],
["B3", 's', " ", " "],
["A3"]
];
]);
function plaintext_test(wb, raw) {
var sheet = wb.Sheets[wb.SheetNames[0]];
plaintext_val.forEach(function(x) {
var cell = get_cell(sheet, x[0]);
var tcval = x[2+!!raw];
var tcval = x[2+(!!raw ? 1 : 0)];
var type = raw ? 's' : x[1];
if(x.length == 1) { if(cell) { assert.equal(cell.t, 'z'); assert.ok(!cell.v); } return; }
assert.equal(cell.v, tcval); assert.equal(cell.t, type);
@ -2055,14 +2081,14 @@ describe('CSV', function() {
var b = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,2/19/14,0.3\n,,,\nbaz,,qux,\n";
it('should generate date numbers by default', function() {
var opts = {type:"binary"};
var cell = get_cell(X.read(b, opts).Sheets.Sheet1, "C3");
var cell = get_cell(X.read(b, opts).Sheets["Sheet1"], "C3");
assert.equal(cell.w, '2/19/14');
assert.equal(cell.t, 'n');
assert.ok(typeof cell.v == "number");
});
it('should generate dates when requested', function() {
var opts = {type:"binary", cellDates:true};
var cell = get_cell(X.read(b, opts).Sheets.Sheet1, "C3");
var cell = get_cell(X.read(b, opts).Sheets["Sheet1"], "C3");
assert.equal(cell.w, '2/19/14');
assert.equal(cell.t, 'd');
assert.ok(cell.v instanceof Date || typeof cell.v == "string");
@ -2070,29 +2096,29 @@ describe('CSV', function() {
it('should use US date code 14 by default', function() {
var opts = ({type:"binary"}/*:any*/);
var cell = get_cell(X.read(b, opts).Sheets.Sheet1, "C3");
var cell = get_cell(X.read(b, opts).Sheets["Sheet1"], "C3");
assert.equal(cell.w, '2/19/14');
opts.cellDates = true;
cell = get_cell(X.read(b, opts).Sheets.Sheet1, "C3");
cell = get_cell(X.read(b, opts).Sheets["Sheet1"], "C3");
assert.equal(cell.w, '2/19/14');
});
it('should honor dateNF override', function() {
var opts = ({type:"binary", dateNF:"YYYY-MM-DD"}/*:any*/);
var cell = get_cell(X.read(b, opts).Sheets.Sheet1, "C3");
var cell = get_cell(X.read(b, opts).Sheets["Sheet1"], "C3");
/* NOTE: IE interprets 2-digit years as 19xx */
assert.ok(cell.w == '2014-02-19' || cell.w == '1914-02-19');
opts.cellDates = true; opts.dateNF = "YY-MM-DD";
cell = get_cell(X.read(b, opts).Sheets.Sheet1, "C3");
cell = get_cell(X.read(b, opts).Sheets["Sheet1"], "C3");
assert.equal(cell.w, '14-02-19');
});
it('should interpret dateNF', function() {
var bb = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,2/3/14,0.3\n,,,\nbaz,,qux,\n";
var opts = {type:"binary", cellDates:true, dateNF:'m/d/yy'};
var cell = get_cell(X.read(bb, opts).Sheets.Sheet1, "C3");
var cell = get_cell(X.read(bb, opts).Sheets["Sheet1"], "C3");
assert.equal(cell.v.getMonth(), 1);
assert.equal(cell.w, "2/3/14");
opts = {type:"binary", cellDates:true, dateNF:'d/m/yy'};
cell = get_cell(X.read(bb, opts).Sheets.Sheet1, "C3");
cell = get_cell(X.read(bb, opts).Sheets["Sheet1"], "C3");
assert.equal(cell.v.getMonth(), 2);
assert.equal(cell.w, "2/3/14");
});
@ -2100,7 +2126,7 @@ describe('CSV', function() {
it('should generate strings if raw option is passed', function() { plaintext_test(X.read(csv_str, {type:"string", raw:true}), true); });
it('should handle formulae', function() {
var bb = '=,=1+1,="100"';
var sheet = X.read(bb, {type:"binary"}).Sheets.Sheet1;
var sheet = X.read(bb, {type:"binary"}).Sheets["Sheet1"];
assert.equal(get_cell(sheet, "A1").t, 's');
assert.equal(get_cell(sheet, "A1").v, '=');
assert.equal(get_cell(sheet, "B1").f, '1+1');
@ -2109,12 +2135,12 @@ describe('CSV', function() {
});
it('should interpret CRLF newlines', function() {
var wb = X.read("sep=&\r\n1&2&3\r\n4&5&6", {type: "string"});
assert.equal(wb.Sheets.Sheet1["!ref"], "A1:C2");
assert.equal(wb.Sheets["Sheet1"]["!ref"], "A1:C2");
});
if(!browser || typeof cptable !== 'undefined') it('should honor codepage for binary strings', function() {
var data = "abc,def\nghi,j\xD3l";
[[1251, 'У'],[1252, 'Ó'], [1253, 'Σ'], [1254, 'Ó'], [1255, '׃'], [1256, 'س'], [10000, '”']].forEach(function(m) {
var ws = X.read(data, {type:"binary", codepage:m[0]}).Sheets.Sheet1;
var ws = X.read(data, {type:"binary", codepage:m[0]}).Sheets["Sheet1"];
assert.equal(get_cell(ws, "B2").v, "j" + m[1] + "l");
});
});
@ -2164,9 +2190,9 @@ describe('CSV', function() {
var data = ["1,a", "2,b", "3,c"];
[ "\r", "\n", "\r\n" ].forEach(function(RS) {
var wb = X.read(data.join(RS), {type:'binary'});
assert.equal(get_cell(wb.Sheets.Sheet1, "A1").v, 1);
assert.equal(get_cell(wb.Sheets.Sheet1, "B3").v, "c");
assert.equal(wb.Sheets.Sheet1['!ref'], "A1:B3");
assert.equal(get_cell(wb.Sheets["Sheet1"], "A1").v, 1);
assert.equal(get_cell(wb.Sheets["Sheet1"], "B3").v, "c");
assert.equal(wb.Sheets["Sheet1"]['!ref'], "A1:B3");
});
});
it('should handle skipHidden for rows if requested', function() {
@ -2210,32 +2236,33 @@ describe('sylk', function() {
describe('input', function(){
it('codepage', cpavail ? function() {
var str = "ID;PWXL;N;E\r\nC;X1;Y1;K\"a b\"\r\nE", A1 = "a b";
assert.equal(get_cell(X.read(str, {type:"string"}).Sheets.Sheet1, "A1").v, A1);
assert.equal(get_cell(X.read(str.replace(//, "\x96"), {type:"binary", codepage:1252}).Sheets.Sheet1, "A1").v, A1);
assert.equal(get_cell(X.read(str, {type:"string"}).Sheets["Sheet1"], "A1").v, A1);
assert.equal(get_cell(X.read(str.replace(//, "\x96"), {type:"binary", codepage:1252}).Sheets["Sheet1"], "A1").v, A1);
if(typeof Buffer !== 'undefined' && !browser) {
assert.equal(get_cell(X.read(Buffer_from(str), {type:"buffer", codepage:65001}).Sheets.Sheet1, "A1").v, A1);
assert.equal(get_cell(X.read(Buffer_from(str.replace(//, "\x96"), "binary"), {type:"buffer", codepage:1252}).Sheets.Sheet1, "A1").v, A1);
assert.equal(get_cell(X.read(Buffer_from(str), {type:"buffer", codepage:65001}).Sheets["Sheet1"], "A1").v, A1);
assert.equal(get_cell(X.read(Buffer_from(str.replace(//, "\x96"), "binary"), {type:"buffer", codepage:1252}).Sheets["Sheet1"], "A1").v, A1);
}
} : null);
});
describe('date system', function() {
function make_slk(d1904) { return "ID;PSheetJS\nP;Pd\\/m\\/yy\nP;Pd\\/m\\/yyyy\n" + (d1904 != null ? "O;D;V" + d1904 : "") + "\nF;P0;FG0G;X1;Y1\nC;K1\nE"; }
it('should default to 1900', function() {
assert.equal(get_cell(X.read(make_slk(), {type: "binary"}).Sheets.Sheet1, "A1").v, 1);
assert.ok(get_cell(X.read(make_slk(), {type: "binary", cellDates: true}).Sheets.Sheet1, "A1").v.getFullYear() < 1902);
assert.equal(get_cell(X.read(make_slk(5), {type: "binary"}).Sheets.Sheet1, "A1").v, 1);
assert.ok(get_cell(X.read(make_slk(5), {type: "binary", cellDates: true}).Sheets.Sheet1, "A1").v.getFullYear() < 1902);
assert.equal(get_cell(X.read(make_slk(), {type: "binary"}).Sheets["Sheet1"], "A1").v, 1);
assert.ok(get_cell(X.read(make_slk(), {type: "binary", cellDates: true}).Sheets["Sheet1"], "A1").v.getFullYear() < 1902);
assert.equal(get_cell(X.read(make_slk(5), {type: "binary"}).Sheets["Sheet1"], "A1").v, 1);
assert.ok(get_cell(X.read(make_slk(5), {type: "binary", cellDates: true}).Sheets["Sheet1"], "A1").v.getFullYear() < 1902);
});
it('should use 1904 when specified', function() {
assert.ok(get_cell(X.read(make_slk(1), {type: "binary", cellDates: true}).Sheets.Sheet1, "A1").v.getFullYear() > 1902);
assert.ok(get_cell(X.read(make_slk(4), {type: "binary", cellDates: true}).Sheets.Sheet1, "A1").v.getFullYear() > 1902);
assert.ok(get_cell(X.read(make_slk(1), {type: "binary", cellDates: true}).Sheets["Sheet1"], "A1").v.getFullYear() > 1902);
assert.ok(get_cell(X.read(make_slk(4), {type: "binary", cellDates: true}).Sheets["Sheet1"], "A1").v.getFullYear() > 1902);
});
});
});
(typeof Uint8Array !== "undefined" ? describe : describe.skip)('numbers', function() {
if(typeof Uint8Array !== "undefined")
describe('numbers', function() {
it('should parse files from Numbers 6.x', function() {
var wb = X.read(fs.readFileSync(dir + 'numbers/types_61.numbers'), {type:TYPE, WTF:1});
var wb = X.read(fs.readFileSync(dir + 'numbers/types_61.numbers'), {type:TYPE, WTF:true});
var ws = wb.Sheets["Sheet 1"];
assert.equal(get_cell(ws, "A1").v, "Sheet");
assert.equal(get_cell(ws, "B1").v, "JS");
@ -2248,14 +2275,14 @@ describe('sylk', function() {
var ws1 = X.utils.aoa_to_sheet(aoa);
var wb1 = X.utils.book_new(); X.utils.book_append_sheet(wb1, ws1, "Sheet1");
var wb2 = X.read(X.write(wb1,{bookType:"numbers",type:'binary',numbers:XLSX_ZAHL}),{type:'binary'});
var ws2 = wb2.Sheets.Sheet1;
var ws2 = wb2.Sheets["Sheet1"];
assert.equal(ws2["!ref"], "A1:ALL3");
assert.equal(get_cell(ws2, "A1").v, 1);
assert.equal(get_cell(ws2, "ALL2").v, 2);
});
});
if(fs.existsSync(dir + 'dbf/d11.dbf')) describe('dbf', function() {
describe('dbf', function() {
var wbs/*:Array<any>*/ = ([
['d11', dir + 'dbf/d11.dbf'],
['vfp3', dir + 'dbf/vfp3.dbf']
@ -2266,7 +2293,7 @@ if(fs.existsSync(dir + 'dbf/d11.dbf')) describe('dbf', function() {
if(typeof before != 'undefined') before(bef);
else it('before', bef);
it(wbs[1][0], function() {
var ws = wbs[1][1].Sheets.Sheet1;
var ws = wbs[1][1].Sheets["Sheet1"];
[
["A1", "v", "CHAR10"], ["A2", "v", "test1"], ["B2", "v", 123.45],
["C2", "v", 12.345], ["D2", "v", 1234.1], ["E2", "w", "19170219"],
@ -2306,7 +2333,7 @@ describe('HTML', function() {
it('should handle newlines correctly', function() {
var table = "<table><tr><td>foo<br/>bar</td><td>baz</td></tr></table>";
var wb = X.read(table, {type:"string"});
assert.equal(get_cell(wb.Sheets.Sheet1, "A1").v, "foo\nbar");
assert.equal(get_cell(wb.Sheets["Sheet1"], "A1").v, "foo\nbar");
});
it('should generate multi-sheet workbooks', function() {
var table = "";
@ -2321,7 +2348,7 @@ describe('HTML', function() {
}
});
});
(domtest ? describe : describe.skip)('input DOM', function() {
if(domtest) describe('input DOM', function() {
it('should interpret values by default', function() { plaintext_test(X.utils.table_to_book(get_dom_element(html_str)), false); });
it('should generate strings if raw option is passed', function() { plaintext_test(X.utils.table_to_book(get_dom_element(html_str), {raw:true}), true); });
it('should handle newlines correctly', function() {
@ -2388,15 +2415,15 @@ describe('HTML', function() {
}
var html = "<table><tr><td t=\"s\">1234567890</td><td>1234567890</td></tr></table>";
it('HTML string', function() {
var ws = X.read(html, {type:'string'}).Sheets.Sheet1; chk(ws);
chk(X.read(X.utils.sheet_to_html(ws), {type:'string'}).Sheets.Sheet1);
var ws = X.read(html, {type:'string'}).Sheets["Sheet1"]; chk(ws);
chk(X.read(X.utils.sheet_to_html(ws), {type:'string'}).Sheets["Sheet1"]);
});
if(domtest) it('DOM', function() { chk(X.utils.table_to_sheet(get_dom_element(html))); });
});
describe('TH/THEAD/TBODY/TFOOT elements', function() {
var html = "<table><thead><tr><th>A</th><th>B</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></tbody><tfoot><tr><th>4</th><th>6</th></tr></tfoot></table>";
it('HTML string', function() {
var ws = X.read(html, {type:'string'}).Sheets.Sheet1;
var ws = X.read(html, {type:'string'}).Sheets["Sheet1"];
assert.equal(X.utils.sheet_to_csv(ws), "A,B\n1,2\n3,4\n4,6");
});
if(domtest) it('DOM', function() {
@ -2408,13 +2435,13 @@ describe('HTML', function() {
var html = "<table><tr><td>abc</td><td><b> </b></td><td>def</td></tr></table>";
var expectedCellCount = 3;
it('HTML string', function() {
var ws = X.read(html, {type:'string'}).Sheets.Sheet1;
var range = X.utils.decode_range(ws['!ref']);
var ws = X.read(html, {type:'string'}).Sheets["Sheet1"];
var range = X.utils.decode_range(ws['!ref']||"A1");
assert.equal(range.e.c,expectedCellCount - 1);
});
if(domtest) it('DOM', function() {
var ws = X.utils.table_to_sheet(get_dom_element(html));
var range = X.utils.decode_range(ws['!ref']);
var range = X.utils.decode_range(ws['!ref']||"A1");
assert.equal(range.e.c, expectedCellCount - 1);
});
});
@ -2529,22 +2556,22 @@ describe('corner cases', function() {
});
});
if(fs.existsSync(dir + 'wtf_path.xlsx')) it('OPC oddities', function() {
X.read(fs.readFileSync(dir + 'wtf_path.xlsx'), {WTF:1, type:TYPE});
X.read(fs.readFileSync(dir + 'wtf_path.xlsb'), {WTF:1, type:TYPE});
X.read(fs.readFileSync(dir + 'wtf_path.xlsx'), {WTF:true, type:TYPE});
X.read(fs.readFileSync(dir + 'wtf_path.xlsb'), {WTF:true, type:TYPE});
});
it("should quote unicode sheet names in formulae", function() {
var wb = X.read(fs.readFileSync(dir + "cross-sheet_formula_names.xlsb"), {WTF:1, type:TYPE});
assert.equal(wb.Sheets.Sheet1.A1.f, "'a-b'!A1");
assert.equal(wb.Sheets.Sheet1.A2.f, "'a#b'!A1");
assert.equal(wb.Sheets.Sheet1.A3.f, "'a^b'!A1");
assert.equal(wb.Sheets.Sheet1.A4.f, "'a%b'!A1");
assert.equal(wb.Sheets.Sheet1.A5.f, "'a\u066ab'!A1");
assert.equal(wb.Sheets.Sheet1.A6.f, "'☃️'!A1");
assert.equal(wb.Sheets.Sheet1.A7.f, "'\ud83c\udf63'!A1");
assert.equal(wb.Sheets.Sheet1.A8.f, "'a!!b'!A1");
assert.equal(wb.Sheets.Sheet1.A9.f, "'a$b'!A1");
assert.equal(wb.Sheets.Sheet1.A10.f, "'a!b'!A1");
assert.equal(wb.Sheets.Sheet1.A11.f, "'a b'!A1");
var wb = X.read(fs.readFileSync(dir + "cross-sheet_formula_names.xlsb"), {WTF:true, type:TYPE});
assert.equal(wb.Sheets["Sheet1"].A1.f, "'a-b'!A1");
assert.equal(wb.Sheets["Sheet1"].A2.f, "'a#b'!A1");
assert.equal(wb.Sheets["Sheet1"].A3.f, "'a^b'!A1");
assert.equal(wb.Sheets["Sheet1"].A4.f, "'a%b'!A1");
assert.equal(wb.Sheets["Sheet1"].A5.f, "'a\u066ab'!A1");
assert.equal(wb.Sheets["Sheet1"].A6.f, "'☃️'!A1");
assert.equal(wb.Sheets["Sheet1"].A7.f, "'\ud83c\udf63'!A1");
assert.equal(wb.Sheets["Sheet1"].A8.f, "'a!!b'!A1");
assert.equal(wb.Sheets["Sheet1"].A9.f, "'a$b'!A1");
assert.equal(wb.Sheets["Sheet1"].A10.f, "'a!b'!A1");
assert.equal(wb.Sheets["Sheet1"].A11.f, "'a b'!A1");
});
it.skip('should parse CSV date values with preceding space', function() {
function check_ws(ws, dNF) {
@ -2559,12 +2586,12 @@ describe('corner cases', function() {
var ws1 = X.read(
'7,2018-03-24',
{cellDates: cD, dateNF: dNF, type:'string'}
).Sheets.Sheet1;
).Sheets["Sheet1"];
check_ws(ws1, dNF);
var ws2 = X.read(
'7, 2018-03-24',
{cellDates: cD, dateNF: dNF, type:'string'}
).Sheets.Sheet1;
).Sheets["Sheet1"];
check_ws(ws2, dNF);
});
});
@ -2702,7 +2729,7 @@ describe('encryption', function() {
if(e.message == "Password is incorrect") throw e;
}
});
it.skip('should decrypt file', function() {
if(false) it('should decrypt file', function() {
/*var wb = */X.read(fs.readFileSync(dir + x), {type:TYPE,password:'password',WTF:opts.WTF});
});
});
@ -2737,19 +2764,18 @@ mft.forEach(function(x) {
cmparr(ss.map(function(s) { return (s['!merges']||[]).map(function(y) { return X.utils.encode_range(y); }).sort(); }));
});
});
it('should have the same CSV', csv ? function() {
if(csv) it('should have the same CSV', function() {
cmparr(f.map(function(x) { return x.SheetNames; }));
f[0].SheetNames.forEach(function(name) {
cmparr(f.map(function(x) { return X.utils.sheet_to_csv(x.Sheets[name]); }));
});
} : null);
it('should have the same formulae', formulae ? function() {
});
if(formulae) it('should have the same formulae', function() {
cmparr(f.map(function(x) { return x.SheetNames; }));
f[0].SheetNames.forEach(function(name) {
cmparr(f.map(function(x) { return X.utils.sheet_to_formulae(x.Sheets[name]).sort(); }));
});
} : null);
});
});
else x.split(/\s+/).forEach(function(w) { switch(w) {
case "no-csv": csv = false; break;

2716
test.mts Normal file

File diff suppressed because it is too large Load Diff

288
test.ts

@ -28,22 +28,22 @@ var DIF_XL = true;
type BSEncoding = 'utf-8' | 'binary' | 'base64';
type BFEncoding = 'buffer';
type ShEncoding = BSEncoding | BFEncoding;
function readFileSync2(x: string): Uint8Array;
function readFileSync2(x: string, e: BSEncoding): string;
function readFileSync2(x: string, e: BFEncoding): Uint8Array;
function readFileSync2(x: string, e: ShEncoding): Uint8Array | string {
const u8 = Deno.readFileSync(x);
switch(e) {
case 'utf-8': return new TextDecoder().decode(u8);
case 'base64': return base64_.encode(u8);
case 'buffer': return u8;
case 'binary': return Array.from({length: u8.length}, (_,i) => String.fromCharCode(u8[i])).join("");
}
throw new Error(`unsupported encoding ${e}`)
function readFileSync2(x: string, e?: ShEncoding): Uint8Array | string {
const u8 = Deno.readFileSync(x);
if(!e) return u8;
switch(e) {
case 'utf-8': return new TextDecoder().decode(u8);
case 'base64': return base64_.encode(u8);
case 'buffer': return u8;
case 'binary': return Array.from({length: u8.length}, (_,i) => String.fromCharCode(u8[i])).join("");
}
throw new Error(`unsupported encoding ${e}`)
}
var fs = {
readFileSync: (x: string) => Deno.readFileSync(x),
readFileSync2,
readFileSync: readFileSync2,
existsSync: (x: string): boolean => { try { Deno.readFileSync(x); } catch(e) { return false; } return true; },
readdirSync: (x: string) => ([...Deno.readDirSync(x)].filter(x => x.isFile).map(x => x.name))
};
@ -72,9 +72,9 @@ function test_file(x: string){ return ex.indexOf(x.slice(-5))>=0||exp.indexOf(x.
var files: string[] = [], fileA: string[] = [];
if(!browser) {
var _files = fs.existsSync('tests.lst') ? fs.readFileSync2('tests.lst', 'utf-8').split("\n").map(function(x) { return x.trim(); }) : fs.readdirSync('test_files');
var _files = fs.existsSync('tests.lst') ? fs.readFileSync('tests.lst', 'utf-8').split("\n").map(function(x) { return x.trim(); }) : fs.readdirSync('test_files');
for(var _filesi = 0; _filesi < _files.length; ++_filesi) if(test_file(_files[_filesi])) files.push(_files[_filesi]);
var _fileA = fs.existsSync('tests/testA.lst') ? fs.readFileSync2('tests/testA.lst', 'utf-8').split("\n").map(function(x) { return x.trim(); }) : [];
var _fileA = fs.existsSync('tests/testA.lst') ? fs.readFileSync('tests/testA.lst', 'utf-8').split("\n").map(function(x) { return x.trim(); }) : [];
for(var _fileAi = 0; _fileAi < _fileA.length; ++_fileAi) if(test_file(_fileA[_fileAi])) fileA.push(_fileA[_fileAi]);
}
@ -269,7 +269,7 @@ async function parsetest(x: string, wb: X.WorkBook, full: boolean, ext: string,
wb.SheetNames.forEach(function(y) { assert.assert(wb.Sheets[y], 'bad sheet ' + y); });
});
if(fs.existsSync(sname)) await t.step('should have the right sheet names', async function(t) {
var file = fs.readFileSync2(sname, 'utf-8').replace(/\r/g,"");
var file = fs.readFileSync(sname, 'utf-8').replace(/\r/g,"");
var names = wb.SheetNames.map(fixsheetname).join("\n") + "\n";
if(file.length && !x.match(/artifacts/)) assert.equal(names, file);
});
@ -317,7 +317,7 @@ async function parsetest(x: string, wb: X.WorkBook, full: boolean, ext: string,
for(var i = 0; i < wb.SheetNames.length; ++i) { var ws = wb.SheetNames[i];
var name = getfile(dir, x, i, ".csv");
if(fs.existsSync(name)) await t.step('#' + i + ' (' + ws + ')', async function(t) {
var file = fs.readFileSync2(name, 'utf-8');
var file = fs.readFileSync(name, 'utf-8');
var csv = X.utils.sheet_to_csv(wb.Sheets[ws]);
assert.equal(fixcsv(csv), fixcsv(file), "CSV badness");
});
@ -327,14 +327,14 @@ async function parsetest(x: string, wb: X.WorkBook, full: boolean, ext: string,
for(var i = 0; i < wb.SheetNames.length; ++i) { var ws = wb.SheetNames[i];
var rawjson = getfile(dir, x, i, ".rawjson");
if(fs.existsSync(rawjson)) await t.step('#' + i + ' (' + ws + ')', async function(t) {
var file = fs.readFileSync2(rawjson, 'utf-8');
var file = fs.readFileSync(rawjson, 'utf-8');
var json: Array<any> = X.utils.sheet_to_json(wb.Sheets[ws],{raw:true});
assert.equal(JSON.stringify(json), fixjson(file), "JSON badness");
});
var jsonf = getfile(dir, x, i, ".json");
if(fs.existsSync(jsonf)) await t.step('#' + i + ' (' + ws + ')', async function(t) {
var file = fs.readFileSync2(jsonf, 'utf-8');
var file = fs.readFileSync(jsonf, 'utf-8');
var json: Array<any> = X.utils.sheet_to_json(wb.Sheets[ws], {raw:false});
assert.equal(JSON.stringify(json), fixjson(file), "JSON badness");
});
@ -400,13 +400,13 @@ function each_sheet(wb: X.WorkBook, f: (ws: X.WorkSheet, i: number)=>any) { wb.S
/* comments_stress_test family */
function check_comments(wb: X.WorkBook) {
var ws0 = wb.Sheets.Sheet2;
var ws0 = wb.Sheets["Sheet2"];
assert.equal(get_cell(ws0,"A1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"A1").c[0].t, 'Author:\nGod thinks this is good');
assert.equal(get_cell(ws0,"C1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"C1").c[0].t, 'I really hope that xlsx decides not to use magic like rPr');
var ws3 = wb.Sheets.Sheet4;
var ws3 = wb.Sheets["Sheet4"];
assert.equal(get_cell(ws3,"B1").c[0].a, 'Author');
assert.equal(get_cell(ws3,"B1").c[0].t, 'The next comment is empty');
assert.equal(get_cell(ws3,"B2").c[0].a, 'Author');
@ -556,27 +556,27 @@ Deno.test('parse options', async function(t) {
await t.step('should not generate sheet stubs by default', async function(t) {
MCPaths.forEach(function(p) {
var wb = X.read(fs.readFileSync(p), {type:TYPE});
assert.throws(function() { return get_cell(wb.Sheets.Merge, "A2").v; });
assert.throws(function() { return get_cell(wb.Sheets["Merge"], "A2").v; });
});
});
await t.step('should generate sheet stubs when requested', async function(t) {
MCPaths.forEach(function(p) {
var wb = X.read(fs.readFileSync(p), {type:TYPE, sheetStubs:true});
assert.assert(get_cell(wb.Sheets.Merge, "A2").t == 'z');
assert.assert(get_cell(wb.Sheets["Merge"], "A2").t == 'z');
});
});
await t.step('should handle stub cells', async function(t) {
MCPaths.forEach(function(p) {
var wb = X.read(fs.readFileSync(p), {type:TYPE, sheetStubs:true});
X.utils.sheet_to_csv(wb.Sheets.Merge);
X.utils.sheet_to_json(wb.Sheets.Merge);
X.utils.sheet_to_formulae(wb.Sheets.Merge);
X.utils.sheet_to_csv(wb.Sheets["Merge"]);
X.utils.sheet_to_json(wb.Sheets["Merge"]);
X.utils.sheet_to_formulae(wb.Sheets["Merge"]);
ofmt.forEach(function(f) { if(f != "dbf") X.write(wb, {type:TYPE, bookType:f}); });
});
});
function checkcells(wb: X.WorkBook, A46: boolean, B26: boolean, C16: boolean, D2: boolean) {
([ ["A46", A46], ["B26", B26], ["C16", C16], ["D2", D2] ] as Array<[string, boolean]>).forEach(function(r: [string, boolean]) {
assert.assert((typeof get_cell(wb.Sheets.Text, r[0]) !== 'undefined') == r[1]);
assert.assert((typeof get_cell(wb.Sheets["Text"], r[0]) !== 'undefined') == r[1]);
});
}
await t.step('should read all cells by default', async function(t) { FSTPaths.forEach(function(p) {
@ -697,19 +697,19 @@ Deno.test('parse options', async function(t) {
Deno.test('input formats', async function(t) {
await t.step('should read binary strings', async function(t) { artifax.forEach(function(p) {
X.read(fs.readFileSync2(p, 'binary'), {type: 'binary'});
X.read(fs.readFileSync(p, 'binary'), {type: 'binary'});
}); });
await t.step('should read base64 strings', async function(t) { artifax.forEach(function(p) {
X.read(fs.readFileSync2(p, 'base64'), {type: 'base64'});
X.read(fs.readFileSync(p, 'base64'), {type: 'base64'});
}); });
await t.step('should read array', async function(t) { artifax.forEach(function(p) {
X.read(fs.readFileSync2(p, 'binary').split("").map(function(x) { return x.charCodeAt(0); }), {type:'array'});
if(typeof Uint8Array !== 'undefined') await t.step('should read array', async function(t) { artifax.forEach(function(p) {
X.read(fs.readFileSync(p, 'binary').split("").map(function(x) { return x.charCodeAt(0); }), {type:'array'});
}); });
await t.step('should read Buffers', async function(t) { artifax.forEach(function(p) {
X.read(fs.readFileSync(p), {type: 'buffer'});
}); });
await t.step('should read ArrayBuffer / Uint8Array', async function(t) { artifax.forEach(function(p) {
var payload: any = fs.readFileSync2(p, "buffer");
if(typeof Uint8Array !== 'undefined') await t.step('should read ArrayBuffer / Uint8Array', async function(t) { artifax.forEach(function(p) {
var payload: any = fs.readFileSync(p, "buffer");
var ab = new ArrayBuffer(payload.length), vu = new Uint8Array(ab);
for(var i = 0; i < payload.length; ++i) vu[i] = payload[i];
X.read(ab, {type: 'array'});
@ -894,7 +894,7 @@ function check_margin(margins: X.MarginInfo, exp: number[]) {
Deno.test('parse features', async function(t) {
await t.step('sheet visibility', async function(t) {
var wbs = SVPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE}); });
var wbs = SVPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE}); });
await t.step('should detect visible sheets', async function(t) {
wbs.forEach(function(wb) {
assert.assert(!wb?.Workbook?.Sheets?.[0]?.Hidden);
@ -940,7 +940,7 @@ Deno.test('parse features', async function(t) {
]; for(var ststi = 0; ststi < stst.length; ++ststi) { let m = stst[ststi]; await t.step(m[0] + ' stress test', async function(t) {
var wb = X.read(fs.readFileSync(m[1]), {type:TYPE});
check_comments(wb);
var ws0 = wb.Sheets.Sheet2;
var ws0 = wb.Sheets["Sheet2"];
assert.equal(get_cell(ws0,"A1").c[0].a, 'Author');
assert.equal(get_cell(ws0,"A1").c[0].t, 'Author:\nGod thinks this is good');
assert.equal(get_cell(ws0,"C1").c[0].a, 'Author');
@ -989,7 +989,7 @@ Deno.test('parse features', async function(t) {
await t.step('should use original range if not set', async function(t) {
var opts = {type:TYPE};
FSTPaths.map(function(p) { return X.read(fs.readFileSync(p), opts); }).forEach(function(wb) {
assert.equal(wb.Sheets.Text["!ref"],"A1:F49");
assert.equal(wb.Sheets["Text"]["!ref"],"A1:F49");
});
});
await t.step('should adjust range if set', async function(t) {
@ -997,8 +997,8 @@ Deno.test('parse features', async function(t) {
var wbs = FSTPaths.map(function(p) { return X.read(fs.readFileSync(p), opts); });
/* TODO: XLS, XML, ODS */
wbs.slice(0,2).forEach(function(wb) {
assert.equal(wb.Sheets.Text["!fullref"],"A1:F49");
assert.equal(wb.Sheets.Text["!ref"],"A1:F10");
assert.equal(wb.Sheets["Text"]["!fullref"],"A1:F49");
assert.equal(wb.Sheets["Text"]["!ref"],"A1:F10");
});
});
await t.step('should not generate comment cells', async function(t) {
@ -1006,8 +1006,8 @@ Deno.test('parse features', async function(t) {
var wbs = CSTPaths.map(function(p) { return X.read(fs.readFileSync(p), opts); });
/* TODO: XLS, XML, ODS */
wbs.slice(0,2).forEach(function(wb) {
assert.equal(wb.Sheets.Sheet7["!fullref"],"A1:N34");
assert.equal(wb.Sheets.Sheet7["!ref"],"A1");
assert.equal(wb.Sheets["Sheet7"]["!fullref"],"A1:N34");
assert.equal(wb.Sheets["Sheet7"]["!ref"],"A1");
});
});
});
@ -1092,9 +1092,9 @@ Deno.test('parse features', async function(t) {
var wbs = MCPaths.map(function(p) { return X.read(fs.readFileSync(p), {type:TYPE}); });
await t.step('should have !merges', async function(t) {
wbs.forEach(function(wb) {
assert.assert(wb.Sheets.Merge['!merges']);
assert.assert(wb.Sheets["Merge"]['!merges']);
});
var m = wbs.map(function(x) { return x.Sheets.Merge?.['!merges']?.map(function(y) { return X.utils.encode_range(y); });});
var m = wbs.map(function(x) { return x.Sheets["Merge"]?.['!merges']?.map(function(y) { return X.utils.encode_range(y); });});
m?.slice(1)?.forEach(function(x) {
assert.deepEqual(m?.[0]?.sort(),x?.sort());
});
@ -1360,6 +1360,31 @@ Deno.test('parse features', async function(t) {
assert.equal(data[4][1], '456.00');
assert.equal(data[5][1], '7,890');
}); } });
await t.step('date system', async function(t) {[
"biff5", "ods", "slk", "xls", "xlsb", "xlsx", "xml"
].forEach(function(ext) {
// TODO: verify actual date values
var wb0 = X.read(fs.readFileSync("./test_files/1904/1900." + ext), {type: TYPE, cellNF: true});
assert.assert(!(wb0?.Workbook?.WBProps?.date1904));
assert.equal(X.utils.sheet_to_csv(wb0.Sheets[wb0.SheetNames[0]]), [
"1,1900-01-01,1900-01-01,1900-01-01",
"11,1900-01-11,1900-01-11,1900-01-11",
"111,1900-04-20,1900-04-20,1900-04-20",
"1111,1903-01-15,1903-01-15,1903-01-15",
"11111,1930-06-02,1930-06-02,1930-06-02"
].join("\n"));
var wb4 = X.read(fs.readFileSync("./test_files/1904/1904." + ext), {type: TYPE, cellNF: true});
assert.assert(wb4?.Workbook?.WBProps?.date1904);
assert.equal(X.utils.sheet_to_csv(wb4.Sheets[wb4.SheetNames[0]]), [
"1,1904-01-02,1904-01-02,1904-01-02",
"11,1904-01-12,1904-01-12,1904-01-12",
"111,1904-04-21,1904-04-21,1904-04-21",
"1111,1907-01-16,1907-01-16,1907-01-16",
"11111,1934-06-03,1934-06-03,1934-06-03"
].join("\n"));
}); });
});
Deno.test('write features', async function(t) {
@ -1433,7 +1458,7 @@ Deno.test('write features', async function(t) {
assert.equal(X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet2"}), "5,6\n7,8");
assert.throws(function() { X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet3"}); });
});
await t.step('should create/update autofilter defined name on write', async function() {([
await t.step('should create/update autofilter defined name on write', async function(t) {([
"xlsx", "xlsb", /* "xls", */ "xlml" /*, "ods" */
] as Array<X.BookType>).forEach(function(fmt) {
var wb = X.utils.book_new();
@ -1445,8 +1470,8 @@ Deno.test('write features', async function(t) {
((wb2.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert.assert(!!Name, "Could not find _xlnm._FilterDatabases name WR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$2");
X.utils.sheet_add_aoa(wb2.Sheets.Sheet1, [[4,5,6]], { origin: -1 });
(wb2.Sheets.Sheet1["!autofilter"] as any).ref = wb2.Sheets.Sheet1["!ref"];
X.utils.sheet_add_aoa(wb2.Sheets["Sheet1"], [[4,5,6]], { origin: -1 });
(wb2.Sheets["Sheet1"]["!autofilter"] as any).ref = wb2.Sheets["Sheet1"]["!ref"];
var wb3 = X.read(X.write(wb2, {bookType:fmt, type:TYPE}), {type:TYPE});
Name = (null as any);
((wb3.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
@ -1522,8 +1547,8 @@ Deno.test('roundtrip features', async function(t) {
var mcf = ["xlsx", "xlsb", "xlml", "ods", "biff8", "numbers"] as Array<X.BookType>; for(let mci = 0; mci < mcf.length; ++mci) { let f = mcf[mci]; await t.step(f, async function(t) {
var wb1 = X.read(fs.readFileSync(paths.mcxlsx), {type:TYPE});
var wb2 = X.read(X.write(wb1,{bookType:f,type:'binary',numbers:XLSX_ZAHL}),{type:'binary'});
var m1 = wb1.Sheets.Merge?.['!merges']?.map(X.utils.encode_range);
var m2 = wb2.Sheets.Merge?.['!merges']?.map(X.utils.encode_range);
var m1 = wb1.Sheets["Merge"]?.['!merges']?.map(X.utils.encode_range);
var m2 = wb2.Sheets["Merge"]?.['!merges']?.map(X.utils.encode_range);
assert.equal(m1?.length, m2?.length);
if(m1 && m2) for(var i = 0; i < m1?.length; ++i) assert.assert(m1?.indexOf(m2?.[i]) > -1);
}); }
@ -1572,18 +1597,18 @@ Deno.test('roundtrip features', async function(t) {
await t.step('should preserve dynamic array formulae', async function(t) { var m19 = [
['xlsx', paths.m19xlsx]
]; for(var h1 = 0; h1 < m19.length; ++h1) { var w = m19[h1]; await t.step(w[0], async function (t) {
]; for(var h1 = 0; h1 < m19.length; ++h1) { var w = m19[h1]; await t.step(w[0], async function(t) {
var wb1 = X.read(fs.readFileSync(w[1]), {xlfn: true, type:TYPE, cellFormula:true, WTF:true});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:TYPE}), {cellFormula:true, xlfn: true, type:TYPE, WTF:true});
assert.equal(!!get_cell(wb2.Sheets.Sheet1, "B3").D, true);
assert.equal(!!get_cell(wb2.Sheets.Sheet1, "B13").D, true);
assert.equal(!!get_cell(wb2.Sheets.Sheet1, "C13").D, true);
assert.equal(!!get_cell(wb2.Sheets["Sheet1"], "B3").D, true);
assert.equal(!!get_cell(wb2.Sheets["Sheet1"], "B13").D, true);
assert.equal(!!get_cell(wb2.Sheets["Sheet1"], "C13").D, true);
get_cell(wb2.Sheets.Sheet1, "B3").D = false;
get_cell(wb2.Sheets["Sheet1"], "B3").D = false;
var wb3 = X.read(X.write(wb2, {bookType:w[0], type:TYPE}), {cellFormula:true, xlfn: true, type:TYPE, WTF:true});
assert.equal(!!get_cell(wb3.Sheets.Sheet1, "B3").D, false);
assert.equal(!!get_cell(wb3.Sheets.Sheet1, "B13").D, true);
assert.equal(!!get_cell(wb3.Sheets.Sheet1, "C13").D, true);
assert.equal(!!get_cell(wb3.Sheets["Sheet1"], "B3").D, false);
assert.equal(!!get_cell(wb3.Sheets["Sheet1"], "B13").D, true);
assert.equal(!!get_cell(wb3.Sheets["Sheet1"], "C13").D, true);
}); } });
await t.step('should preserve hyperlink', async function(t) { var hl = [
@ -1684,7 +1709,7 @@ Deno.test('roundtrip features', async function(t) {
await t.step('should preserve cell comments', async function(t) { var cc = [
['xlsx', paths.cstxlsx],
['xlsb', paths.cstxlsb],
//['xls', paths.cstxlsx],
//['xls', paths.cstxls],
['xlml', paths.cstxml]
//['ods', paths.cstods]
]; for(var cci = 0; cci < cc.length; ++cci) { let w = cc[cci];
@ -1711,7 +1736,7 @@ Deno.test('roundtrip features', async function(t) {
});
});
await t.step('should preserve autofilter settings', async function() {[
await t.step('should preserve autofilter settings', async function(t) {[
['xlsx', paths.afxlsx],
['xlsb', paths.afxlsb],
// TODO:
@ -1727,6 +1752,7 @@ Deno.test('roundtrip features', async function(t) {
assert.equal((wb2.Sheets[wb2.SheetNames[i]]['!autofilter'] as any).ref,"A1:E22");
}
}); });
});
//function password_file(x){return x.match(/^password.*\.xls$/); }
@ -1742,8 +1768,8 @@ Deno.test('invalid files', async function(t) {
['passwords', 'apachepoi_xor-encryption-abc.xls'],
['DOC files', 'word_doc.doc']
]; for(var f1 = 0; f1 < fl.length; ++f1) { let w = fl[f1]; await t.step('should fail on ' + w[0], async function(t) {
assert.throws(function() { X.read(fs.readFileSync2(dir + w[1], 'binary'), {type:'binary'}); });
assert.throws(function() { X.read(fs.readFileSync2(dir + w[1], 'base64'), {type:'base64'}); });
assert.throws(function() { X.read(fs.readFileSync(dir + w[1], 'binary'), {type:'binary'}); });
assert.throws(function() { X.read(fs.readFileSync(dir + w[1], 'base64'), {type:'base64'}); });
}); } });
await t.step('write', async function(t) {
await t.step('should pass -> XLSX', async function(t) { FSTPaths.forEach(function(p) {
@ -2159,12 +2185,12 @@ Deno.test('sylk', async function(t) {
assert.equal(get_cell(X.read(str, {type:"string"}).Sheets["Sheet1"], "A1").v, A1);
assert.equal(get_cell(X.read(str.replace(//, "\x96"), {type:"binary", codepage:1252}).Sheets["Sheet1"], "A1").v, A1);
if(true) {
assert.equal(get_cell(X.read(Buffer_from(str), {type:"buffer", codepage:65001}).Sheets.Sheet1, "A1").v, A1);
assert.equal(get_cell(X.read(Buffer_from(str.replace(//, "\x96"), "binary"), {type:"buffer", codepage:1252}).Sheets.Sheet1, "A1").v, A1);
assert.equal(get_cell(X.read(Buffer_from(str), {type:"buffer", codepage:65001}).Sheets["Sheet1"], "A1").v, A1);
assert.equal(get_cell(X.read(Buffer_from(str.replace(//, "\x96"), "binary"), {type:"buffer", codepage:1252}).Sheets["Sheet1"], "A1").v, A1);
}
});
});
await t.step('date system', async function(t){
await t.step('date system', async function(t) {
function make_slk(d1904?: number) { return "ID;PSheetJS\nP;Pd\\/m\\/yy\nP;Pd\\/m\\/yyyy\n" + (d1904 != null ? "O;D;V" + d1904 : "") + "\nF;P0;FG0G;X1;Y1\nC;K1\nE"; }
await t.step('should default to 1900', async function(t) {
assert.equal(get_cell(X.read(make_slk(), {type: "binary"}).Sheets["Sheet1"], "A1").v, 1);
@ -2179,6 +2205,7 @@ Deno.test('sylk', async function(t) {
});
});
if(typeof Uint8Array !== "undefined")
Deno.test('numbers', async function(t) {
await t.step('should parse files from Numbers 6.x', async function(t) {
var wb = X.read(fs.readFileSync(dir + 'numbers/types_61.numbers'), {type:TYPE, WTF:true});
@ -2194,20 +2221,20 @@ Deno.test('numbers', async function(t) {
var ws1 = X.utils.aoa_to_sheet(aoa);
var wb1 = X.utils.book_new(); X.utils.book_append_sheet(wb1, ws1, "Sheet1");
var wb2 = X.read(X.write(wb1,{bookType:"numbers",type:'binary',numbers:XLSX_ZAHL}),{type:'binary'});
var ws2 = wb2.Sheets.Sheet1;
var ws2 = wb2.Sheets["Sheet1"];
assert.equal(ws2["!ref"], "A1:ALL3");
assert.equal(get_cell(ws2, "A1").v, 1);
assert.equal(get_cell(ws2, "ALL2").v, 2);
});
});
if(fs.existsSync(dir + 'dbf/d11.dbf')) Deno.test('dbf', async function(t) {
Deno.test('dbf', async function(t) {
var wbs: Array<[string, X.WorkBook]> = ([
['d11', dir + 'dbf/d11.dbf'],
['vfp3', dir + 'dbf/vfp3.dbf']
]).map(function(x) { return [x[0], X.read(fs.readFileSync(x[1]), {type:TYPE})]; });
await t.step(wbs[1][0], async function(t) {
var ws = wbs[1][1].Sheets.Sheet1;
var ws = wbs[1][1].Sheets["Sheet1"];
([
["A1", "v", "CHAR10"], ["A2", "v", "test1"], ["B2", "v", 123.45],
["C2", "v", 12.345], ["D2", "v", 1234.1], ["E2", "w", "19170219"],
@ -2216,6 +2243,21 @@ if(fs.existsSync(dir + 'dbf/d11.dbf')) Deno.test('dbf', async function(t) {
] as Array<[string, string, any]>).forEach(function(r) { assert.equal(get_cell(ws, r[0])[r[1]], r[2]); });
});
});
import { JSDOM } from 'jsdom';
var domtest = false; // error: Error: Not implemented: isContext
var inserted_dom_elements = [];
function get_dom_element(html: string) {
if(browser) {
var domelt = document.createElement('div');
domelt.innerHTML = html;
if(document.body) document.body.appendChild(domelt);
inserted_dom_elements.push(domelt);
return domelt.children[0];
}
if(!JSDOM) throw new Error("Browser test fail");
return new JSDOM(html).window.document.body.children[0];
}
Deno.test('HTML', async function(t) {
await t.step('input string', async function(t) {
@ -2225,7 +2267,7 @@ Deno.test('HTML', async function(t) {
await t.step('should handle newlines correctly', async function(t) {
var table = "<table><tr><td>foo<br/>bar</td><td>baz</td></tr></table>";
var wb = X.read(table, {type:"string"});
assert.equal(get_cell(wb.Sheets.Sheet1, "A1").v, "foo\nbar");
assert.equal(get_cell(wb.Sheets["Sheet1"], "A1").v, "foo\nbar");
});
await t.step('should generate multi-sheet workbooks', async function(t) {
var table = "";
@ -2240,6 +2282,64 @@ Deno.test('HTML', async function(t) {
}
});
});
if(domtest) await t.step('input DOM', async function(t) {
await t.step('should interpret values by default', async function(t) { plaintext_test(X.utils.table_to_book(get_dom_element(html_str)), false); });
await t.step('should generate strings if raw option is passed', async function(t) { plaintext_test(X.utils.table_to_book(get_dom_element(html_str), {raw:true}), true); });
await t.step('should handle newlines correctly', async function(t) {
var table = get_dom_element("<table><tr><td>foo<br/>bar</td><td>baz</td></tr></table>");
var ws = X.utils.table_to_sheet(table);
assert.equal(get_cell(ws, "A1").v, "foo\nbar");
});
await t.step('should trim whitespace', async function(t) {
if(get_dom_element("foo <br> bar").innerHTML != "foo <br> bar") return;
var table = get_dom_element("<table><tr><td> foo <br/> bar </td><td> baz qux </td></tr></table>");
var ws = X.utils.table_to_sheet(table);
assert.equal(get_cell(ws, "A1").v.replace(/\n/g, "|"), "foo | bar");
assert.equal(get_cell(ws, "B1").v, "baz qux");
});
});
if(domtest) await t.step('should handle entities', async function(t) {
var html = "<table><tr><td>A&amp;B</td><td>A&middot;B</td></tr></table>";
var ws = X.utils.table_to_sheet(get_dom_element(html));
assert.equal(get_cell(ws, "A1").v, "A&B");
assert.equal(get_cell(ws, "B1").v, "A·B");
});
if(domtest) await t.step('should honor sheetRows', async function(t) {
var html = X.utils.sheet_to_html(X.utils.aoa_to_sheet([[1,2],[3,4],[5,6]]));
// $FlowIgnore
html = /<body[^>]*>([\s\S]*)<\/body>/i.exec(html)?.[1] || "";
var ws = X.utils.table_to_sheet(get_dom_element(html));
assert.equal(ws['!ref'], "A1:B3");
ws = X.utils.table_to_sheet(get_dom_element(html), {sheetRows:1});
assert.equal(ws['!ref'], "A1:B1");
assert.equal(ws['!fullref'], "A1:B3");
ws = X.utils.table_to_sheet(get_dom_element(html), {sheetRows:2});
assert.equal(ws['!ref'], "A1:B2");
assert.equal(ws['!fullref'], "A1:B3");
});
if(domtest) await t.step('should hide hidden rows', async function(t) {
var html = "<table><tr style='display: none;'><td>Foo</td></tr><tr><td style='display: none;'>Bar</td></tr><tr class='hidden'><td>Baz</td></tr></table><style>.hidden {display: none}</style>";
var ws = X.utils.table_to_sheet(get_dom_element(html));
var expected_rows = [];
expected_rows[0] = expected_rows[2] = {hidden: true};
assert.equal(ws['!ref'], "A1:A3");
try {
assert.deepEqual(ws['!rows'], expected_rows);
} catch(e) {
expected_rows[1] = {};
assert.deepEqual(ws['!rows'], expected_rows);
}
assert.equal(get_cell(ws, "A1").v, "Foo");
assert.equal(get_cell(ws, "A2").v, "Bar");
assert.equal(get_cell(ws, "A3").v, "Baz");
});
if(domtest) await t.step('should ignore hidden rows and cells when the `display` option is on', async function(t) {
var html = "<table><tr style='display: none;'><td>1</td><td>2</td><td>3</td></tr><tr><td class='hidden'>Foo</td><td>Bar</td><td style='display: none;'>Baz</td></tr></table><style>.hidden {display: none}</style>";
var ws = X.utils.table_to_sheet(get_dom_element(html), {display: true});
assert.equal(ws['!ref'], "A1");
assert.assert(ws.hasOwnProperty('!rows') == false || !ws["!rows"]?.[0] || !ws["!rows"]?.[0]?.hidden);
assert.equal(get_cell(ws, "A1").v, "Bar");
});
await t.step('type override', async function(t) {
function chk(ws: X.WorkSheet) {
assert.equal(get_cell(ws, "A1").t, "s");
@ -2249,25 +2349,35 @@ Deno.test('HTML', async function(t) {
}
var html = "<table><tr><td t=\"s\">1234567890</td><td>1234567890</td></tr></table>";
await t.step('HTML string', async function(t) {
var ws = X.read(html, {type:'string'}).Sheets.Sheet1; chk(ws);
chk(X.read(X.utils.sheet_to_html(ws), {type:'string'}).Sheets.Sheet1);
var ws = X.read(html, {type:'string'}).Sheets["Sheet1"]; chk(ws);
chk(X.read(X.utils.sheet_to_html(ws), {type:'string'}).Sheets["Sheet1"]);
});
if(domtest) await t.step('DOM', async function(t) { chk(X.utils.table_to_sheet(get_dom_element(html))); });
});
await t.step('TH/THEAD/TBODY/TFOOT elements', async function(t) {
var html = "<table><thead><tr><th>A</th><th>B</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></tbody><tfoot><tr><th>4</th><th>6</th></tr></tfoot></table>";
await t.step('HTML string', async function(t) {
var ws = X.read(html, {type:'string'}).Sheets.Sheet1;
var ws = X.read(html, {type:'string'}).Sheets["Sheet1"];
assert.equal(X.utils.sheet_to_csv(ws), "A,B\n1,2\n3,4\n4,6");
});
if(domtest) await t.step('DOM', async function(t) {
var ws = X.utils.table_to_sheet(get_dom_element(html));
assert.equal(X.utils.sheet_to_csv(ws), "A,B\n1,2\n3,4\n4,6");
});
});
await t.step('empty cell containing html element should increment cell index', async function(t) {
var html = "<table><tr><td>abc</td><td><b> </b></td><td>def</td></tr></table>";
var expectedCellCount = 3;
await t.step('HTML string', async function() {
var ws = X.read(html, {type:'string'}).Sheets.Sheet1;
await t.step('HTML string', async function(t) {
var ws = X.read(html, {type:'string'}).Sheets["Sheet1"];
var range = X.utils.decode_range(ws['!ref']||"A1");
assert.equal(range.e.c,expectedCellCount - 1);
});
if(domtest) await t.step('DOM', async function(t) {
var ws = X.utils.table_to_sheet(get_dom_element(html));
var range = X.utils.decode_range(ws['!ref']||"A1");
assert.equal(range.e.c, expectedCellCount - 1);
});
});
});
@ -2346,7 +2456,7 @@ Deno.test('corner cases', async function(t) {
});
if(typeof JSON !== 'undefined') await t.step('SSF oddities', async function(t) {
// $FlowIgnore
var ssfdata: Array<any> = JSON.parse(fs.readFileSync2('./misc/ssf.json', 'utf-8'));
var ssfdata: Array<any> = JSON.parse(fs.readFileSync('./misc/ssf.json', 'utf-8'));
var cb = function(d: any, j: any) { return function() { return X.SSF.format(d[0], d[j][0]); }; };
ssfdata.forEach(function(d) {
for(var j=1;j<d.length;++j) {
@ -2381,17 +2491,17 @@ Deno.test('corner cases', async function(t) {
});
await t.step("should quote unicode sheet names in formulae", async function(t) {
var wb = X.read(fs.readFileSync(dir + "cross-sheet_formula_names.xlsb"), {WTF:true, type:TYPE});
assert.equal(wb.Sheets.Sheet1.A1.f, "'a-b'!A1");
assert.equal(wb.Sheets.Sheet1.A2.f, "'a#b'!A1");
assert.equal(wb.Sheets.Sheet1.A3.f, "'a^b'!A1");
assert.equal(wb.Sheets.Sheet1.A4.f, "'a%b'!A1");
assert.equal(wb.Sheets.Sheet1.A5.f, "'a\u066ab'!A1");
assert.equal(wb.Sheets.Sheet1.A6.f, "'☃️'!A1");
assert.equal(wb.Sheets.Sheet1.A7.f, "'\ud83c\udf63'!A1");
assert.equal(wb.Sheets.Sheet1.A8.f, "'a!!b'!A1");
assert.equal(wb.Sheets.Sheet1.A9.f, "'a$b'!A1");
assert.equal(wb.Sheets.Sheet1.A10.f, "'a!b'!A1");
assert.equal(wb.Sheets.Sheet1.A11.f, "'a b'!A1");
assert.equal(wb.Sheets["Sheet1"].A1.f, "'a-b'!A1");
assert.equal(wb.Sheets["Sheet1"].A2.f, "'a#b'!A1");
assert.equal(wb.Sheets["Sheet1"].A3.f, "'a^b'!A1");
assert.equal(wb.Sheets["Sheet1"].A4.f, "'a%b'!A1");
assert.equal(wb.Sheets["Sheet1"].A5.f, "'a\u066ab'!A1");
assert.equal(wb.Sheets["Sheet1"].A6.f, "'☃️'!A1");
assert.equal(wb.Sheets["Sheet1"].A7.f, "'\ud83c\udf63'!A1");
assert.equal(wb.Sheets["Sheet1"].A8.f, "'a!!b'!A1");
assert.equal(wb.Sheets["Sheet1"].A9.f, "'a$b'!A1");
assert.equal(wb.Sheets["Sheet1"].A10.f, "'a!b'!A1");
assert.equal(wb.Sheets["Sheet1"].A11.f, "'a b'!A1");
});
if(false) await t.step('should parse CSV date values with preceding space', async function(t) {
function check_ws(ws: X.WorkSheet, dNF?: any) {
@ -2406,12 +2516,12 @@ Deno.test('corner cases', async function(t) {
var ws1 = X.read(
'7,2018-03-24',
{cellDates: cD, dateNF: dNF, type:'string'}
).Sheets.Sheet1;
).Sheets["Sheet1"];
check_ws(ws1, dNF);
var ws2 = X.read(
'7, 2018-03-24',
{cellDates: cD, dateNF: dNF, type:'string'}
).Sheets.Sheet1;
).Sheets["Sheet1"];
check_ws(ws2, dNF);
});
});
@ -2549,16 +2659,17 @@ Deno.test('encryption', async function(t) {
if(e.message == "Password is incorrect") throw e;
}
});
//it.skip('should decrypt file', async function(t) {
// /*var wb = */X.read(fs.readFileSync(dir + x), {type:TYPE,password:'password',WTF:opts.WTF});
//});
if(false) await t.step('should decrypt file', async function(t) {
/*var wb = */X.read(fs.readFileSync(dir + x), {type:TYPE,password:'password',WTF:opts.WTF});
});
});
};
});
if(!browser || typeof cptable !== 'undefined')
Deno.test('multiformat tests', async function(t) {
var mfopts = opts;
var mft = fs.readFileSync2('multiformat.lst','utf-8').replace(/\r/g,"").split("\n").map(function(x) { return x.trim(); });
var mft = fs.readFileSync('multiformat.lst','utf-8').replace(/\r/g,"").split("\n").map(function(x) { return x.trim(); });
var csv = true, formulae = false;
for(var mfti = 0; mfti < mft.length; ++mfti) { var x = mft[mfti];
if(x.charAt(0)!="#") await t.step('MFT ' + x, async function(t) {
@ -2603,3 +2714,4 @@ for(var mfti = 0; mfti < mft.length; ++mfti) { var x = mft[mfti];
case "yes-formula": formulae = true; break;
}});
} });

@ -1 +1 @@
Subproject commit 50158c2288492df6f6eb4037568023a3de3e0ef9
Subproject commit 02324c31f26195b3b5c15c38ca46fe6d22c9ac1c

@ -1,4 +1,5 @@
var assert = function(bool) { if(!bool) { throw new Error("failed assert"); } };
assert.ok = function(bool) { if(!bool) { throw new Error("failed assert"); } };
assert.deepEqualArray = function(x,y) {
if(x.length != y.length) throw new Error("Length mismatch: " + x.length + " != " + y.length);
for(var i = 0; i < x.length; ++i) assert.deepEqual(x[i], y[i]);

@ -4372,8 +4372,8 @@ function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksh
else if(typeof cell.v === 'boolean') cell.t = 'b';
else if(cell.v instanceof Date) {
cell.z = o.dateNF || table_fmt[14];
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v)); }
else { cell.t = 'n'; cell.v = datenum(cell.v); cell.w = SSF_format(cell.z, cell.v); }
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v, o.date1904)); }
else { cell.t = 'n'; cell.v = datenum(cell.v, o.date1904); cell.w = SSF_format(cell.z, cell.v); }
}
else cell.t = 's';
}
@ -8026,7 +8026,7 @@ var SYLK = /*#__PURE__*/(function() {
wb.Workbook.Names.push(nn);
} break;
case 'C': /* cell */
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1;
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1, formula = "", cell_t = "z";
for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'A': break; // TODO: comment
case 'X': C = parseInt(record[rj].slice(1), 10)-1; C_seen_X = true; break;
@ -8036,26 +8036,24 @@ var SYLK = /*#__PURE__*/(function() {
break;
case 'K':
val = record[rj].slice(1);
if(val.charAt(0) === '"') val = val.slice(1,val.length - 1);
else if(val === 'TRUE') val = true;
else if(val === 'FALSE') val = false;
if(val.charAt(0) === '"') { val = val.slice(1,val.length - 1); cell_t = "s"; }
else if(val === 'TRUE' || val === 'FALSE') { val = val === 'TRUE'; cell_t = "b"; }
else if(!isNaN(fuzzynum(val))) {
val = fuzzynum(val);
if(next_cell_format !== null && fmt_is_date(next_cell_format)) val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val);
val = fuzzynum(val); cell_t = "n";
if(next_cell_format !== null && fmt_is_date(next_cell_format) && opts.cellDates) { val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val); cell_t = "d"; }
} else if(!isNaN(fuzzydate(val).getDate())) {
val = parseDate(val);
val = parseDate(val); cell_t = "d";
if(!opts.cellDates) { cell_t = "n"; val = datenum(val, wb.Workbook.WBProps.date1904); }
}
if(typeof $cptable !== 'undefined' && typeof val == "string" && ((opts||{}).type != "string") && (opts||{}).codepage) val = $cptable.utils.decode(opts.codepage, val);
C_seen_K = true;
break;
case 'E':
C_seen_E = true;
var formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
arr[R][C] = [arr[R][C], formula];
formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
break;
case 'S':
C_seen_S = true;
arr[R][C] = [arr[R][C], "S5S"];
break;
case 'G': break; // unknown
case 'R': _R = parseInt(record[rj].slice(1), 10)-1; break;
@ -8063,15 +8061,21 @@ var SYLK = /*#__PURE__*/(function() {
default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
}
if(C_seen_K) {
if(arr[R][C] && arr[R][C].length == 2) arr[R][C][0] = val;
else arr[R][C] = val;
if(!arr[R][C]) arr[R][C] = { t: cell_t, v: val };
else { arr[R][C].t = cell_t; arr[R][C].v = val; }
if(next_cell_format) arr[R][C].z = next_cell_format;
if(opts.cellText !== false && next_cell_format) arr[R][C].w = SSF_format(arr[R][C].z, arr[R][C].v, { date1904: wb.Workbook.WBProps.date1904 });
next_cell_format = null;
}
if(C_seen_S) {
if(C_seen_E) throw new Error("SYLK shared formula cannot have own formula");
var shrbase = _R > -1 && arr[_R][_C];
if(!shrbase || !shrbase[1]) throw new Error("SYLK shared formula cannot find base");
arr[R][C][1] = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
formula = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
}
if(formula) {
if(!arr[R][C]) arr[R][C] = { t: 'n', f: formula };
else arr[R][C].f = formula;
}
break;
case 'F': /* Format */
@ -8123,7 +8127,8 @@ var SYLK = /*#__PURE__*/(function() {
function sylk_to_workbook(d/*:RawData*/, opts)/*:Workbook*/ {
var aoasht = sylk_to_aoa(d, opts);
var aoa = aoasht[0], ws = aoasht[1], wb = aoasht[2];
var o = aoa_to_sheet(aoa, opts);
var _opts = dup(opts); _opts.date1904 = (((wb||{}).Workbook || {}).WBProps || {}).date1904;
var o = aoa_to_sheet(aoa, _opts);
keys(ws).forEach(function(k) { o[k] = ws[k]; });
var outwb = sheet_to_workbook(o, opts);
keys(wb).forEach(function(k) { outwb[k] = wb[k]; });
@ -8167,11 +8172,12 @@ var SYLK = /*#__PURE__*/(function() {
});
}
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/, wb/*:?WorkBook*/)/*:string*/ {
var preamble/*:Array<string>*/ = ["ID;PSheetJS;N;E"], o/*:Array<string>*/ = [];
var r = safe_decode_range(ws['!ref']), cell/*:Cell*/;
var dense = Array.isArray(ws);
var RS = "\r\n";
var d1904 = (((wb||{}).Workbook||{}).WBProps||{}).date1904;
preamble.push("P;PGeneral");
preamble.push("F;P0;DG0G8;M255");
@ -8179,12 +8185,13 @@ var SYLK = /*#__PURE__*/(function() {
if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']);
preamble.push("B;Y" + (r.e.r - r.s.r + 1) + ";X" + (r.e.c - r.s.c + 1) + ";D" + [r.s.c,r.s.r,r.e.c,r.e.r].join(" "));
preamble.push("O;L;D;B" + (d1904 ? ";V4" : "") + ";K47;G100 0.001");
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
cell = dense ? (ws[R]||[])[C]: ws[coord];
if(!cell || (cell.v == null && (!cell.f || cell.F))) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); // TODO: pass date1904 info
}
}
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
@ -17405,6 +17412,7 @@ function write_BrtWbProp(data/*:?WBProps*/, o) {
var flags = 0;
if(data) {
/* TODO: mirror parse_BrtWbProp fields */
if(data.date1904) flags |= 0x01;
if(data.filterPrivacy) flags |= 0x08;
}
o.write_shift(4, flags);
@ -18711,9 +18719,10 @@ function write_props_xlml(wb/*:Workbook*/, opts)/*:string*/ {
return o.join("");
}
/* TODO */
function write_wb_xlml(/*::wb, opts*/)/*:string*/ {
function write_wb_xlml(wb/*::, opts*/)/*:string*/ {
/* OfficeDocumentSettings */
/* ExcelWorkbook */
if((((wb||{}).Workbook||{}).WBProps||{}).date1904) return '<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"><Date1904/></ExcelWorkbook>';
return "";
}
/* TODO */
@ -21766,13 +21775,14 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
var merges/*:Array<Range>*/ = [], mrange = {}, mR = 0, mC = 0;
var rowinfo/*:Array<RowInfo>*/ = [], rowpeat = 1, colpeat = 1;
var arrayf/*:Array<[Range, string]>*/ = [];
var WB = {Names:[]};
var WB = {Names:[], WBProps:{}};
var atag = ({}/*:any*/);
var _Ref/*:[string, string]*/ = ["", ""];
var comments/*:Array<Comment>*/ = [], comment/*:Comment*/ = ({}/*:any*/);
var creator = "", creatoridx = 0;
var isstub = false, intable = false;
var i = 0;
var baddate = 1;
xlmlregex.lastIndex = 0;
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
@ -21884,7 +21894,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v); }
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
q.z = 'm/d/yy'; break;
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
@ -22085,7 +22095,14 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
case 'table-header-columns': break; // 9.1.11 <table:table-header-columns>
case 'table-columns': break; // 9.1.12 <table:table-columns>
case 'null-date': break; // 9.4.2 <table:null-date> TODO: date1904
case 'null-date': // 9.4.2 <table:null-date>
tag = parsexmltag(Rn[0], false);
switch(tag["date-value"]) {
case "1904-01-01": WB.WBProps.date1904 = true;
/* falls through */
case "1900-01-01": baddate = 0;
}
break;
case 'graphic-properties': break; // 17.21 <style:graphic-properties>
case 'calculation-settings': break; // 9.4.1 <table:calculation-settings>
@ -22540,6 +22557,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
write_automatic_styles_ods(o, wb);
o.push(' <office:body>\n');
o.push(' <office:spreadsheet>\n');
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
o.push(' </office:spreadsheet>\n');
@ -24664,7 +24682,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
case 'xml':
case 'xlml': return write_string_type(write_xlml(wb, o), o);
case 'slk':
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb), o);
case 'htm':
case 'html': return write_string_type(sheet_to_html(wb.Sheets[wb.SheetNames[idx]], o), o);
case 'txt': return write_stxt_type(sheet_to_txt(wb.Sheets[wb.SheetNames[idx]], o), o);

64
xlsx.js generated

@ -4287,8 +4287,8 @@ function sheet_add_aoa(_ws, data, opts) {
else if(typeof cell.v === 'boolean') cell.t = 'b';
else if(cell.v instanceof Date) {
cell.z = o.dateNF || table_fmt[14];
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v)); }
else { cell.t = 'n'; cell.v = datenum(cell.v); cell.w = SSF_format(cell.z, cell.v); }
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v, o.date1904)); }
else { cell.t = 'n'; cell.v = datenum(cell.v, o.date1904); cell.w = SSF_format(cell.z, cell.v); }
}
else cell.t = 's';
}
@ -7936,7 +7936,7 @@ var SYLK = (function() {
wb.Workbook.Names.push(nn);
} break;
case 'C': /* cell */
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1;
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1, formula = "", cell_t = "z";
for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'A': break; // TODO: comment
case 'X': C = parseInt(record[rj].slice(1), 10)-1; C_seen_X = true; break;
@ -7946,26 +7946,24 @@ var SYLK = (function() {
break;
case 'K':
val = record[rj].slice(1);
if(val.charAt(0) === '"') val = val.slice(1,val.length - 1);
else if(val === 'TRUE') val = true;
else if(val === 'FALSE') val = false;
if(val.charAt(0) === '"') { val = val.slice(1,val.length - 1); cell_t = "s"; }
else if(val === 'TRUE' || val === 'FALSE') { val = val === 'TRUE'; cell_t = "b"; }
else if(!isNaN(fuzzynum(val))) {
val = fuzzynum(val);
if(next_cell_format !== null && fmt_is_date(next_cell_format)) val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val);
val = fuzzynum(val); cell_t = "n";
if(next_cell_format !== null && fmt_is_date(next_cell_format) && opts.cellDates) { val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val); cell_t = "d"; }
} else if(!isNaN(fuzzydate(val).getDate())) {
val = parseDate(val);
val = parseDate(val); cell_t = "d";
if(!opts.cellDates) { cell_t = "n"; val = datenum(val, wb.Workbook.WBProps.date1904); }
}
if(typeof $cptable !== 'undefined' && typeof val == "string" && ((opts||{}).type != "string") && (opts||{}).codepage) val = $cptable.utils.decode(opts.codepage, val);
C_seen_K = true;
break;
case 'E':
C_seen_E = true;
var formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
arr[R][C] = [arr[R][C], formula];
formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
break;
case 'S':
C_seen_S = true;
arr[R][C] = [arr[R][C], "S5S"];
break;
case 'G': break; // unknown
case 'R': _R = parseInt(record[rj].slice(1), 10)-1; break;
@ -7973,15 +7971,21 @@ var SYLK = (function() {
default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
}
if(C_seen_K) {
if(arr[R][C] && arr[R][C].length == 2) arr[R][C][0] = val;
else arr[R][C] = val;
if(!arr[R][C]) arr[R][C] = { t: cell_t, v: val };
else { arr[R][C].t = cell_t; arr[R][C].v = val; }
if(next_cell_format) arr[R][C].z = next_cell_format;
if(opts.cellText !== false && next_cell_format) arr[R][C].w = SSF_format(arr[R][C].z, arr[R][C].v, { date1904: wb.Workbook.WBProps.date1904 });
next_cell_format = null;
}
if(C_seen_S) {
if(C_seen_E) throw new Error("SYLK shared formula cannot have own formula");
var shrbase = _R > -1 && arr[_R][_C];
if(!shrbase || !shrbase[1]) throw new Error("SYLK shared formula cannot find base");
arr[R][C][1] = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
formula = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
}
if(formula) {
if(!arr[R][C]) arr[R][C] = { t: 'n', f: formula };
else arr[R][C].f = formula;
}
break;
case 'F': /* Format */
@ -8033,7 +8037,8 @@ var SYLK = (function() {
function sylk_to_workbook(d, opts) {
var aoasht = sylk_to_aoa(d, opts);
var aoa = aoasht[0], ws = aoasht[1], wb = aoasht[2];
var o = aoa_to_sheet(aoa, opts);
var _opts = dup(opts); _opts.date1904 = (((wb||{}).Workbook || {}).WBProps || {}).date1904;
var o = aoa_to_sheet(aoa, _opts);
keys(ws).forEach(function(k) { o[k] = ws[k]; });
var outwb = sheet_to_workbook(o, opts);
keys(wb).forEach(function(k) { outwb[k] = wb[k]; });
@ -8077,11 +8082,12 @@ var SYLK = (function() {
});
}
function sheet_to_sylk(ws, opts) {
function sheet_to_sylk(ws, opts, wb) {
var preamble = ["ID;PSheetJS;N;E"], o = [];
var r = safe_decode_range(ws['!ref']), cell;
var dense = Array.isArray(ws);
var RS = "\r\n";
var d1904 = (((wb||{}).Workbook||{}).WBProps||{}).date1904;
preamble.push("P;PGeneral");
preamble.push("F;P0;DG0G8;M255");
@ -8089,12 +8095,13 @@ var SYLK = (function() {
if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']);
preamble.push("B;Y" + (r.e.r - r.s.r + 1) + ";X" + (r.e.c - r.s.c + 1) + ";D" + [r.s.c,r.s.r,r.e.c,r.e.r].join(" "));
preamble.push("O;L;D;B" + (d1904 ? ";V4" : "") + ";K47;G100 0.001");
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
cell = dense ? (ws[R]||[])[C]: ws[coord];
if(!cell || (cell.v == null && (!cell.f || cell.F))) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); // TODO: pass date1904 info
}
}
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
@ -17308,6 +17315,7 @@ function write_BrtWbProp(data, o) {
var flags = 0;
if(data) {
/* TODO: mirror parse_BrtWbProp fields */
if(data.date1904) flags |= 0x01;
if(data.filterPrivacy) flags |= 0x08;
}
o.write_shift(4, flags);
@ -18609,9 +18617,10 @@ function write_props_xlml(wb, opts) {
return o.join("");
}
/* TODO */
function write_wb_xlml() {
function write_wb_xlml(wb) {
/* OfficeDocumentSettings */
/* ExcelWorkbook */
if((((wb||{}).Workbook||{}).WBProps||{}).date1904) return '<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"><Date1904/></ExcelWorkbook>';
return "";
}
/* TODO */
@ -21656,13 +21665,14 @@ function parse_content_xml(d, _opts) {
var merges = [], mrange = {}, mR = 0, mC = 0;
var rowinfo = [], rowpeat = 1, colpeat = 1;
var arrayf = [];
var WB = {Names:[]};
var WB = {Names:[], WBProps:{}};
var atag = ({});
var _Ref = ["", ""];
var comments = [], comment = ({});
var creator = "", creatoridx = 0;
var isstub = false, intable = false;
var i = 0;
var baddate = 1;
xlmlregex.lastIndex = 0;
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
@ -21774,7 +21784,7 @@ function parse_content_xml(d, _opts) {
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v); }
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
q.z = 'm/d/yy'; break;
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
@ -21975,7 +21985,14 @@ function parse_content_xml(d, _opts) {
case 'table-header-columns': break; // 9.1.11 <table:table-header-columns>
case 'table-columns': break; // 9.1.12 <table:table-columns>
case 'null-date': break; // 9.4.2 <table:null-date> TODO: date1904
case 'null-date': // 9.4.2 <table:null-date>
tag = parsexmltag(Rn[0], false);
switch(tag["date-value"]) {
case "1904-01-01": WB.WBProps.date1904 = true;
/* falls through */
case "1900-01-01": baddate = 0;
}
break;
case 'graphic-properties': break; // 17.21 <style:graphic-properties>
case 'calculation-settings': break; // 9.4.1 <table:calculation-settings>
@ -22430,6 +22447,7 @@ var write_content_ods = /* @__PURE__ */(function() {
write_automatic_styles_ods(o, wb);
o.push(' <office:body>\n');
o.push(' <office:spreadsheet>\n');
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
o.push(' </office:spreadsheet>\n');
@ -24549,7 +24567,7 @@ function writeSync(wb, opts) {
case 'xml':
case 'xlml': return write_string_type(write_xlml(wb, o), o);
case 'slk':
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb), o);
case 'htm':
case 'html': return write_string_type(sheet_to_html(wb.Sheets[wb.SheetNames[idx]], o), o);
case 'txt': return write_stxt_type(sheet_to_txt(wb.Sheets[wb.SheetNames[idx]], o), o);

64
xlsx.mjs generated

@ -4367,8 +4367,8 @@ function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksh
else if(typeof cell.v === 'boolean') cell.t = 'b';
else if(cell.v instanceof Date) {
cell.z = o.dateNF || table_fmt[14];
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v)); }
else { cell.t = 'n'; cell.v = datenum(cell.v); cell.w = SSF_format(cell.z, cell.v); }
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v, o.date1904)); }
else { cell.t = 'n'; cell.v = datenum(cell.v, o.date1904); cell.w = SSF_format(cell.z, cell.v); }
}
else cell.t = 's';
}
@ -8021,7 +8021,7 @@ var SYLK = /*#__PURE__*/(function() {
wb.Workbook.Names.push(nn);
} break;
case 'C': /* cell */
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1;
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1, formula = "", cell_t = "z";
for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'A': break; // TODO: comment
case 'X': C = parseInt(record[rj].slice(1), 10)-1; C_seen_X = true; break;
@ -8031,26 +8031,24 @@ var SYLK = /*#__PURE__*/(function() {
break;
case 'K':
val = record[rj].slice(1);
if(val.charAt(0) === '"') val = val.slice(1,val.length - 1);
else if(val === 'TRUE') val = true;
else if(val === 'FALSE') val = false;
if(val.charAt(0) === '"') { val = val.slice(1,val.length - 1); cell_t = "s"; }
else if(val === 'TRUE' || val === 'FALSE') { val = val === 'TRUE'; cell_t = "b"; }
else if(!isNaN(fuzzynum(val))) {
val = fuzzynum(val);
if(next_cell_format !== null && fmt_is_date(next_cell_format)) val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val);
val = fuzzynum(val); cell_t = "n";
if(next_cell_format !== null && fmt_is_date(next_cell_format) && opts.cellDates) { val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val); cell_t = "d"; }
} else if(!isNaN(fuzzydate(val).getDate())) {
val = parseDate(val);
val = parseDate(val); cell_t = "d";
if(!opts.cellDates) { cell_t = "n"; val = datenum(val, wb.Workbook.WBProps.date1904); }
}
if(typeof $cptable !== 'undefined' && typeof val == "string" && ((opts||{}).type != "string") && (opts||{}).codepage) val = $cptable.utils.decode(opts.codepage, val);
C_seen_K = true;
break;
case 'E':
C_seen_E = true;
var formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
arr[R][C] = [arr[R][C], formula];
formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
break;
case 'S':
C_seen_S = true;
arr[R][C] = [arr[R][C], "S5S"];
break;
case 'G': break; // unknown
case 'R': _R = parseInt(record[rj].slice(1), 10)-1; break;
@ -8058,15 +8056,21 @@ var SYLK = /*#__PURE__*/(function() {
default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
}
if(C_seen_K) {
if(arr[R][C] && arr[R][C].length == 2) arr[R][C][0] = val;
else arr[R][C] = val;
if(!arr[R][C]) arr[R][C] = { t: cell_t, v: val };
else { arr[R][C].t = cell_t; arr[R][C].v = val; }
if(next_cell_format) arr[R][C].z = next_cell_format;
if(opts.cellText !== false && next_cell_format) arr[R][C].w = SSF_format(arr[R][C].z, arr[R][C].v, { date1904: wb.Workbook.WBProps.date1904 });
next_cell_format = null;
}
if(C_seen_S) {
if(C_seen_E) throw new Error("SYLK shared formula cannot have own formula");
var shrbase = _R > -1 && arr[_R][_C];
if(!shrbase || !shrbase[1]) throw new Error("SYLK shared formula cannot find base");
arr[R][C][1] = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
formula = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
}
if(formula) {
if(!arr[R][C]) arr[R][C] = { t: 'n', f: formula };
else arr[R][C].f = formula;
}
break;
case 'F': /* Format */
@ -8118,7 +8122,8 @@ var SYLK = /*#__PURE__*/(function() {
function sylk_to_workbook(d/*:RawData*/, opts)/*:Workbook*/ {
var aoasht = sylk_to_aoa(d, opts);
var aoa = aoasht[0], ws = aoasht[1], wb = aoasht[2];
var o = aoa_to_sheet(aoa, opts);
var _opts = dup(opts); _opts.date1904 = (((wb||{}).Workbook || {}).WBProps || {}).date1904;
var o = aoa_to_sheet(aoa, _opts);
keys(ws).forEach(function(k) { o[k] = ws[k]; });
var outwb = sheet_to_workbook(o, opts);
keys(wb).forEach(function(k) { outwb[k] = wb[k]; });
@ -8162,11 +8167,12 @@ var SYLK = /*#__PURE__*/(function() {
});
}
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/, wb/*:?WorkBook*/)/*:string*/ {
var preamble/*:Array<string>*/ = ["ID;PSheetJS;N;E"], o/*:Array<string>*/ = [];
var r = safe_decode_range(ws['!ref']), cell/*:Cell*/;
var dense = Array.isArray(ws);
var RS = "\r\n";
var d1904 = (((wb||{}).Workbook||{}).WBProps||{}).date1904;
preamble.push("P;PGeneral");
preamble.push("F;P0;DG0G8;M255");
@ -8174,12 +8180,13 @@ var SYLK = /*#__PURE__*/(function() {
if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']);
preamble.push("B;Y" + (r.e.r - r.s.r + 1) + ";X" + (r.e.c - r.s.c + 1) + ";D" + [r.s.c,r.s.r,r.e.c,r.e.r].join(" "));
preamble.push("O;L;D;B" + (d1904 ? ";V4" : "") + ";K47;G100 0.001");
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
cell = dense ? (ws[R]||[])[C]: ws[coord];
if(!cell || (cell.v == null && (!cell.f || cell.F))) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); // TODO: pass date1904 info
}
}
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
@ -17400,6 +17407,7 @@ function write_BrtWbProp(data/*:?WBProps*/, o) {
var flags = 0;
if(data) {
/* TODO: mirror parse_BrtWbProp fields */
if(data.date1904) flags |= 0x01;
if(data.filterPrivacy) flags |= 0x08;
}
o.write_shift(4, flags);
@ -18706,9 +18714,10 @@ function write_props_xlml(wb/*:Workbook*/, opts)/*:string*/ {
return o.join("");
}
/* TODO */
function write_wb_xlml(/*::wb, opts*/)/*:string*/ {
function write_wb_xlml(wb/*::, opts*/)/*:string*/ {
/* OfficeDocumentSettings */
/* ExcelWorkbook */
if((((wb||{}).Workbook||{}).WBProps||{}).date1904) return '<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"><Date1904/></ExcelWorkbook>';
return "";
}
/* TODO */
@ -21761,13 +21770,14 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
var merges/*:Array<Range>*/ = [], mrange = {}, mR = 0, mC = 0;
var rowinfo/*:Array<RowInfo>*/ = [], rowpeat = 1, colpeat = 1;
var arrayf/*:Array<[Range, string]>*/ = [];
var WB = {Names:[]};
var WB = {Names:[], WBProps:{}};
var atag = ({}/*:any*/);
var _Ref/*:[string, string]*/ = ["", ""];
var comments/*:Array<Comment>*/ = [], comment/*:Comment*/ = ({}/*:any*/);
var creator = "", creatoridx = 0;
var isstub = false, intable = false;
var i = 0;
var baddate = 1;
xlmlregex.lastIndex = 0;
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
@ -21879,7 +21889,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v); }
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
q.z = 'm/d/yy'; break;
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
@ -22080,7 +22090,14 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
case 'table-header-columns': break; // 9.1.11 <table:table-header-columns>
case 'table-columns': break; // 9.1.12 <table:table-columns>
case 'null-date': break; // 9.4.2 <table:null-date> TODO: date1904
case 'null-date': // 9.4.2 <table:null-date>
tag = parsexmltag(Rn[0], false);
switch(tag["date-value"]) {
case "1904-01-01": WB.WBProps.date1904 = true;
/* falls through */
case "1900-01-01": baddate = 0;
}
break;
case 'graphic-properties': break; // 17.21 <style:graphic-properties>
case 'calculation-settings': break; // 9.4.1 <table:calculation-settings>
@ -22535,6 +22552,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
write_automatic_styles_ods(o, wb);
o.push(' <office:body>\n');
o.push(' <office:spreadsheet>\n');
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
o.push(' </office:spreadsheet>\n');
@ -24659,7 +24677,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
case 'xml':
case 'xlml': return write_string_type(write_xlml(wb, o), o);
case 'slk':
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb), o);
case 'htm':
case 'html': return write_string_type(sheet_to_html(wb.Sheets[wb.SheetNames[idx]], o), o);
case 'txt': return write_stxt_type(sheet_to_txt(wb.Sheets[wb.SheetNames[idx]], o), o);