version bump 0.9.7: write features

- write !cols widths : XLSX XLSB XLML
- write hyperlinks   :           XLML
- XLSB convert date cell to numeric on write
- fixed issue with assigning self in jszip

issues:
- closes #607 h/t @jscheid
- closes #195 h/t @asfman
This commit is contained in:
SheetJS 2017-03-28 18:03:03 -04:00
parent 085150db3b
commit b89a876076
29 changed files with 921 additions and 297 deletions

@ -8,6 +8,7 @@ changes may not be included if they are not expected to break existing code.
## Unreleased
* XLS legacy `!range` field removed
* Hyperlink tooltip is stored in the `Tooltip` field
## 0.9.6 (2017-03-25)

@ -1 +1 @@
XLSX.version = '0.9.6';
XLSX.version = '0.9.7';

@ -95,7 +95,7 @@ function ReadShift(size/*:number*/, t/*:?string*/) {
case 'cstr': size = 0; o = "";
while((w=__readUInt8(this, this.l + size++))!==0) oo.push(_getchar(w));
o = oo.join(""); break;
case 'wstr': size = 0; o = "";
case '_wstr': size = 0; o = "";
while((w=__readUInt16LE(this,this.l +size))!==0){oo.push(_getchar(w));size+=2;}
size+=2; o = oo.join(""); break;

@ -226,6 +226,15 @@ var CTYPE_XML_ROOT = writextag('Types', null, {
var CTYPE_DEFAULTS = [
['xml', 'application/xml'],
['bin', 'application/vnd.ms-excel.sheet.binary.macroEnabled.main'],
/* from test files */
['bmp', 'image/bmp'],
['png', 'image/png'],
['gif', 'image/gif'],
['emf', 'image/x-emf'],
['wmf', 'image/x-wmf'],
['jpg', 'image/jpeg'], ['jpeg', 'image/jpeg'],
['tif', 'image/tiff'], ['tiff', 'image/tiff'],
['pdf', 'application/pdf'],
['rels', type2ct.rels[0]]
].map(function(x) {
return writextag('Default', null, {'Extension':x[0], 'ContentType': x[1]});
@ -267,6 +276,7 @@ function write_ct(ct, opts)/*:string*/ {
['strs', 'styles'].forEach(f1);
['coreprops', 'extprops', 'custprops'].forEach(f3);
f3('vba');
f3('comments');
if(o.length>2){ o[o.length] = ('</Types>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}

@ -18,7 +18,7 @@ function parse_cust_props(data/*:string*/, opts) {
var type = toks[0].substring(4), text = toks[1];
/* 22.4.2.32 (CT_Variant). Omit the binary types from 22.4 (Variant Types) */
switch(type) {
case 'lpstr': case 'lpwstr': case 'bstr': case 'lpwstr':
case 'lpstr': case 'bstr': case 'lpwstr':
p[name] = unescapexml(text);
break;
case 'bool':

@ -51,7 +51,7 @@ function rgb_tint(hex, tint) {
/* 18.3.1.13 width calculations */
/* [MS-OI29500] 2.1.595 Column Width & Formatting */
var DEF_MDW = 7, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
var DEF_MDW = 6, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
function width2px(width) { return Math.floor(( width + (Math.round(128/MDW))/256 )* MDW ); }
function px2char(px) { return (Math.floor((px - 5)/MDW * 100 + 0.5))/100; }
function char2width(chr) { return (Math.round((chr * MDW + 5)/MDW*256))/256; }

@ -75,8 +75,8 @@ function parse_fills(t, styles, themes, opts) {
case '</fill>': styles.Fills.push(fill); fill = {}; break;
/* 18.8.24 gradientFill CT_GradientFill */
case '<fill>': break;
case '</fill>': styles.Fills.push(fill); fill = {}; break;
case '<gradientFill>': break;
case '</gradientFill>': styles.Fills.push(fill); fill = {}; break;
/* 18.8.32 patternFill CT_PatternFill */
case '<patternFill': case '<patternFill>':

@ -11,6 +11,18 @@ function get_sst_id(sst/*:SST*/, str/*:string*/)/*:number*/ {
sst[len] = {t:str}; sst.Count ++; sst.Unique ++; return len;
}
function col_obj_w(C/*:number*/, col) {
var p = ({min:C+1,max:C+1}/*:any*/);
/* wch (chars), wpx (pixels) */
var width = -1;
if(col.MDW) MDW = col.MDW;
if(col.width != null) p.customWidth = 1;
else if(col.wpx != null) width = px2char(col.wpx);
else if(col.wch != null) width = col.wch;
if(width > -1) { p.width = char2width(width); p.customWidth = 1; }
return p;
}
function get_cell_style(styles, cell, opts) {
var z = opts.revssf[cell.z != null ? cell.z : "General"];
for(var i = 0, len = styles.length; i != len; ++i) if(styles[i].numFmtId === z) return i;

@ -70,7 +70,7 @@ function write_ws_xml_merges(merges) {
return o + '</mergeCells>';
}
function parse_ws_xml_hlinks(s, data, rels) {
function parse_ws_xml_hlinks(s, data/*:Array<string>*/, rels) {
for(var i = 0; i != data.length; ++i) {
var val = parsexmltag(data[i], true);
if(!val.ref) return;
@ -84,6 +84,7 @@ function parse_ws_xml_hlinks(s, data, rels) {
rel = {Target: val.location, TargetMode: 'Internal'};
val.Rel = rel;
}
if(val.tooltip) { val.Tooltip = val.tooltip; delete val.tooltip; }
var rng = safe_decode_range(val.ref);
for(var R=rng.s.r;R<=rng.e.r;++R) for(var C=rng.s.c;C<=rng.e.c;++C) {
var addr = encode_cell({c:C,r:R});
@ -109,15 +110,7 @@ function write_ws_xml_cols(ws, cols)/*:string*/ {
var o = ["<cols>"], col, width;
for(var i = 0; i != cols.length; ++i) {
if(!(col = cols[i])) continue;
var p = ({min:i+1,max:i+1}/*:any*/);
/* wch (chars), wpx (pixels) */
width = -1;
if(col.MDW) MDW = col.MDW;
if(col.width);
else if(col.wpx) width = px2char(col.wpx);
else if(col.wch) width = col.wch;
if(width > -1) { p.width = char2width(width); p.customWidth= 1; }
o[o.length] = (writextag('col', null, p));
o[o.length] = (writextag('col', null, col_obj_w(i, col)));
}
o[o.length] = "</cols>";
return o.join("");
@ -328,14 +321,16 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ {
if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols']));
o[sidx = o.length] = '<sheetData/>';
if(ws['!ref'] !== undefined) {
if(ws['!ref'] != null) {
rdata = write_ws_xml_data(ws, opts, idx, wb);
if(rdata.length > 0) o[o.length] = (rdata);
}
if(o.length>sidx+1) { o[o.length] = ('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); }
if(ws['!merges'] !== undefined && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges']));
if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges']));
if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); }
delete ws['!links'];
return o.join("");
}

@ -226,7 +226,7 @@ function parse_BrtHLink(data, length, opts) {
var tooltip = parse_XLWideString(data);
var display = parse_XLWideString(data);
data.l = end;
return {rfx:rfx, relId:relId, loc:loc, tooltip:tooltip, display:display};
return {rfx:rfx, relId:relId, loc:loc, Tooltip:tooltip, display:display};
}
/* [MS-XLSB] 2.4.6 BrtArrFmla */
@ -255,6 +255,20 @@ function parse_BrtShrFmla(data, length, opts) {
return o;
}
/* [MS-XLSB] 2.4.323 BrtColInfo */
/* TODO: once XLS ColInfo is set, combine the functions */
function write_BrtColInfo(C/*:number*/, col, o) {
if(o == null) o = new_buf(18);
var p = col_obj_w(C, col);
o.write_shift(-4, C);
o.write_shift(-4, C);
o.write_shift(4, p.width * 256);
o.write_shift(4, 0/*ixfe*/); // style
o.write_shift(1, 2); // bit flag
o.write_shift(1, 0); // bit flag
return o;
}
/* [MS-XLSB] 2.1.7.61 Worksheet */
function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
@ -521,9 +535,15 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
/* TODO: something useful -- this is a stub */
function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts) {
if(cell.v === undefined) return "";
var vv = "";
var vv = ""; var olddate = null;
switch(cell.t) {
case 'b': vv = cell.v ? "1" : "0"; break;
case 'd': // no BrtCellDate :(
cell.z = cell.z || SSF._table[14];
olddate = cell.v;
cell.v = datenum((cell.v/*:any*/)); cell.t = 'n';
break;
/* falls through */
case 'n': case 'e': vv = ''+cell.v; break;
default: vv = cell.v; break;
}
@ -533,7 +553,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num
switch(cell.t) {
case 's': case 'str':
if(opts.bookSST) {
vv = get_sst_id(opts.Strings, cell.v);
vv = get_sst_id(opts.Strings, (cell.v/*:any*/));
o.t = "s"; o.v = vv;
write_record(ba, "BrtCellIsst", write_BrtCellIsst(cell, o));
} else {
@ -545,6 +565,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num
/* TODO: determine threshold for Real vs RK */
if(cell.v == (cell.v | 0) && cell.v > -1000 && cell.v < 1000) write_record(ba, "BrtCellRk", write_BrtCellRk(cell, o));
else write_record(ba, "BrtCellReal", write_BrtCellReal(cell, o));
if(olddate) { cell.t = 'd'; cell.v = olddate; }
return;
case 'b':
o.t = "b";
@ -555,7 +576,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num
write_record(ba, "BrtCellBlank", write_BrtCellBlank(cell, o));
}
function write_CELLTABLE(ba, ws, idx, opts, wb) {
function write_CELLTABLE(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbook*/) {
var range = safe_decode_range(ws['!ref'] || "A1"), ref, rr = "", cols = [];
write_record(ba, 'BrtBeginSheetData');
for(var R = range.s.r; R <= range.e.r; ++R) {
@ -582,6 +603,13 @@ function write_MERGECELLS(ba, ws/*:Worksheet*/) {
write_record(ba, 'BrtEndMergeCells');
}
function write_COLINFOS(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbook*/) {
if(!ws || !ws['!cols']) return;
write_record(ba, 'BrtBeginColInfos');
ws['!cols'].forEach(function(m, i) { if(m) write_record(ba, 'BrtColInfo', write_BrtColInfo(i, m)); });
write_record(ba, 'BrtEndColInfos');
}
function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) {
var ba = buf_array();
var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {};
@ -591,7 +619,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) {
write_record(ba, "BrtWsDim", write_BrtWsDim(r));
/* [WSVIEWS2] */
/* [WSFMTINFO] */
/* *COLINFOS */
write_COLINFOS(ba, ws, idx, opts, wb);
write_CELLTABLE(ba, ws, idx, opts, wb);
/* [BrtSheetCalcProp] */
/* [[BrtSheetProtectionIso] BrtSheetProtection] */

@ -199,8 +199,8 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
if(comments.length > 0) cell.c = comments;
if((!opts.sheetRows || opts.sheetRows > r) && cell.v !== undefined) cursheet[encode_col(c) + encode_row(r)] = cell;
if(cell.HRef) {
cell.l = {Target:cell.HRef, tooltip:cell.HRefScreenTip};
cell.HRef = cell.HRefScreenTip = undefined;
cell.l = {Target:cell.HRef, Tooltip:cell.HRefScreenTip};
delete cell.HRef; delete cell.HRefScreenTip;
}
if(cell.MergeAcross || cell.MergeDown) {
var cc = c + (parseInt(cell.MergeAcross,10)|0);
@ -787,6 +787,11 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
attr["ss:ArrayRange"] = "RC:R" + (end.r == addr.r ? "" : "[" + (end.r - addr.r) + "]") + "C" + (end.c == addr.c ? "" : "[" + (end.c - addr.c) + "]");
}
if(cell.l && cell.l.Target) {
attr["ss:HRef"] = escapexml(cell.l.Target);
if(cell.l.Tooltip) attr["x:HRefScreenTip"] = escapexml(cell.l.Tooltip);
}
if(ws['!merges']) {
var marr = ws['!merges'];
for(var mi = 0; mi != marr.length; ++mi) {
@ -822,6 +827,10 @@ function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbo
var range = safe_decode_range(ws['!ref']);
var marr = ws['!merges'] || [], mi = 0;
var o = [];
if(ws['!cols']) ws['!cols'].forEach(function(n, i) {
var p = col_obj_w(i, n);
o.push(writextag("Column",null, {"ss:Index":i+1, "ss:Width":width2px(p.width)}));
});
for(var R = range.s.r; R <= range.e.r; ++R) {
var row = ['<Row ss:Index="' + (R+1) + '">'];
for(var C = range.s.c; C <= range.e.c; ++C) {

@ -432,7 +432,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
for(rngR = val[0].s.r; rngR <= val[0].e.r; ++rngR)
for(rngC = val[0].s.c; rngC <= val[0].e.c; ++rngC)
if(out[encode_cell({c:rngC,r:rngR})])
out[encode_cell({c:rngC,r:rngR})].l.tooltip = val[1];
out[encode_cell({c:rngC,r:rngR})].l.Tooltip = val[1];
} break;
/* Comments */

1
dist/jszip.js vendored

@ -13,7 +13,6 @@ https://github.com/nodeca/pako/blob/master/LICENSE
if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();
else if("function"==typeof define&&define.amd){JSZip=e();define([],e);}
else{
if(typeof self == 'undefined' && typeof app != 'undefined') self = app;
var f;
"undefined"!=typeof window?f=window:
"undefined"!=typeof global?f=global:

30
dist/xlsx.core.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

41
dist/xlsx.full.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

637
dist/xlsx.js vendored

File diff suppressed because it is too large Load Diff

24
dist/xlsx.min.js vendored

File diff suppressed because one or more lines are too long

2
dist/xlsx.min.map vendored

File diff suppressed because one or more lines are too long

@ -11,7 +11,7 @@
| `h` | HTML rendering of the rich text (if applicable) |
| `c` | comments associated with the cell |
| `z` | number format string associated with the cell (if requested) |
| `l` | cell hyperlink object (.Target holds link, .tooltip is tooltip) |
| `l` | cell hyperlink object (.Target holds link, .Tooltip is tooltip) |
| `s` | the style/theme of the cell (if applicable) |
Built-in export utilities (such as the CSV exporter) will use the `w` text if it

17
docbits/64_cellprops.md Normal file

@ -0,0 +1,17 @@
#### Hyperlinks
Hyperlinks are stored in the `l` key of cell objects. The `Target` field of the
hyperlink object is the target of the link, including the URI fragment. Tooltips
are stored in the `Tooltip` field and are displayed when you move your mouse
over the text.
For example, the following snippet creates a link from cell `A3` to
<http://sheetjs.com> with the tip `"Find us @ SheetJS.com!"`:
```js
ws['A3'].l = { Target:"http://sheetjs.com", Tooltip:"Find us @ SheetJS.com!" };
```
Note that Excel does not automatically style hyperlinks -- they will generally
be displayed as normal text.

@ -13,7 +13,6 @@ https://github.com/nodeca/pako/blob/master/LICENSE
if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();
else if("function"==typeof define&&define.amd){JSZip=e();define([],e);}
else{
if(typeof self == 'undefined' && typeof app != 'undefined') self = app;
var f;
"undefined"!=typeof window?f=window:
"undefined"!=typeof global?f=global:

@ -1,6 +1,6 @@
{
"name": "xlsx",
"version": "0.9.6",
"version": "0.9.7",
"author": "sheetjs",
"description": "Excel (XLSB/XLSX/XLSM/XLS/XML) and ODS (ODS/FODS/UOS) spreadsheet parser and writer",
"keywords": [ "excel", "xls", "xlsx", "xlsb", "xlsm", "ods", "office", "spreadsheet" ],

41
test.js

@ -602,6 +602,19 @@ function diffsty(ws, r1,r2) {
});
}
function hlink(wb) {
var ws = wb.Sheets.Sheet1;
assert.equal(ws.A1.l.Target, "http://www.sheetjs.com");
assert.equal(ws.A2.l.Target, "http://oss.sheetjs.com");
assert.equal(ws.A3.l.Target, "http://oss.sheetjs.com#foo");
assert.equal(ws.A4.l.Target, "mailto:dev@sheetjs.com");
assert.equal(ws.A5.l.Target, "mailto:dev@sheetjs.com?subject=hyperlink");
assert.equal(ws.A6.l.Target, "../../sheetjs/Documents/Test.xlsx");
assert.equal(ws.A7.l.Target, "http://sheetjs.com");
assert.equal(ws.A7.l.Tooltip, "foo bar baz");
}
describe('parse features', function() {
if(fs.existsSync(paths.swcxlsx)) it('should have comment as part of cell properties', function(){
var X = require(modp);
@ -760,17 +773,6 @@ describe('parse features', function() {
if(typeof before != 'undefined') before(bef);
else it('before', bef);
function hlink(wb) {
var ws = wb.Sheets.Sheet1;
assert.equal(ws.A1.l.Target, "http://www.sheetjs.com");
assert.equal(ws.A2.l.Target, "http://oss.sheetjs.com");
assert.equal(ws.A3.l.Target, "http://oss.sheetjs.com#foo");
assert.equal(ws.A4.l.Target, "mailto:dev@sheetjs.com");
assert.equal(ws.A5.l.Target, "mailto:dev@sheetjs.com?subject=hyperlink");
assert.equal(ws.A6.l.Target, "../../sheetjs/Documents/Test.xlsx");
assert.equal(ws.A7.l.Target, "http://sheetjs.com");
}
it(N1, function() { hlink(wb1); });
it(N2, function() { hlink(wb2); });
it(N3, function() { hlink(wb3); });
@ -977,14 +979,27 @@ describe('roundtrip features', function() {
].forEach(function(w) {
it(w[0], function() {
var wb1 = X.readFile(w[1], {cellFormula:true});
if(w[0] == 'ods') X.writeFile(wb1, "./tmp/_.ods", {bookType:"ods"});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"buffer"}), {cellFormula:true, type:"buffer"});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"buffer"}), {type:"buffer"});
wb1.SheetNames.forEach(function(n) {
assert.equal( X.utils.sheet_to_formulae(wb1.Sheets[n]).sort().join("\n"), X.utils.sheet_to_formulae(wb2.Sheets[n]).sort().join("\n") );
});
});
});
});
describe('should preserve hyperlink', function() { [
['xlml', paths.hlxml],
//['xlsx', paths.hlxlsx], // TODO
//['xlsb', paths.hlxlsb] // TODO
].forEach(function(w) {
it(w[0], function() {
var wb1 = X.readFile(w[1]);
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"buffer"}), {type:"buffer"});
hlink(wb1);
hlink(wb2);
});
});
});
});
function password_file(x){return x.match(/^password.*\.xls$/); }

@ -444,6 +444,19 @@ function diffsty(ws, r1,r2) {
});
}
function hlink(wb) {
var ws = wb.Sheets.Sheet1;
assert.equal(ws.A1.l.Target, "http://www.sheetjs.com");
assert.equal(ws.A2.l.Target, "http://oss.sheetjs.com");
assert.equal(ws.A3.l.Target, "http://oss.sheetjs.com#foo");
assert.equal(ws.A4.l.Target, "mailto:dev@sheetjs.com");
assert.equal(ws.A5.l.Target, "mailto:dev@sheetjs.com?subject=hyperlink");
assert.equal(ws.A6.l.Target, "../../sheetjs/Documents/Test.xlsx");
assert.equal(ws.A7.l.Target, "http://sheetjs.com");
assert.equal(ws.A7.l.Tooltip, "foo bar baz");
}
describe('parse features', function() {
if(fs.existsSync(paths.swcxlsx)) it('should have comment as part of cell properties', function(){
var X = require(modp);
@ -602,17 +615,6 @@ describe('parse features', function() {
if(typeof before != 'undefined') before(bef);
else it('before', bef);
function hlink(wb) {
var ws = wb.Sheets.Sheet1;
assert.equal(ws.A1.l.Target, "http://www.sheetjs.com");
assert.equal(ws.A2.l.Target, "http://oss.sheetjs.com");
assert.equal(ws.A3.l.Target, "http://oss.sheetjs.com#foo");
assert.equal(ws.A4.l.Target, "mailto:dev@sheetjs.com");
assert.equal(ws.A5.l.Target, "mailto:dev@sheetjs.com?subject=hyperlink");
assert.equal(ws.A6.l.Target, "../../sheetjs/Documents/Test.xlsx");
assert.equal(ws.A7.l.Target, "http://sheetjs.com");
}
it(N1, function() { hlink(wb1); });
it(N2, function() { hlink(wb2); });
it(N3, function() { hlink(wb3); });
@ -825,6 +827,20 @@ describe('roundtrip features', function() {
});
});
});
describe('should preserve hyperlink', function() { [
['xlml', paths.hlxml],
//['xlsx', paths.hlxlsx], // TODO
//['xlsb', paths.hlxlsb] // TODO
].forEach(function(w) {
it(w[0], function() {
var wb1 = X.read(fs.readFileSync(w[1]), {type:"binary"});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"binary"}), {type:"binary"});
hlink(wb1);
hlink(wb2);
});
});
});
});
function password_file(x){return x.match(/^password.*\.xls$/); }

@ -12,10 +12,10 @@ var data = [
var ws_name = "SheetJS";
var wscols = [
{wch:6},
{wch:7},
{wch:6}, // "characters"
{wpx:50}, // "pixels"
{wch:10},
{wch:20}
{wpx:125}
];
@ -28,52 +28,11 @@ console.log("Columns :"); for(i=0; i!=wscols.length;++i) console.log(wscols[i]);
/* require XLSX */
if(typeof XLSX === "undefined") { try { XLSX = require('./'); } catch(e) { XLSX = require('../'); } }
/* dummy workbook constructor */
function Workbook() {
if(!(this instanceof Workbook)) return new Workbook();
this.SheetNames = [];
this.Sheets = {};
}
var wb = new Workbook();
function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
var epoch = v.getTime();
if(date1904) epoch += 1462*24*60*60*1000;
return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
}
/* blank workbook constructor */
var wb = { SheetNames: [], Sheets: {} };
/* convert an array of arrays in JS to a CSF spreadsheet */
function sheet_from_array_of_arrays(data, opts) {
var ws = {};
var range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
for(var R = 0; R != data.length; ++R) {
for(var C = 0; C != data[R].length; ++C) {
if(range.s.r > R) range.s.r = R;
if(range.s.c > C) range.s.c = C;
if(range.e.r < R) range.e.r = R;
if(range.e.c < C) range.e.c = C;
var cell = {v: data[R][C] };
if(cell.v == null) continue;
var cell_ref = XLSX.utils.encode_cell({c:C,r:R});
/* TEST: proper cell types and value handling */
if(typeof cell.v === 'number') cell.t = 'n';
else if(typeof cell.v === 'boolean') cell.t = 'b';
else if(cell.v instanceof Date) {
cell.t = 'n'; cell.z = XLSX.SSF._table[14];
cell.v = datenum(cell.v);
}
else cell.t = 's';
ws[cell_ref] = cell;
}
}
/* TEST: proper range */
if(range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
return ws;
}
var ws = sheet_from_array_of_arrays(data);
var ws = XLSX.utils.aoa_to_sheet(data, {cellDates:true});
/* TEST: add worksheet to workbook */
wb.SheetNames.push(ws_name);
@ -96,8 +55,18 @@ ws["!ref"] = "A1:E4";
/* TEST: column widths */
ws['!cols'] = wscols;
/* TEST: hyperlink note: Excel does not automatically style hyperlinks */
ws['A3'].l = { Target: "http://sheetjs.com", Tooltip: "Visit us <SheetJS.com!>" };
/* TEST: built-in format */
//ws['A1'].z = "0%"; wb.SSF[9] = "0%"; // Format Code 9
/* TEST: custom format */
//ws['B2'].z = "0.0"; wb.SSF[60] = "0.0"; // Custom
console.log("JSON Data: "); console.log(XLSX.utils.sheet_to_json(ws, {header:1}));
console.log("Worksheet Model:")
console.log(ws);
/* write file */
XLSX.writeFile(wb, 'sheetjs.xlsx', {bookSST:true});

@ -5,7 +5,7 @@
/*exported XLSX */
var XLSX = {};
(function make_xlsx(XLSX){
XLSX.version = '0.9.6';
XLSX.version = '0.9.7';
var current_codepage = 1200, current_cptable;
/*:: declare var cptable:any; */
if(typeof module !== "undefined" && typeof require !== 'undefined') {
@ -1830,7 +1830,7 @@ function ReadShift(size/*:number*/, t/*:?string*/) {
case 'cstr': size = 0; o = "";
while((w=__readUInt8(this, this.l + size++))!==0) oo.push(_getchar(w));
o = oo.join(""); break;
case 'wstr': size = 0; o = "";
case '_wstr': size = 0; o = "";
while((w=__readUInt16LE(this,this.l +size))!==0){oo.push(_getchar(w));size+=2;}
size+=2; o = oo.join(""); break;
@ -2780,6 +2780,15 @@ var CTYPE_XML_ROOT = writextag('Types', null, {
var CTYPE_DEFAULTS = [
['xml', 'application/xml'],
['bin', 'application/vnd.ms-excel.sheet.binary.macroEnabled.main'],
/* from test files */
['bmp', 'image/bmp'],
['png', 'image/png'],
['gif', 'image/gif'],
['emf', 'image/x-emf'],
['wmf', 'image/x-wmf'],
['jpg', 'image/jpeg'], ['jpeg', 'image/jpeg'],
['tif', 'image/tiff'], ['tiff', 'image/tiff'],
['pdf', 'application/pdf'],
['rels', type2ct.rels[0]]
].map(function(x) {
return writextag('Default', null, {'Extension':x[0], 'ContentType': x[1]});
@ -2821,6 +2830,7 @@ function write_ct(ct, opts)/*:string*/ {
['strs', 'styles'].forEach(f1);
['coreprops', 'extprops', 'custprops'].forEach(f3);
f3('vba');
f3('comments');
if(o.length>2){ o[o.length] = ('</Types>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}
@ -3128,7 +3138,7 @@ function parse_cust_props(data/*:string*/, opts) {
var type = toks[0].substring(4), text = toks[1];
/* 22.4.2.32 (CT_Variant). Omit the binary types from 22.4 (Variant Types) */
switch(type) {
case 'lpstr': case 'lpwstr': case 'bstr': case 'lpwstr':
case 'lpstr': case 'bstr': case 'lpwstr':
p[name] = unescapexml(text);
break;
case 'bool':
@ -5393,7 +5403,7 @@ function rgb_tint(hex, tint) {
/* 18.3.1.13 width calculations */
/* [MS-OI29500] 2.1.595 Column Width & Formatting */
var DEF_MDW = 7, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
var DEF_MDW = 6, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
function width2px(width) { return Math.floor(( width + (Math.round(128/MDW))/256 )* MDW ); }
function px2char(px) { return (Math.floor((px - 5)/MDW * 100 + 0.5))/100; }
function char2width(chr) { return (Math.round((chr * MDW + 5)/MDW*256))/256; }
@ -5527,8 +5537,8 @@ function parse_fills(t, styles, themes, opts) {
case '</fill>': styles.Fills.push(fill); fill = {}; break;
/* 18.8.24 gradientFill CT_GradientFill */
case '<fill>': break;
case '</fill>': styles.Fills.push(fill); fill = {}; break;
case '<gradientFill>': break;
case '</gradientFill>': styles.Fills.push(fill); fill = {}; break;
/* 18.8.32 patternFill CT_PatternFill */
case '<patternFill': case '<patternFill>':
@ -8812,6 +8822,18 @@ function get_sst_id(sst/*:SST*/, str/*:string*/)/*:number*/ {
sst[len] = {t:str}; sst.Count ++; sst.Unique ++; return len;
}
function col_obj_w(C/*:number*/, col) {
var p = ({min:C+1,max:C+1}/*:any*/);
/* wch (chars), wpx (pixels) */
var width = -1;
if(col.MDW) MDW = col.MDW;
if(col.width != null) p.customWidth = 1;
else if(col.wpx != null) width = px2char(col.wpx);
else if(col.wch != null) width = col.wch;
if(width > -1) { p.width = char2width(width); p.customWidth = 1; }
return p;
}
function get_cell_style(styles, cell, opts) {
var z = opts.revssf[cell.z != null ? cell.z : "General"];
for(var i = 0, len = styles.length; i != len; ++i) if(styles[i].numFmtId === z) return i;
@ -8932,7 +8954,7 @@ function write_ws_xml_merges(merges) {
return o + '</mergeCells>';
}
function parse_ws_xml_hlinks(s, data, rels) {
function parse_ws_xml_hlinks(s, data/*:Array<string>*/, rels) {
for(var i = 0; i != data.length; ++i) {
var val = parsexmltag(data[i], true);
if(!val.ref) return;
@ -8946,6 +8968,7 @@ function parse_ws_xml_hlinks(s, data, rels) {
rel = {Target: val.location, TargetMode: 'Internal'};
val.Rel = rel;
}
if(val.tooltip) { val.Tooltip = val.tooltip; delete val.tooltip; }
var rng = safe_decode_range(val.ref);
for(var R=rng.s.r;R<=rng.e.r;++R) for(var C=rng.s.c;C<=rng.e.c;++C) {
var addr = encode_cell({c:C,r:R});
@ -8971,15 +8994,7 @@ function write_ws_xml_cols(ws, cols)/*:string*/ {
var o = ["<cols>"], col, width;
for(var i = 0; i != cols.length; ++i) {
if(!(col = cols[i])) continue;
var p = ({min:i+1,max:i+1}/*:any*/);
/* wch (chars), wpx (pixels) */
width = -1;
if(col.MDW) MDW = col.MDW;
if(col.width);
else if(col.wpx) width = px2char(col.wpx);
else if(col.wch) width = col.wch;
if(width > -1) { p.width = char2width(width); p.customWidth= 1; }
o[o.length] = (writextag('col', null, p));
o[o.length] = (writextag('col', null, col_obj_w(i, col)));
}
o[o.length] = "</cols>";
return o.join("");
@ -9190,15 +9205,17 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ {
if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols']));
o[sidx = o.length] = '<sheetData/>';
if(ws['!ref'] !== undefined) {
if(ws['!ref'] != null) {
rdata = write_ws_xml_data(ws, opts, idx, wb);
if(rdata.length > 0) o[o.length] = (rdata);
}
if(o.length>sidx+1) { o[o.length] = ('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); }
if(ws['!merges'] !== undefined && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges']));
if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges']));
if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); }
delete ws['!links'];
return o.join("");
}
@ -9429,7 +9446,7 @@ function parse_BrtHLink(data, length, opts) {
var tooltip = parse_XLWideString(data);
var display = parse_XLWideString(data);
data.l = end;
return {rfx:rfx, relId:relId, loc:loc, tooltip:tooltip, display:display};
return {rfx:rfx, relId:relId, loc:loc, Tooltip:tooltip, display:display};
}
/* [MS-XLSB] 2.4.6 BrtArrFmla */
@ -9458,6 +9475,20 @@ function parse_BrtShrFmla(data, length, opts) {
return o;
}
/* [MS-XLSB] 2.4.323 BrtColInfo */
/* TODO: once XLS ColInfo is set, combine the functions */
function write_BrtColInfo(C/*:number*/, col, o) {
if(o == null) o = new_buf(18);
var p = col_obj_w(C, col);
o.write_shift(-4, C);
o.write_shift(-4, C);
o.write_shift(4, p.width * 256);
o.write_shift(4, 0/*ixfe*/); // style
o.write_shift(1, 2); // bit flag
o.write_shift(1, 0); // bit flag
return o;
}
/* [MS-XLSB] 2.1.7.61 Worksheet */
function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
if(!data) return data;
@ -9724,9 +9755,15 @@ function parse_ws_bin(data, opts, rels, wb, themes, styles)/*:Worksheet*/ {
/* TODO: something useful -- this is a stub */
function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts) {
if(cell.v === undefined) return "";
var vv = "";
var vv = ""; var olddate = null;
switch(cell.t) {
case 'b': vv = cell.v ? "1" : "0"; break;
case 'd': // no BrtCellDate :(
cell.z = cell.z || SSF._table[14];
olddate = cell.v;
cell.v = datenum((cell.v/*:any*/)); cell.t = 'n';
break;
/* falls through */
case 'n': case 'e': vv = ''+cell.v; break;
default: vv = cell.v; break;
}
@ -9736,7 +9773,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num
switch(cell.t) {
case 's': case 'str':
if(opts.bookSST) {
vv = get_sst_id(opts.Strings, cell.v);
vv = get_sst_id(opts.Strings, (cell.v/*:any*/));
o.t = "s"; o.v = vv;
write_record(ba, "BrtCellIsst", write_BrtCellIsst(cell, o));
} else {
@ -9748,6 +9785,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num
/* TODO: determine threshold for Real vs RK */
if(cell.v == (cell.v | 0) && cell.v > -1000 && cell.v < 1000) write_record(ba, "BrtCellRk", write_BrtCellRk(cell, o));
else write_record(ba, "BrtCellReal", write_BrtCellReal(cell, o));
if(olddate) { cell.t = 'd'; cell.v = olddate; }
return;
case 'b':
o.t = "b";
@ -9758,7 +9796,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num
write_record(ba, "BrtCellBlank", write_BrtCellBlank(cell, o));
}
function write_CELLTABLE(ba, ws, idx, opts, wb) {
function write_CELLTABLE(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbook*/) {
var range = safe_decode_range(ws['!ref'] || "A1"), ref, rr = "", cols = [];
write_record(ba, 'BrtBeginSheetData');
for(var R = range.s.r; R <= range.e.r; ++R) {
@ -9785,6 +9823,13 @@ function write_MERGECELLS(ba, ws/*:Worksheet*/) {
write_record(ba, 'BrtEndMergeCells');
}
function write_COLINFOS(ba, ws/*:Worksheet*/, idx/*:number*/, opts, wb/*:Workbook*/) {
if(!ws || !ws['!cols']) return;
write_record(ba, 'BrtBeginColInfos');
ws['!cols'].forEach(function(m, i) { if(m) write_record(ba, 'BrtColInfo', write_BrtColInfo(i, m)); });
write_record(ba, 'BrtEndColInfos');
}
function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) {
var ba = buf_array();
var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {};
@ -9794,7 +9839,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/) {
write_record(ba, "BrtWsDim", write_BrtWsDim(r));
/* [WSVIEWS2] */
/* [WSFMTINFO] */
/* *COLINFOS */
write_COLINFOS(ba, ws, idx, opts, wb);
write_CELLTABLE(ba, ws, idx, opts, wb);
/* [BrtSheetCalcProp] */
/* [[BrtSheetProtectionIso] BrtSheetProtection] */
@ -10674,8 +10719,8 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
if(comments.length > 0) cell.c = comments;
if((!opts.sheetRows || opts.sheetRows > r) && cell.v !== undefined) cursheet[encode_col(c) + encode_row(r)] = cell;
if(cell.HRef) {
cell.l = {Target:cell.HRef, tooltip:cell.HRefScreenTip};
cell.HRef = cell.HRefScreenTip = undefined;
cell.l = {Target:cell.HRef, Tooltip:cell.HRefScreenTip};
delete cell.HRef; delete cell.HRefScreenTip;
}
if(cell.MergeAcross || cell.MergeDown) {
var cc = c + (parseInt(cell.MergeAcross,10)|0);
@ -11262,6 +11307,11 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
attr["ss:ArrayRange"] = "RC:R" + (end.r == addr.r ? "" : "[" + (end.r - addr.r) + "]") + "C" + (end.c == addr.c ? "" : "[" + (end.c - addr.c) + "]");
}
if(cell.l && cell.l.Target) {
attr["ss:HRef"] = escapexml(cell.l.Target);
if(cell.l.Tooltip) attr["x:HRefScreenTip"] = escapexml(cell.l.Tooltip);
}
if(ws['!merges']) {
var marr = ws['!merges'];
for(var mi = 0; mi != marr.length; ++mi) {
@ -11297,6 +11347,10 @@ function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbo
var range = safe_decode_range(ws['!ref']);
var marr = ws['!merges'] || [], mi = 0;
var o = [];
if(ws['!cols']) ws['!cols'].forEach(function(n, i) {
var p = col_obj_w(i, n);
o.push(writextag("Column",null, {"ss:Index":i+1, "ss:Width":width2px(p.width)}));
});
for(var R = range.s.r; R <= range.e.r; ++R) {
var row = ['<Row ss:Index="' + (R+1) + '">'];
for(var C = range.s.c; C <= range.e.c; ++C) {
@ -11780,7 +11834,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
for(rngR = val[0].s.r; rngR <= val[0].e.r; ++rngR)
for(rngC = val[0].s.c; rngC <= val[0].e.c; ++rngC)
if(out[encode_cell({c:rngC,r:rngR})])
out[encode_cell({c:rngC,r:rngR})].l.tooltip = val[1];
out[encode_cell({c:rngC,r:rngR})].l.Tooltip = val[1];
} break;
/* Comments */

102
xlsx.js

@ -5,7 +5,7 @@
/*exported XLSX */
var XLSX = {};
(function make_xlsx(XLSX){
XLSX.version = '0.9.6';
XLSX.version = '0.9.7';
var current_codepage = 1200, current_cptable;
if(typeof module !== "undefined" && typeof require !== 'undefined') {
if(typeof cptable === 'undefined') cptable = require('./dist/cpexcel.js');
@ -1781,7 +1781,7 @@ function ReadShift(size, t) {
case 'cstr': size = 0; o = "";
while((w=__readUInt8(this, this.l + size++))!==0) oo.push(_getchar(w));
o = oo.join(""); break;
case 'wstr': size = 0; o = "";
case '_wstr': size = 0; o = "";
while((w=__readUInt16LE(this,this.l +size))!==0){oo.push(_getchar(w));size+=2;}
size+=2; o = oo.join(""); break;
@ -2728,6 +2728,15 @@ var CTYPE_XML_ROOT = writextag('Types', null, {
var CTYPE_DEFAULTS = [
['xml', 'application/xml'],
['bin', 'application/vnd.ms-excel.sheet.binary.macroEnabled.main'],
/* from test files */
['bmp', 'image/bmp'],
['png', 'image/png'],
['gif', 'image/gif'],
['emf', 'image/x-emf'],
['wmf', 'image/x-wmf'],
['jpg', 'image/jpeg'], ['jpeg', 'image/jpeg'],
['tif', 'image/tiff'], ['tiff', 'image/tiff'],
['pdf', 'application/pdf'],
['rels', type2ct.rels[0]]
].map(function(x) {
return writextag('Default', null, {'Extension':x[0], 'ContentType': x[1]});
@ -2769,6 +2778,7 @@ function write_ct(ct, opts) {
['strs', 'styles'].forEach(f1);
['coreprops', 'extprops', 'custprops'].forEach(f3);
f3('vba');
f3('comments');
if(o.length>2){ o[o.length] = ('</Types>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}
@ -3076,7 +3086,7 @@ function parse_cust_props(data, opts) {
var type = toks[0].substring(4), text = toks[1];
/* 22.4.2.32 (CT_Variant). Omit the binary types from 22.4 (Variant Types) */
switch(type) {
case 'lpstr': case 'lpwstr': case 'bstr': case 'lpwstr':
case 'lpstr': case 'bstr': case 'lpwstr':
p[name] = unescapexml(text);
break;
case 'bool':
@ -5339,7 +5349,7 @@ function rgb_tint(hex, tint) {
/* 18.3.1.13 width calculations */
/* [MS-OI29500] 2.1.595 Column Width & Formatting */
var DEF_MDW = 7, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
var DEF_MDW = 6, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
function width2px(width) { return Math.floor(( width + (Math.round(128/MDW))/256 )* MDW ); }
function px2char(px) { return (Math.floor((px - 5)/MDW * 100 + 0.5))/100; }
function char2width(chr) { return (Math.round((chr * MDW + 5)/MDW*256))/256; }
@ -5473,8 +5483,8 @@ function parse_fills(t, styles, themes, opts) {
case '</fill>': styles.Fills.push(fill); fill = {}; break;
/* 18.8.24 gradientFill CT_GradientFill */
case '<fill>': break;
case '</fill>': styles.Fills.push(fill); fill = {}; break;
case '<gradientFill>': break;
case '</gradientFill>': styles.Fills.push(fill); fill = {}; break;
/* 18.8.32 patternFill CT_PatternFill */
case '<patternFill': case '<patternFill>':
@ -8757,6 +8767,18 @@ function get_sst_id(sst, str) {
sst[len] = {t:str}; sst.Count ++; sst.Unique ++; return len;
}
function col_obj_w(C, col) {
var p = ({min:C+1,max:C+1});
/* wch (chars), wpx (pixels) */
var width = -1;
if(col.MDW) MDW = col.MDW;
if(col.width != null) p.customWidth = 1;
else if(col.wpx != null) width = px2char(col.wpx);
else if(col.wch != null) width = col.wch;
if(width > -1) { p.width = char2width(width); p.customWidth = 1; }
return p;
}
function get_cell_style(styles, cell, opts) {
var z = opts.revssf[cell.z != null ? cell.z : "General"];
for(var i = 0, len = styles.length; i != len; ++i) if(styles[i].numFmtId === z) return i;
@ -8891,6 +8913,7 @@ function parse_ws_xml_hlinks(s, data, rels) {
rel = {Target: val.location, TargetMode: 'Internal'};
val.Rel = rel;
}
if(val.tooltip) { val.Tooltip = val.tooltip; delete val.tooltip; }
var rng = safe_decode_range(val.ref);
for(var R=rng.s.r;R<=rng.e.r;++R) for(var C=rng.s.c;C<=rng.e.c;++C) {
var addr = encode_cell({c:C,r:R});
@ -8916,15 +8939,7 @@ function write_ws_xml_cols(ws, cols) {
var o = ["<cols>"], col, width;
for(var i = 0; i != cols.length; ++i) {
if(!(col = cols[i])) continue;
var p = ({min:i+1,max:i+1});
/* wch (chars), wpx (pixels) */
width = -1;
if(col.MDW) MDW = col.MDW;
if(col.width);
else if(col.wpx) width = px2char(col.wpx);
else if(col.wch) width = col.wch;
if(width > -1) { p.width = char2width(width); p.customWidth= 1; }
o[o.length] = (writextag('col', null, p));
o[o.length] = (writextag('col', null, col_obj_w(i, col)));
}
o[o.length] = "</cols>";
return o.join("");
@ -9135,15 +9150,17 @@ function write_ws_xml(idx, opts, wb) {
if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols']));
o[sidx = o.length] = '<sheetData/>';
if(ws['!ref'] !== undefined) {
if(ws['!ref'] != null) {
rdata = write_ws_xml_data(ws, opts, idx, wb);
if(rdata.length > 0) o[o.length] = (rdata);
}
if(o.length>sidx+1) { o[o.length] = ('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); }
if(ws['!merges'] !== undefined && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges']));
if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges']));
if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); }
delete ws['!links'];
return o.join("");
}
@ -9374,7 +9391,7 @@ function parse_BrtHLink(data, length, opts) {
var tooltip = parse_XLWideString(data);
var display = parse_XLWideString(data);
data.l = end;
return {rfx:rfx, relId:relId, loc:loc, tooltip:tooltip, display:display};
return {rfx:rfx, relId:relId, loc:loc, Tooltip:tooltip, display:display};
}
/* [MS-XLSB] 2.4.6 BrtArrFmla */
<