Skip to content

Commit

Permalink
roundtrip 1904 date setting
Browse files Browse the repository at this point in the history
  • Loading branch information
SheetJSDev committed Jun 6, 2022
1 parent 83ddb4c commit 4cc0412
Show file tree
Hide file tree
Showing 22 changed files with 3,556 additions and 554 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/deno.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions bits/27_csfutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down
39 changes: 23 additions & 16 deletions bits/40_harb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -450,42 +450,46 @@ 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;
case 'C': _C = parseInt(record[rj].slice(1), 10)-1; break;
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 */
Expand Down Expand Up @@ -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]; });
Expand Down Expand Up @@ -581,24 +586,26 @@ 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");
if(ws['!cols']) write_ws_cols_sylk(preamble, ws['!cols']);
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;
Expand Down
1 change: 1 addition & 0 deletions bits/73_wbbin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion bits/75_xlml.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
14 changes: 11 additions & 3 deletions bits/80_parseods.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(/_.*$/,""))) {
Expand Down Expand Up @@ -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); }
Expand Down Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions bits/81_writeods.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion bits/88_write.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
Expand Down
5 changes: 5 additions & 0 deletions misc/import_map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"imports": {
"jsdom": "https://esm.sh/jsdom"
}
}
11 changes: 11 additions & 0 deletions misc/test.deno.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"lib": [
"deno.ns",
"dom",
"dom.iterable",
"dom.asynciterable"
]
},
"importMap": "./import_map.json"
}
Loading

0 comments on commit 4cc0412

Please sign in to comment.