version bump 0.10.5

- HTML output new preamble, <table> should not be in header/footer
- XLS Built-In defined names translated to XLSX _xlnm form
- README: streaming read strategies
- If approximation error is sufficiently small, assume MDW is 6
- TS rename Utils type to XLSX$Utils (see #618)
This commit is contained in:
SheetJS 2017-06-09 21:47:42 -04:00
parent da1c111275
commit 5d997df419
28 changed files with 446 additions and 204 deletions

@ -5,7 +5,7 @@ but not limited to API changes and file location changes. Minor behavioral
changes may not be included if they are not expected to break existing code.
## Unreleased (2017-06-??)
## 0.10.5 (2017-06-09)
* HTML Table output header/footer should not include `<table>` tag

@ -60,7 +60,7 @@ enhancements, additional features by request, and dedicated support.
- [Philosophy](#philosophy)
- [Parsing Workbooks](#parsing-workbooks)
* [Complete Examples](#complete-examples)
* [Note on Streaming Read](#note-on-streaming-read)
* [Streaming Read](#streaming-read)
- [Working with the Workbook](#working-with-the-workbook)
* [Complete Examples](#complete-examples-1)
- [Writing Workbooks](#writing-workbooks)
@ -429,7 +429,10 @@ On Windows XP and up you can get the base64 encoding using `certutil`:
- <http://oss.sheetjs.com/js-xlsx/ajax.html> XMLHttpRequest
### Note on Streaming Read
### Streaming Read
<details>
<summary><b>Why is there no Streaming Read API?</b> (click to show)</summary>
The most common and interesting formats (XLS, XLSX/M, XLSB, ODS) are ultimately
ZIP or CFB containers of files. Neither format puts the directory structure at
@ -437,9 +440,61 @@ the beginning of the file: ZIP files place the Central Directory records at the
end of the logical file, while CFB files can place the FAT structure anywhere in
the file! As a result, to properly handle these formats, a streaming function
would have to buffer the entire file before commencing. That belies the
expectations of streaming, so we do not provide any streaming read API. If you
really want to stream, there are node modules like `concat-stream` that will do
the buffering for you.
expectations of streaming, so we do not provide any streaming read API.
</details>
When dealing with Readable Streams, the easiest approach is to buffer the stream
and process the whole thing at the end. This can be done with a temporary file
or by explicitly concatenating the stream:
<details>
<summary><b>Explicitly concatenating streams</b> (click to show)</summary>
```js
var fs = require('fs');
var XLSX = require('xlsx');
function process_RS(stream/*:ReadStream*/, cb/*:(wb:Workbook)=>void*/)/*:void*/{
var buffers = [];
stream.on('data', function(data) { buffers.push(data); });
stream.on('end', function() {
var buffer = Buffer.concat(buffers);
var workbook = XLSX.read(buffer, {type:"buffer"});
/* DO SOMETHING WITH workbook IN THE CALLBACK */
cb(workbook);
});
}
```
More robust solutions are available using modules like `concat-stream`.
</details>
<details>
<summary><b>Writing to filesystem first</b> (click to show)</summary>
This example uses [`tempfile`](https://npm.im/tempfile) for filenames:
```js
var fs = require('fs'), tempfile = require('tempfile');
var XLSX = require('xlsx');
function process_RS(stream/*:ReadStream*/, cb/*:(wb:Workbook)=>void*/)/*:void*/{
var fname = tempfile('.sheetjs');
console.log(fname);
var ostream = fs.createWriteStream(fname);
stream.pipe(ostream);
ostream.on('finish', function() {
var workbook = XLSX.readFile(fname);
fs.unlinkSync(fname);
/* DO SOMETHING WITH workbook IN THE CALLBACK */
cb(workbook);
});
}
```
</details>
## Working with the Workbook
@ -558,8 +613,8 @@ saveAs(new Blob([s2ab(wbout)],{type:"application/octet-stream"}), "test.xlsx");
### Streaming Write
The streaming write functions are available in the `XLSX.stream` object. They
take the same arguments as the normal write functions but return a readable
stream. They are only exposed in node.
take the same arguments as the normal write functions but return a Readable
Stream. They are only exposed in NodeJS.
- `XLSX.stream.to_csv` is the streaming version of `XLSX.utils.sheet_to_csv`.
- `XLSX.stream.to_html` is the streaming version of `XLSX.utils.sheet_to_html`.

@ -1 +1 @@
XLSX.version = '0.10.4';
XLSX.version = '0.10.5';

@ -15,5 +15,5 @@ type EvertArrType = {[string]:Array<string>};
type StringConv = {(string):string};
type WriteObjStrFactory = {from_sheet(ws:Worksheet, o:any):string};
type WriteObjStrFactory = {from_sheet(ws:Worksheet, o:any, wb:?Workbook):string};
*/

@ -441,10 +441,11 @@ function parse_SupBook(blob, length, opts) {
var end = blob.l + length;
var ctab = blob.read_shift(2);
var cch = blob.read_shift(2);
var virtPath;
if(cch >=0x01 && cch <=0xff) virtPath = parse_XLUnicodeStringNoCch(blob, cch);
var rgst = blob.read_shift(end - blob.l);
opts.sbcch = cch;
if(cch == 0x0401 || cch == 0x3A01) return [cch, ctab];
if(cch < 0x01 || cch >0xff) throw new Error("Unexpected SupBook type: "+cch);
var virtPath = parse_XLUnicodeStringNoCch(blob, cch);
var rgst = blob.read_shift(end - blob.l);
return [cch, ctab, virtPath, rgst];
}
@ -469,6 +470,22 @@ function parse_ExternName(blob, length, opts) {
}
/* 2.4.150 TODO */
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
function parse_Lbl(blob, length, opts) {
var target = blob.l + length;
var flags = blob.read_shift(2);
@ -482,6 +499,7 @@ function parse_Lbl(blob, length, opts) {
blob.l += 4;
}
var name = parse_XLUnicodeStringNoCch(blob, cch, opts);
if(flags & 0x20) name = XLSLblBuiltIn[name.charCodeAt(0)];
var npflen = target - blob.l; if(opts && opts.biff == 2) --npflen;
var rgce = target == blob.l || cce == 0 ? [] : parse_NameParsedFormula(blob, npflen, opts, cce);
return {

@ -60,8 +60,8 @@ function char2width_(chr) { return (((chr * MDW + 5)/MDW*256))/256; }
function cycle_width(collw) { return char2width(px2char(width2px(collw))); }
/* XLSX/XLSB/XLS specify width in units of MDW */
function find_mdw_colw(collw) {
var delta = Infinity, _MDW = MIN_MDW;
for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
var delta = Math.abs(collw - cycle_width(collw)), _MDW = MDW;
if(delta > 0.005) for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
MDW = _MDW;
}
/* XLML specifies width in terms of pixels */

@ -260,11 +260,13 @@ function write_numFmts(NF/*:{[n:number]:string}*/, opts) {
}
/* 18.8.10 cellXfs CT_CellXfs */
var cellXF_uint = [ "numFmtId", "fillId", "fontId", "borderId", "xfId" ];
var cellXF_bool = [ "applyAlignment", "applyBorder", "applyFill", "applyFont", "applyNumberFormat", "applyProtection", "pivotButton", "quotePrefix" ];
function parse_cellXfs(t, styles, opts) {
styles.CellXf = [];
var xf;
t[0].match(tagregex).forEach(function(x) {
var y = parsexmltag(x);
var y = parsexmltag(x), i = 0;
switch(y[0]) {
case '<cellXfs': case '<cellXfs>': case '<cellXfs/>': case '</cellXfs>': break;
@ -272,8 +274,10 @@ function parse_cellXfs(t, styles, opts) {
case '<xf':
xf = y;
delete xf[0];
if(xf.numFmtId) xf.numFmtId = parseInt(xf.numFmtId, 10);
if(xf.fillId) xf.fillId = parseInt(xf.fillId, 10);
for(i = 0; i < cellXF_uint.length; ++i) if(xf[cellXF_uint[i]])
xf[cellXF_uint[i]] = parseInt(xf[cellXF_uint[i]], 10);
for(i = 0; i < cellXF_bool.length; ++i) if(xf[cellXF_bool[i]])
xf[cellXF_bool[i]] = parsexmlbool(xf[cellXF_bool[i]], "");
styles.CellXf.push(xf); break;
case '</xf>': break;

@ -19,7 +19,7 @@ function parse_ws_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksh
var refguess/*:Range*/ = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }/*:any*/);
var data1 = "", data2 = "";
var mtch/*:?any*/ =data.match(sheetdataregex);
var mtch/*:?any*/ = data.match(sheetdataregex);
if(mtch) {
data1 = data.substr(0, mtch.index);
data2 = data.substr(mtch.index + mtch[0].length);

@ -256,7 +256,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
row = xlml_parsexmltag(Rn[0]);
if(row.Index) r = +row.Index - 1;
rowobj = {};
if(row.AutoFitHeight == "0") {
if(row.AutoFitHeight == "0" || row.Height) {
rowobj.hpx = parseInt(row.Height, 10); rowobj.hpt = px2pt(rowobj.hpx);
rowinfo[r] = rowobj;
}

@ -274,7 +274,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
supbooks.names.push(last_lbl);
if(!supbooks[0]) supbooks[0] = [];
supbooks[supbooks.length-1].push(val);
if(val.Name == "\r" && val.itab > 0)
if(val.Name == "_xlnm._FilterDatabase" && val.itab > 0)
if(val.rgce && val.rgce[0] && val.rgce[0][0] && val.rgce[0][0][0] == 'PtgArea3d')
FilterDatabases[val.itab - 1] = { ref: encode_range(val.rgce[0][0][1][2]) };
break;

@ -82,7 +82,7 @@ function write_biff_ws(ba/*:BufArray*/, ws/*:Worksheet*/, idx/*:number*/, opts,
for(var C = range.s.c; C <= range.e.c; ++C) {
if(R === range.s.r) cols[C] = encode_col(C);
ref = cols[C] + rr;
var cell = dense ? ws[R][C] : ws[ref];
var cell = dense ? (ws[R]||[])[C] : ws[ref];
if(!cell) continue;
/* write cell */
write_ws_biff_cell(ba, cell, R, C, opts);

@ -73,21 +73,29 @@ var HTML_ = (function() {
if(RS > 1) sp.rowspan = RS;
if(CS > 1) sp.colspan = CS;
if(o.editable) sp.contenteditable = "true";
sp.id = "sjs-" + coord;
oo.push(writextag('td', w, sp));
}
return "<tr>" + oo.join("") + "</tr>";
var preamble = "<tr>";
return preamble + oo.join("") + "</tr>";
}
var _BEGIN = "<html><head><title>SheetJS Table Export</title></head><body><table>";
var _END = "</table></body></html>";
function sheet_to_html(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/)/*:string*/ {
function make_html_preamble(ws/*:Worksheet*/, R/*:Range*/, o/*:Sheet2HTMLOpts*/)/*:string*/ {
var out = [];
return out.join("") + '<table>';
}
var _BEGIN = '<html><head><meta charset="utf-8"/><title>SheetJS Table Export</title></head><body>';
var _END = '</body></html>';
function sheet_to_html(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/, wb/*:?Workbook*/)/*:string*/ {
var o = opts || {};
var out/*:Array<string>*/ = [];
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o));
var header = o.header != null ? o.header : _BEGIN;
var footer = o.footer != null ? o.footer : _END;
return header + out.join("") + footer ;
var out/*:Array<string>*/ = [header];
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
out.push(make_html_preamble(ws, r, o));
for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o));
out.push("</table>" + footer);
return out.join("");
}
return {
@ -96,6 +104,7 @@ var HTML_ = (function() {
_row: make_html_row,
BEGIN: _BEGIN,
END: _END,
_preamble: make_html_preamble,
from_sheet: sheet_to_html
};
})();

@ -4,7 +4,7 @@ function write_obj_str(factory/*:WriteObjStrFactory*/) {
var idx = 0;
for(var i=0;i<wb.SheetNames.length;++i) if(wb.SheetNames[i] == o.sheet) idx=i;
if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
return factory.from_sheet(wb.Sheets[wb.SheetNames[idx]], o);
return factory.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb);
};
}

@ -29,23 +29,25 @@ if(has_buf && typeof require != 'undefined') (function() {
return stream;
};
var write_html_stream = function(sheet/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/) {
var write_html_stream = function(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/) {
var stream = Readable();
var o = opts == null ? {} : opts;
var r = decode_range(sheet['!ref']), cell/*:Cell*/;
o.dense = Array.isArray(sheet);
stream.push(HTML_.BEGIN);
var o = opts || {};
var header = o.header != null ? o.header : HTML_.BEGIN;
var footer = o.footer != null ? o.footer : HTML_.END;
stream.push(header);
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
stream.push(HTML_._preamble(ws, r, o));
var R = r.s.r;
var end = false;
stream._read = function() {
if(R > r.e.r) {
if(!end) { end = true; stream.push(HTML_.END); }
if(!end) { end = true; stream.push("</table>" + footer); }
return stream.push(null);
}
while(R <= r.e.r) {
stream.push(HTML_._row(sheet, r, R, o));
stream.push(HTML_._row(ws, r, R, o));
++R;
break;
}

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

30
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

121
dist/xlsx.js vendored

@ -6,7 +6,7 @@
/*global global, exports, module, require:false, process:false, Buffer:false */
var XLSX = {};
(function make_xlsx(XLSX){
XLSX.version = '0.10.4';
XLSX.version = '0.10.5';
var current_codepage = 1200;
/*global cptable:true */
if(typeof module !== "undefined" && typeof require !== 'undefined') {
@ -99,7 +99,7 @@ var chr0 = /\u0000/g, chr1 = /[\u0001-\u0006]/;
/*jshint -W041 */
var SSF = {};
var make_ssf = function make_ssf(SSF){
SSF.version = '0.9.3';
SSF.version = '0.9.4';
function _strrev(x) { var o = "", i = x.length-1; while(i>=0) o += x.charAt(i--); return o; }
function fill(c,l) { var o = ""; while(o.length < l) o+=c; return o; }
function pad0(v,d){var t=""+v; return t.length>=d?t:fill('0',d-t.length)+t;}
@ -395,7 +395,7 @@ function write_num_f1(r, aval, sign) {
function write_num_f2(r, aval, sign) {
return sign + (aval === 0 ? "" : ""+aval) + fill(" ", r[1].length + 2 + r[4].length);
}
var dec1 = /^#*0*\.(0+)/;
var dec1 = /^#*0*\.([0#]+)/;
var closeparen = /\).*[0#]/;
var phone = /\(###\) ###\\?-####/;
function hashq(str) {
@ -442,7 +442,7 @@ function write_num_flt(type, fmt, val) {
if((r = fmt.match(frac1))) return write_num_f1(r, aval, sign);
if(fmt.match(/^#+0+$/)) return sign + pad0r(aval,fmt.length - fmt.indexOf("0"));
if((r = fmt.match(dec1))) {
o = rnd(val, r[1].length).replace(/^([^\.]+)$/,"$1."+r[1]).replace(/\.$/,"."+r[1]).replace(/\.(\d*)$/,function($$, $1) { return "." + $1 + fill("0", r[1].length-$1.length); });
o = rnd(val, r[1].length).replace(/^([^\.]+)$/,"$1."+hashq(r[1])).replace(/\.$/,"."+hashq(r[1])).replace(/\.(\d*)$/,function($$, $1) { return "." + $1 + fill("0", hashq(r[1]).length-$1.length); });
return fmt.indexOf("0.") !== -1 ? o : o.replace(/^0\./,".");
}
fmt = fmt.replace(/^#+([0.])/, "$1");
@ -558,9 +558,9 @@ function write_num_int(type, fmt, val) {
if((r = fmt.match(frac1))) return write_num_f2(r, aval, sign);
if(fmt.match(/^#+0+$/)) return sign + pad0(aval,fmt.length - fmt.indexOf("0"));
if((r = fmt.match(dec1))) {
o = (""+val).replace(/^([^\.]+)$/,"$1."+r[1]).replace(/\.$/,"."+r[1]);
o = (""+val).replace(/^([^\.]+)$/,"$1."+hashq(r[1])).replace(/\.$/,"."+hashq(r[1]));
o = o.replace(/\.(\d*)$/,function($$, $1) {
return "." + $1 + fill("0", r[1].length-$1.length); });
return "." + $1 + fill("0", hashq(r[1]).length-$1.length); });
return fmt.indexOf("0.") !== -1 ? o : o.replace(/^0\./,".");
}
fmt = fmt.replace(/^#+([0.])/, "$1");
@ -658,9 +658,9 @@ function fmt_is_date(fmt) {
case 'M': case 'D': case 'Y': case 'H': case 'S': case 'E':
/* falls through */
case 'm': case 'd': case 'y': case 'h': case 's': case 'e': case 'g': return true;
case 'A':
if(fmt.substr(i, 3) === "A/P") return true;
if(fmt.substr(i, 5) === "AM/PM") return true;
case 'A': case 'a':
if(fmt.substr(i, 3).toUpperCase() === "A/P") return true;
if(fmt.substr(i, 5).toUpperCase() === "AM/PM") return true;
++i; break;
case '[':
o = c;
@ -717,11 +717,11 @@ function eval_fmt(fmt, v, opts, flen) {
if(c === 'm' && lst.toLowerCase() === 'h') c = 'M';
if(c === 'h') c = hr;
out[out.length] = {t:c, v:o}; lst = c; break;
case 'A':
q={t:c, v:"A"};
case 'A': case 'a':
q={t:c, v:c};
if(dt==null) dt=parse_date_code(v, opts);
if(fmt.substr(i, 3) === "A/P") { if(dt!=null) q.v = dt.H >= 12 ? "P" : "A"; q.t = 'T'; hr='h';i+=3;}
else if(fmt.substr(i,5) === "AM/PM") { if(dt!=null) q.v = dt.H >= 12 ? "PM" : "AM"; q.t = 'T'; i+=5; hr='h'; }
if(fmt.substr(i, 3).toUpperCase() === "A/P") { if(dt!=null) q.v = dt.H >= 12 ? "P" : "A"; q.t = 'T'; hr='h';i+=3;}
else if(fmt.substr(i,5).toUpperCase() === "AM/PM") { if(dt!=null) q.v = dt.H >= 12 ? "PM" : "AM"; q.t = 'T'; i+=5; hr='h'; }
else { q.t = "t"; ++i; }
if(dt==null && q.t === 'T') return "";
out[out.length] = q; lst = c; break;
@ -758,7 +758,7 @@ function eval_fmt(fmt, v, opts, flen) {
out[out.length] = {t:'D', v:o}; break;
case ' ': out[out.length] = {t:c, v:c}; ++i; break;
default:
if(",$-+/():!^&'~{}<>=€acfijklopqrtuvwxz".indexOf(c) === -1) throw new Error('unrecognized character ' + c + ' in ' + fmt);
if(",$-+/():!^&'~{}<>=€acfijklopqrtuvwxzP".indexOf(c) === -1) throw new Error('unrecognized character ' + c + ' in ' + fmt);
out[out.length] = {t:'t', v:c}; ++i; break;
}
}
@ -4433,10 +4433,11 @@ function parse_SupBook(blob, length, opts) {
var end = blob.l + length;
var ctab = blob.read_shift(2);
var cch = blob.read_shift(2);
var virtPath;
if(cch >=0x01 && cch <=0xff) virtPath = parse_XLUnicodeStringNoCch(blob, cch);
var rgst = blob.read_shift(end - blob.l);
opts.sbcch = cch;
if(cch == 0x0401 || cch == 0x3A01) return [cch, ctab];
if(cch < 0x01 || cch >0xff) throw new Error("Unexpected SupBook type: "+cch);
var virtPath = parse_XLUnicodeStringNoCch(blob, cch);
var rgst = blob.read_shift(end - blob.l);
return [cch, ctab, virtPath, rgst];
}
@ -4461,6 +4462,22 @@ function parse_ExternName(blob, length, opts) {
}
/* 2.4.150 TODO */
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
function parse_Lbl(blob, length, opts) {
var target = blob.l + length;
var flags = blob.read_shift(2);
@ -4474,6 +4491,7 @@ function parse_Lbl(blob, length, opts) {
blob.l += 4;
}
var name = parse_XLUnicodeStringNoCch(blob, cch, opts);
if(flags & 0x20) name = XLSLblBuiltIn[name.charCodeAt(0)];
var npflen = target - blob.l; if(opts && opts.biff == 2) --npflen;
var rgce = target == blob.l || cce == 0 ? [] : parse_NameParsedFormula(blob, npflen, opts, cce);
return {
@ -6706,8 +6724,8 @@ function char2width_(chr) { return (((chr * MDW + 5)/MDW*256))/256; }
function cycle_width(collw) { return char2width(px2char(width2px(collw))); }
/* XLSX/XLSB/XLS specify width in units of MDW */
function find_mdw_colw(collw) {
var delta = Infinity, _MDW = MIN_MDW;
for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
var delta = Math.abs(collw - cycle_width(collw)), _MDW = MDW;
if(delta > 0.005) for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
MDW = _MDW;
}
/* XLML specifies width in terms of pixels */
@ -7024,11 +7042,13 @@ function write_numFmts(NF, opts) {
}
/* 18.8.10 cellXfs CT_CellXfs */
var cellXF_uint = [ "numFmtId", "fillId", "fontId", "borderId", "xfId" ];
var cellXF_bool = [ "applyAlignment", "applyBorder", "applyFill", "applyFont", "applyNumberFormat", "applyProtection", "pivotButton", "quotePrefix" ];
function parse_cellXfs(t, styles, opts) {
styles.CellXf = [];
var xf;
t[0].match(tagregex).forEach(function(x) {
var y = parsexmltag(x);
var y = parsexmltag(x), i = 0;
switch(y[0]) {
case '<cellXfs': case '<cellXfs>': case '<cellXfs/>': case '</cellXfs>': break;
@ -7036,8 +7056,10 @@ function parse_cellXfs(t, styles, opts) {
case '<xf':
xf = y;
delete xf[0];
if(xf.numFmtId) xf.numFmtId = parseInt(xf.numFmtId, 10);
if(xf.fillId) xf.fillId = parseInt(xf.fillId, 10);
for(i = 0; i < cellXF_uint.length; ++i) if(xf[cellXF_uint[i]])
xf[cellXF_uint[i]] = parseInt(xf[cellXF_uint[i]], 10);
for(i = 0; i < cellXF_bool.length; ++i) if(xf[cellXF_bool[i]])
xf[cellXF_bool[i]] = parsexmlbool(xf[cellXF_bool[i]], "");
styles.CellXf.push(xf); break;
case '</xf>': break;
@ -10639,7 +10661,7 @@ function parse_ws_xml(data, opts, rels, wb, themes, styles) {
var refguess = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} });
var data1 = "", data2 = "";
var mtch =data.match(sheetdataregex);
var mtch = data.match(sheetdataregex);
if(mtch) {
data1 = data.substr(0, mtch.index);
data2 = data.substr(mtch.index + mtch[0].length);
@ -13051,7 +13073,7 @@ for(var cma = c; cma <= cc; ++cma) {
row = xlml_parsexmltag(Rn[0]);
if(row.Index) r = +row.Index - 1;
rowobj = {};
if(row.AutoFitHeight == "0") {
if(row.AutoFitHeight == "0" || row.Height) {
rowobj.hpx = parseInt(row.Height, 10); rowobj.hpt = px2pt(rowobj.hpx);
rowinfo[r] = rowobj;
}
@ -14149,7 +14171,7 @@ wb.opts.Date1904 = Workbook.WBProps.date1904 = val; break;
supbooks.names.push(last_lbl);
if(!supbooks[0]) supbooks[0] = [];
supbooks[supbooks.length-1].push(val);
if(val.Name == "\r" && val.itab > 0)
if(val.Name == "_xlnm._FilterDatabase" && val.itab > 0)
if(val.rgce && val.rgce[0] && val.rgce[0][0] && val.rgce[0][0][0] == 'PtgArea3d')
FilterDatabases[val.itab - 1] = { ref: encode_range(val.rgce[0][0][1][2]) };
break;
@ -16047,7 +16069,7 @@ function write_biff_ws(ba, ws, idx, opts, wb) {
for(var C = range.s.c; C <= range.e.c; ++C) {
if(R === range.s.r) cols[C] = encode_col(C);
ref = cols[C] + rr;
var cell = dense ? ws[R][C] : ws[ref];
var cell = dense ? (ws[R]||[])[C] : ws[ref];
if(!cell) continue;
/* write cell */
write_ws_biff_cell(ba, cell, R, C, opts);
@ -16146,21 +16168,29 @@ var HTML_ = (function() {
if(RS > 1) sp.rowspan = RS;
if(CS > 1) sp.colspan = CS;
if(o.editable) sp.contenteditable = "true";
sp.id = "sjs-" + coord;
oo.push(writextag('td', w, sp));
}
return "<tr>" + oo.join("") + "</tr>";
var preamble = "<tr>";
return preamble + oo.join("") + "</tr>";
}
var _BEGIN = "<html><head><title>SheetJS Table Export</title></head><body><table>";
var _END = "</table></body></html>";
function sheet_to_html(ws, opts) {
var o = opts || {};
function make_html_preamble(ws, R, o) {
var out = [];
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o));
return out.join("") + '<table>';
}
var _BEGIN = '<html><head><meta charset="utf-8"/><title>SheetJS Table Export</title></head><body>';
var _END = '</body></html>';
function sheet_to_html(ws, opts, wb) {
var o = opts || {};
var header = o.header != null ? o.header : _BEGIN;
var footer = o.footer != null ? o.footer : _END;
return header + out.join("") + footer ;
var out = [header];
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
out.push(make_html_preamble(ws, r, o));
for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o));
out.push("</table>" + footer);
return out.join("");
}
return {
@ -16169,6 +16199,7 @@ var HTML_ = (function() {
_row: make_html_row,
BEGIN: _BEGIN,
END: _END,
_preamble: make_html_preamble,
from_sheet: sheet_to_html
};
})();
@ -16871,7 +16902,7 @@ function write_obj_str(factory) {
var idx = 0;
for(var i=0;i<wb.SheetNames.length;++i) if(wb.SheetNames[i] == o.sheet) idx=i;
if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
return factory.from_sheet(wb.Sheets[wb.SheetNames[idx]], o);
return factory.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb);
};
}
@ -17808,23 +17839,25 @@ if(has_buf && typeof require != 'undefined') (function() {
return stream;
};
var write_html_stream = function(sheet, opts) {
var write_html_stream = function(ws, opts) {
var stream = Readable();
var o = opts == null ? {} : opts;
var r = decode_range(sheet['!ref']), cell;
o.dense = Array.isArray(sheet);
stream.push(HTML_.BEGIN);
var o = opts || {};
var header = o.header != null ? o.header : HTML_.BEGIN;
var footer = o.footer != null ? o.footer : HTML_.END;
stream.push(header);
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
stream.push(HTML_._preamble(ws, r, o));
var R = r.s.r;
var end = false;
stream._read = function() {
if(R > r.e.r) {
if(!end) { end = true; stream.push(HTML_.END); }
if(!end) { end = true; stream.push("</table>" + footer); }
return stream.push(null);
}
while(R <= r.e.r) {
stream.push(HTML_._row(sheet, r, R, o));
stream.push(HTML_._row(ws, r, R, o));
++R;
break;
}

28
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

@ -1,4 +1,7 @@
### Note on Streaming Read
### Streaming Read
<details>
<summary><b>Why is there no Streaming Read API?</b> (click to show)</summary>
The most common and interesting formats (XLS, XLSX/M, XLSB, ODS) are ultimately
ZIP or CFB containers of files. Neither format puts the directory structure at
@ -6,7 +9,59 @@ the beginning of the file: ZIP files place the Central Directory records at the
end of the logical file, while CFB files can place the FAT structure anywhere in
the file! As a result, to properly handle these formats, a streaming function
would have to buffer the entire file before commencing. That belies the
expectations of streaming, so we do not provide any streaming read API. If you
really want to stream, there are node modules like `concat-stream` that will do
the buffering for you.
expectations of streaming, so we do not provide any streaming read API.
</details>
When dealing with Readable Streams, the easiest approach is to buffer the stream
and process the whole thing at the end. This can be done with a temporary file
or by explicitly concatenating the stream:
<details>
<summary><b>Explicitly concatenating streams</b> (click to show)</summary>
```js
var fs = require('fs');
var XLSX = require('xlsx');
function process_RS(stream/*:ReadStream*/, cb/*:(wb:Workbook)=>void*/)/*:void*/{
var buffers = [];
stream.on('data', function(data) { buffers.push(data); });
stream.on('end', function() {
var buffer = Buffer.concat(buffers);
var workbook = XLSX.read(buffer, {type:"buffer"});
/* DO SOMETHING WITH workbook IN THE CALLBACK */
cb(workbook);
});
}
```
More robust solutions are available using modules like `concat-stream`.
</details>
<details>
<summary><b>Writing to filesystem first</b> (click to show)</summary>
This example uses [`tempfile`](https://npm.im/tempfile) for filenames:
```js
var fs = require('fs'), tempfile = require('tempfile');
var XLSX = require('xlsx');
function process_RS(stream/*:ReadStream*/, cb/*:(wb:Workbook)=>void*/)/*:void*/{
var fname = tempfile('.sheetjs');
console.log(fname);
var ostream = fs.createWriteStream(fname);
stream.pipe(ostream);
ostream.on('finish', function() {
var workbook = XLSX.readFile(fname);
fs.unlinkSync(fname);
/* DO SOMETHING WITH workbook IN THE CALLBACK */
cb(workbook);
});
}
```
</details>

@ -1,8 +1,8 @@
### Streaming Write
The streaming write functions are available in the `XLSX.stream` object. They
take the same arguments as the normal write functions but return a readable
stream. They are only exposed in node.
take the same arguments as the normal write functions but return a Readable
Stream. They are only exposed in NodeJS.
- `XLSX.stream.to_csv` is the streaming version of `XLSX.utils.sheet_to_csv`.
- `XLSX.stream.to_html` is the streaming version of `XLSX.utils.sheet_to_html`.

@ -8,7 +8,7 @@
- [Philosophy](README.md#philosophy)
- [Parsing Workbooks](README.md#parsing-workbooks)
* [Complete Examples](README.md#complete-examples)
* [Note on Streaming Read](README.md#note-on-streaming-read)
* [Streaming Read](README.md#streaming-read)
- [Working with the Workbook](README.md#working-with-the-workbook)
* [Complete Examples](README.md#complete-examples-1)
- [Writing Workbooks](README.md#writing-workbooks)

@ -1,6 +1,6 @@
{
"name": "xlsx",
"version": "0.10.4",
"version": "0.10.5",
"author": "sheetjs",
"description": "Excel (XLSB/XLSX/XLS/XML) ODS and other spreadsheet format (CSV/DIF/DBF/SYLK) parser and writer",
"keywords": [ "excel", "xls", "xlsx", "xlsb", "xlsm", "ods", "csv", "dbf", "dif", "sylk", "office", "spreadsheet" ],

4
types/index.d.ts vendored

@ -13,7 +13,7 @@ export function writeFile(data: WorkBook, filename: string, opts?: WritingOption
/** Attempts to write the workbook data */
export function write(data: WorkBook, opts?: WritingOptions): any;
export const utils: Utils;
export const utils: XLSX$Utils;
export const stream: StreamUtils;
/** Number Format (either a string or an index to the format table) */
@ -571,7 +571,7 @@ export interface Table2SheetOpts {
/**
* General utilities
*/
export interface Utils {
export interface XLSX$Utils {
/* --- Import Functions --- */
/** Converts an array of arrays of JS data to a worksheet. */

@ -6,7 +6,7 @@
/*global global, exports, module, require:false, process:false, Buffer:false */
var XLSX = {};
(function make_xlsx(XLSX){
XLSX.version = '0.10.4';
XLSX.version = '0.10.5';
var current_codepage = 1200;
/*:: declare var cptable:any; */
/*global cptable:true */
@ -113,7 +113,7 @@ type EvertArrType = {[string]:Array<string>};
type StringConv = {(string):string};
type WriteObjStrFactory = {from_sheet(ws:Worksheet, o:any):string};
type WriteObjStrFactory = {from_sheet(ws:Worksheet, o:any, wb:?Workbook):string};
*/
/* ssf.js (C) 2013-present SheetJS -- http://sheetjs.com */
/*jshint -W041 */
@ -4495,10 +4495,11 @@ function parse_SupBook(blob, length, opts) {
var end = blob.l + length;
var ctab = blob.read_shift(2);
var cch = blob.read_shift(2);
var virtPath;
if(cch >=0x01 && cch <=0xff) virtPath = parse_XLUnicodeStringNoCch(blob, cch);
var rgst = blob.read_shift(end - blob.l);
opts.sbcch = cch;
if(cch == 0x0401 || cch == 0x3A01) return [cch, ctab];
if(cch < 0x01 || cch >0xff) throw new Error("Unexpected SupBook type: "+cch);
var virtPath = parse_XLUnicodeStringNoCch(blob, cch);
var rgst = blob.read_shift(end - blob.l);
return [cch, ctab, virtPath, rgst];
}
@ -4523,6 +4524,22 @@ function parse_ExternName(blob, length, opts) {
}
/* 2.4.150 TODO */
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
function parse_Lbl(blob, length, opts) {
var target = blob.l + length;
var flags = blob.read_shift(2);
@ -4536,6 +4553,7 @@ function parse_Lbl(blob, length, opts) {
blob.l += 4;
}
var name = parse_XLUnicodeStringNoCch(blob, cch, opts);
if(flags & 0x20) name = XLSLblBuiltIn[name.charCodeAt(0)];
var npflen = target - blob.l; if(opts && opts.biff == 2) --npflen;
var rgce = target == blob.l || cce == 0 ? [] : parse_NameParsedFormula(blob, npflen, opts, cce);
return {
@ -6769,8 +6787,8 @@ function char2width_(chr) { return (((chr * MDW + 5)/MDW*256))/256; }
function cycle_width(collw) { return char2width(px2char(width2px(collw))); }
/* XLSX/XLSB/XLS specify width in units of MDW */
function find_mdw_colw(collw) {
var delta = Infinity, _MDW = MIN_MDW;
for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
var delta = Math.abs(collw - cycle_width(collw)), _MDW = MDW;
if(delta > 0.005) for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
MDW = _MDW;
}
/* XLML specifies width in terms of pixels */
@ -7087,11 +7105,13 @@ function write_numFmts(NF/*:{[n:number]:string}*/, opts) {
}
/* 18.8.10 cellXfs CT_CellXfs */
var cellXF_uint = [ "numFmtId", "fillId", "fontId", "borderId", "xfId" ];
var cellXF_bool = [ "applyAlignment", "applyBorder", "applyFill", "applyFont", "applyNumberFormat", "applyProtection", "pivotButton", "quotePrefix" ];
function parse_cellXfs(t, styles, opts) {
styles.CellXf = [];
var xf;
t[0].match(tagregex).forEach(function(x) {
var y = parsexmltag(x);
var y = parsexmltag(x), i = 0;
switch(y[0]) {
case '<cellXfs': case '<cellXfs>': case '<cellXfs/>': case '</cellXfs>': break;
@ -7099,8 +7119,10 @@ function parse_cellXfs(t, styles, opts) {
case '<xf':
xf = y;
delete xf[0];
if(xf.numFmtId) xf.numFmtId = parseInt(xf.numFmtId, 10);
if(xf.fillId) xf.fillId = parseInt(xf.fillId, 10);
for(i = 0; i < cellXF_uint.length; ++i) if(xf[cellXF_uint[i]])
xf[cellXF_uint[i]] = parseInt(xf[cellXF_uint[i]], 10);
for(i = 0; i < cellXF_bool.length; ++i) if(xf[cellXF_bool[i]])
xf[cellXF_bool[i]] = parsexmlbool(xf[cellXF_bool[i]], "");
styles.CellXf.push(xf); break;
case '</xf>': break;
@ -10705,7 +10727,7 @@ function parse_ws_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksh
var refguess/*:Range*/ = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }/*:any*/);
var data1 = "", data2 = "";
var mtch/*:?any*/ =data.match(sheetdataregex);
var mtch/*:?any*/ = data.match(sheetdataregex);
if(mtch) {
data1 = data.substr(0, mtch.index);
data2 = data.substr(mtch.index + mtch[0].length);
@ -13122,7 +13144,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
row = xlml_parsexmltag(Rn[0]);
if(row.Index) r = +row.Index - 1;
rowobj = {};
if(row.AutoFitHeight == "0") {
if(row.AutoFitHeight == "0" || row.Height) {
rowobj.hpx = parseInt(row.Height, 10); rowobj.hpt = px2pt(rowobj.hpx);
rowinfo[r] = rowobj;
}
@ -14223,7 +14245,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
supbooks.names.push(last_lbl);
if(!supbooks[0]) supbooks[0] = [];
supbooks[supbooks.length-1].push(val);
if(val.Name == "\r" && val.itab > 0)
if(val.Name == "_xlnm._FilterDatabase" && val.itab > 0)
if(val.rgce && val.rgce[0] && val.rgce[0][0] && val.rgce[0][0][0] == 'PtgArea3d')
FilterDatabases[val.itab - 1] = { ref: encode_range(val.rgce[0][0][1][2]) };
break;
@ -16121,7 +16143,7 @@ function write_biff_ws(ba/*:BufArray*/, ws/*:Worksheet*/, idx/*:number*/, opts,
for(var C = range.s.c; C <= range.e.c; ++C) {
if(R === range.s.r) cols[C] = encode_col(C);
ref = cols[C] + rr;
var cell = dense ? ws[R][C] : ws[ref];
var cell = dense ? (ws[R]||[])[C] : ws[ref];
if(!cell) continue;
/* write cell */
write_ws_biff_cell(ba, cell, R, C, opts);
@ -16220,21 +16242,29 @@ var HTML_ = (function() {
if(RS > 1) sp.rowspan = RS;
if(CS > 1) sp.colspan = CS;
if(o.editable) sp.contenteditable = "true";
sp.id = "sjs-" + coord;
oo.push(writextag('td', w, sp));
}
return "<tr>" + oo.join("") + "</tr>";
var preamble = "<tr>";
return preamble + oo.join("") + "</tr>";
}
var _BEGIN = "<html><head><title>SheetJS Table Export</title></head><body><table>";
var _END = "</table></body></html>";
function sheet_to_html(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/)/*:string*/ {
function make_html_preamble(ws/*:Worksheet*/, R/*:Range*/, o/*:Sheet2HTMLOpts*/)/*:string*/ {
var out = [];
return out.join("") + '<table>';
}
var _BEGIN = '<html><head><meta charset="utf-8"/><title>SheetJS Table Export</title></head><body>';
var _END = '</body></html>';
function sheet_to_html(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/, wb/*:?Workbook*/)/*:string*/ {
var o = opts || {};
var out/*:Array<string>*/ = [];
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o));
var header = o.header != null ? o.header : _BEGIN;
var footer = o.footer != null ? o.footer : _END;
return header + out.join("") + footer ;
var out/*:Array<string>*/ = [header];
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
out.push(make_html_preamble(ws, r, o));
for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o));
out.push("</table>" + footer);
return out.join("");
}
return {
@ -16243,6 +16273,7 @@ var HTML_ = (function() {
_row: make_html_row,
BEGIN: _BEGIN,
END: _END,
_preamble: make_html_preamble,
from_sheet: sheet_to_html
};
})();
@ -16946,7 +16977,7 @@ function write_obj_str(factory/*:WriteObjStrFactory*/) {
var idx = 0;
for(var i=0;i<wb.SheetNames.length;++i) if(wb.SheetNames[i] == o.sheet) idx=i;
if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
return factory.from_sheet(wb.Sheets[wb.SheetNames[idx]], o);
return factory.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb);
};
}
@ -17886,23 +17917,25 @@ if(has_buf && typeof require != 'undefined') (function() {
return stream;
};
var write_html_stream = function(sheet/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/) {
var write_html_stream = function(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/) {
var stream = Readable();
var o = opts == null ? {} : opts;
var r = decode_range(sheet['!ref']), cell/*:Cell*/;
o.dense = Array.isArray(sheet);
stream.push(HTML_.BEGIN);
var o = opts || {};
var header = o.header != null ? o.header : HTML_.BEGIN;
var footer = o.footer != null ? o.footer : HTML_.END;
stream.push(header);
var r = decode_range(ws['!ref']);
o.dense = Array.isArray(ws);
stream.push(HTML_._preamble(ws, r, o));
var R = r.s.r;
var end = false;
stream._read = function() {
if(R > r.e.r) {
if(!end) { end = true; stream.push(HTML_.END); }
if(!end) { end = true; stream.push("</table>" + footer); }
return stream.push(null);
}
while(R <= r.e.r) {
stream.push(HTML_._row(sheet, r, R, o));
stream.push(HTML_._row(ws, r, R, o));
++R;
break;
}

95
xlsx.js

@ -6,7 +6,7 @@
/*global global, exports, module, require:false, process:false, Buffer:false */
var XLSX = {};
(function make_xlsx(XLSX){
XLSX.version = '0.10.4';
XLSX.version = '0.10.5';
var current_codepage = 1200;
/*global cptable:true */
if(typeof module !== "undefined" && typeof require !== 'undefined') {
@ -4433,10 +4433,11 @@ function parse_SupBook(blob, length, opts) {
var end = blob.l + length;
var ctab = blob.read_shift(2);
var cch = blob.read_shift(2);
var virtPath;
if(cch >=0x01 && cch <=0xff) virtPath = parse_XLUnicodeStringNoCch(blob, cch);
var rgst = blob.read_shift(end - blob.l);
opts.sbcch = cch;
if(cch == 0x0401 || cch == 0x3A01) return [cch, ctab];
if(cch < 0x01 || cch >0xff) throw new Error("Unexpected SupBook type: "+cch);
var virtPath = parse_XLUnicodeStringNoCch(blob, cch);
var rgst = blob.read_shift(end - blob.l);
return [cch, ctab, virtPath, rgst];
}
@ -4461,6 +4462,22 @@ function parse_ExternName(blob, length, opts) {
}
/* 2.4.150 TODO */
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
function parse_Lbl(blob, length, opts) {
var target = blob.l + length;
var flags = blob.read_shift(2);
@ -4474,6 +4491,7 @@ function parse_Lbl(blob, length, opts) {
blob.l += 4;
}
var name = parse_XLUnicodeStringNoCch(blob, cch, opts);
if(flags & 0x20) name = XLSLblBuiltIn[name.charCodeAt(0)];
var npflen = target - blob.l; if(opts && opts.biff == 2) --npflen;
var rgce = target == blob.l || cce == 0 ? [] : parse_NameParsedFormula(blob, npflen, opts, cce);
return {
@ -6706,8 +6724,8 @@ function char2width_(chr) { return (((chr * MDW + 5)/MDW*256))/256; }
function cycle_width(collw) { return char2width(px2char(width2px(collw))); }
/* XLSX/XLSB/XLS specify width in units of MDW */
function find_mdw_colw(collw) {
var delta = Infinity, _MDW = MIN_MDW;
for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
var delta = Math.abs(collw - cycle_width(collw)), _MDW = MDW;
if(delta > 0.005) for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
MDW = _MDW;
}
/* XLML specifies width in terms of pixels */