works minutiae

This commit is contained in:
SheetJS 2021-12-29 04:16:02 -05:00
parent b3bc49afe8
commit d6161103b1
20 changed files with 184 additions and 81 deletions

3
.gitignore vendored

@ -15,7 +15,7 @@ tmp
*.[pP][dD][fF]
*.[sS][lL][kK]
*.socialcalc
*.[xX][lL][sSwWcCaAtTmM]
*.[xX][lL][sSwWcCaAtTmMrR]
*.[xX][lL][sSaAtT][xXmMbB]
*.[oO][dD][sS]
*.[fF][oO][dD][sS]
@ -23,6 +23,7 @@ tmp
*.[uU][oO][sS]
*.[wW][kKqQbB][S1234567890]
*.[qQ][pP][wW]
*.[fF][mM][3tT]
*.[bB][iI][fF][fF][23458]
*.[rR][tT][fF]
*.[eE][tT][hH]

@ -59,6 +59,15 @@ webpack
weex
# Other terms
1.x
2.x
3.x
4.x
5.x
6.x
7.x
8.x
9.x
ActiveX
APIs
ArrayBuffer

@ -25,7 +25,7 @@ FLOWAUX=$(patsubst %.js,%.flow.js,$(AUXTARGETS))
AUXSCPTS=xlsxworker.js
FLOWTGTS=$(TARGET) $(AUXTARGETS) $(AUXSCPTS) $(MINITGT)
UGLIFYOPTS=--support-ie8 -m
# CLOSURE=/usr/local/lib/node_modules/google-closure-compiler/compiler.jar
CLOSURE=/usr/local/lib/node_modules/google-closure-compiler/compiler.jar
## Main Targets
@ -73,7 +73,7 @@ DISTHDR=misc/suppress_export.js
.PHONY: dist
dist: dist-deps $(TARGET) bower.json ## Prepare JS files for distribution
mkdir -p dist
<$(TARGET) sed "s/require('stream')/{}/g;s/require('....*')/undefined/g" > dist/$(TARGET)
<$(TARGET) sed "s/require('....*')/undefined/g" > dist/$(TARGET)
cp LICENSE dist/
uglifyjs shim.js $(UGLIFYOPTS) -o dist/shim.min.js --preamble "$$(head -n 1 bits/00_header.js)"
uglifyjs $(DISTHDR) dist/$(TARGET) $(UGLIFYOPTS) -o dist/$(LIB).min.js --source-map dist/$(LIB).min.map --preamble "$$(head -n 1 bits/00_header.js)"
@ -148,47 +148,6 @@ ctest: ## Build browser test fixtures
ctestserv: ## Start a test server on port 8000
@cd tests && python -mSimpleHTTPServer
## Demos
DEMOS=angular angular-new browserify requirejs rollup systemjs webpack
DEMOTGTS=$(patsubst %,demo-%,$(DEMOS))
.PHONY: demos
demos: $(DEMOTGTS)
.PHONY: demo-angular
demo-angular: ## Run angular demo build
#make -C demos/angular
@echo "start a local server and go to demos/angular/angular.html"
.PHONY: demo-angular-new
demo-angular-new: ## Run angular 2 demo build
make -C demos/angular2
@echo "go to demos/angular/angular.html and run 'ng serve'"
.PHONY: demo-browserify
demo-browserify: ## Run browserify demo build
make -C demos/browserify
@echo "start a local server and go to demos/browserify/browserify.html"
.PHONY: demo-webpack
demo-webpack: ## Run webpack demo build
make -C demos/webpack
@echo "start a local server and go to demos/webpack/webpack.html"
.PHONY: demo-requirejs
demo-requirejs: ## Run requirejs demo build
make -C demos/requirejs
@echo "start a local server and go to demos/requirejs/requirejs.html"
.PHONY: demo-rollup
demo-rollup: ## Run rollup demo build
make -C demos/rollup
@echo "start a local server and go to demos/rollup/rollup.html"
.PHONY: demo-systemjs
demo-systemjs: ## Run systemjs demo build
make -C demos/systemjs
## Code Checking
.PHONY: fullint
@ -197,7 +156,7 @@ fullint: lint mdlint ## Run all checks (removed: old-lint, tslint, flow)
.PHONY: lint
lint: $(TARGET) $(AUXTARGETS) ## Run eslint checks
@./node_modules/.bin/eslint --ext .js,.njs,.json,.html,.htm $(TARGET) $(AUXTARGETS) $(CMDS) $(HTMLLINT) package.json bower.json
if [ -n "$(CLOSURE-)" ] && [ -e "${CLOSURE}" ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi
@if [ -x "$(CLOSURE)" ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi
.PHONY: old-lint
old-lint: $(TARGET) $(AUXTARGETS) ## Run jshint and jscs checks
@ -206,7 +165,7 @@ old-lint: $(TARGET) $(AUXTARGETS) ## Run jshint and jscs checks
@./node_modules/.bin/jshint --show-non-errors package.json bower.json test.js
@./node_modules/.bin/jshint --show-non-errors --extract=always $(HTMLLINT)
@./node_modules/.bin/jscs $(TARGET) $(AUXTARGETS) test.js
if [ -e $(CLOSURE) ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi
@if [ -x "$(CLOSURE)" ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi
.PHONY: tslint
tslint: $(TARGET) ## Run typescript checks

@ -5,7 +5,7 @@
var n = "xlsx";
var X = require('../');
try { X = require('../xlsx.flow'); } catch(e) {}
require('exit-on-epipe');
try { require('exit-on-epipe'); } catch(e) {}
var fs = require('fs'), program;
try { program = require('commander'); } catch(e) {
[

@ -30,7 +30,7 @@ function parse_rels(data/*:?string*/, currentFilePath/*:string*/) {
var y = parsexmltag(x);
/* 9.3.2.2 OPC_Relationships */
if (y[0] === '<Relationship') {
var rel = {}; rel.Type = y.Type; rel.Target = y.Target; rel.Id = y.Id; rel.TargetMode = y.TargetMode;
var rel = {}; rel.Type = y.Type; rel.Target = y.Target; rel.Id = y.Id; if(y.TargetMode) rel.TargetMode = y.TargetMode;
var canonictarget = y.TargetMode === 'External' ? y.Target : resolve_path(y.Target, currentFilePath);
rels[canonictarget] = rel;
hash[y.Id] = rel;

@ -701,7 +701,9 @@ function parse_Lbl(blob, length, opts) {
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;
/*jshint -W018 */
var rgce = (target == blob.l || cce === 0 || !(npflen > 0)) ? [] : parse_NameParsedFormula(blob, npflen, opts, cce);
/*jshint +W018 */
return {
chKey: chKey,
Name: name,

@ -28,12 +28,18 @@ var WK_ = /*#__PURE__*/ (function() {
if(!d) return d;
var o = opts || {};
if(DENSE != null && o.dense == null) o.dense = DENSE;
var s/*:Worksheet*/ = ((o.dense ? [] : {})/*:any*/), n = "Sheet1", sidx = 0;
var sheets = {}, snames = [n], realnames = [];
var s/*:Worksheet*/ = ((o.dense ? [] : {})/*:any*/), n = "Sheet1", next_n = "", sidx = 0;
var sheets = {}, snames = [], realnames = [];
var refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
var sheetRows = o.sheetRows || 0;
if(d[2] == 0x00) {
if(d[3] == 0x08 || d[3] == 0x09) {
if(d.length >= 16 && d[14] == 0x05 && d[15] === 0x6c) throw new Error("Unsupported Works 3 for Mac file");
}
}
if(d[2] == 0x02) {
o.Enum = WK1Enum;
lotushopper(d, function(val, R, RT) { switch(RT) {
@ -42,6 +48,8 @@ var WK_ = /*#__PURE__*/ (function() {
if(val >= 0x1000) o.qpro = true;
break;
case 0x06: refguess = val; break; /* RANGE */
case 0xCC: if(val) next_n = val; break; /* SHEETNAMECS */
case 0xDE: next_n = val; break; /* SHEETNAMELP */
case 0x0F: /* LABEL */
case 0x33: /* STRING */
if(!o.qpro) val[1].v = val[1].v.slice(1);
@ -54,6 +62,18 @@ var WK_ = /*#__PURE__*/ (function() {
val[1].z = o.dateNF || SSF._table[14];
if(o.cellDates) { val[1].t = 'd'; val[1].v = numdate(val[1].v); }
}
if(o.qpro) {
if(val[3] > sidx) {
s["!ref"] = encode_range(refguess);
sheets[n] = s;
snames.push(n);
s = (o.dense ? [] : {});
refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
sidx = val[3]; n = next_n || "Sheet" + (sidx + 1); next_n = "";
}
}
var tmpcell = o.dense ? (s[val[0].r]||[])[val[0].c] : s[encode_cell(val[0])];
if(tmpcell) {
tmpcell.t = val[1].t; tmpcell.v = val[1].v;
@ -72,6 +92,7 @@ var WK_ = /*#__PURE__*/ (function() {
o.Enum = WK3Enum;
if(d[2] == 0x0E) { o.qpro = true; d.l = 0; }
lotushopper(d, function(val, R, RT) { switch(RT) {
case 0xCC: n = val; break; /* SHEETNAMECS */
case 0x16: /* LABEL16 */
val[1].v = val[1].v.slice(1);
/* falls through */
@ -84,10 +105,10 @@ var WK_ = /*#__PURE__*/ (function() {
if(val[3] > sidx) {
s["!ref"] = encode_range(refguess);
sheets[n] = s;
snames.push(n);
s = (o.dense ? [] : {});
refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
sidx = val[3]; n = "Sheet" + (sidx + 1);
snames.push(n);
}
if(sheetRows > 0 && val[0].r >= sheetRows) break;
if(o.dense) {
@ -100,17 +121,23 @@ var WK_ = /*#__PURE__*/ (function() {
case 0x1B: /* XFORMAT */
if(val[0x36b0]) realnames[val[0x36b0][0]] = val[0x36b0][1];
break;
case 0x0601: /* SHEETINFOQP */
realnames[val[0]] = val[1]; if(val[0] == sidx) n = val[1]; break;
default: break;
}}, o);
} else throw new Error("Unrecognized LOTUS BOF " + d[2]);
s["!ref"] = encode_range(refguess);
sheets[n] = s;
sheets[next_n || n] = s;
snames.push(next_n || n);
if(!realnames.length) return { SheetNames: snames, Sheets: sheets };
var osheets = {}, rnames = [];
/* TODO: verify no collisions */
for(var i = 0; i < realnames.length; ++i) if(sheets[snames[i]]) {
rnames.push(realnames[i] || snames[i]);
osheets[realnames[i]] = sheets[realnames[i]] || sheets[snames[i]];
} else {
rnames.push(realnames[i]);
osheets[realnames[i]] = sheets[snames[i]];
osheets[realnames[i]] = ({ "!ref": "A1" });
}
return { SheetNames: rnames, Sheets: osheets };
}
@ -126,7 +153,8 @@ var WK_ = /*#__PURE__*/ (function() {
write_biff_rec(ba, 0x00, write_BOF_WK1(0x0406));
write_biff_rec(ba, 0x06, write_RANGE(range));
for(var R = range.s.r; R <= range.e.r; ++R) {
var max_R = Math.min(range.e.r, 8191);
for(var R = range.s.r; R <= max_R; ++R) {
var rr = encode_row(R);
for(var C = range.s.c; C <= range.e.c; ++C) {
if(R === range.s.r) cols[C] = encode_col(C);
@ -165,7 +193,8 @@ var WK_ = /*#__PURE__*/ (function() {
var range = safe_decode_range(ws["!ref"]);
var dense = Array.isArray(ws);
var cols = [];
for(var R = range.s.r; R <= range.e.r; ++R) {
var max_R = Math.min(range.e.r, 8191);
for(var R = range.s.r; R <= max_R; ++R) {
var rr = encode_row(R);
for(var C = range.s.c; C <= range.e.c; ++C) {
if(R === range.s.r) cols[C] = encode_col(C);
@ -211,6 +240,7 @@ var WK_ = /*#__PURE__*/ (function() {
if(rows < range.e.r) rows = range.e.r;
if(cols < range.e.c) cols = range.e.c;
}
if(rows > 8191) rows = 8191;
out.write_shift(2, rows);
out.write_shift(1, wscnt);
out.write_shift(1, cols);
@ -223,12 +253,23 @@ var WK_ = /*#__PURE__*/ (function() {
return out;
}
function parse_RANGE(blob) {
function parse_RANGE(blob, length, opts) {
var o = {s:{c:0,r:0},e:{c:0,r:0}};
if(length == 8 && opts.qpro) {
o.s.c = blob.read_shift(1);
blob.l++;
o.s.r = blob.read_shift(2);
o.e.c = blob.read_shift(1);
blob.l++;
o.e.r = blob.read_shift(2);
return o;
}
o.s.c = blob.read_shift(2);
o.s.r = blob.read_shift(2);
if(length == 12 && opts.qpro) blob.l += 2;
o.e.c = blob.read_shift(2);
o.e.r = blob.read_shift(2);
if(length == 12 && opts.qpro) blob.l += 2;
if(o.s.c == 0xFFFF) o.s.c = o.e.c = o.s.r = o.e.r = 0;
return o;
}
@ -242,10 +283,10 @@ var WK_ = /*#__PURE__*/ (function() {
}
function parse_cell(blob, length, opts) {
var o = [{c:0,r:0}, {t:'n',v:0}, 0];
var o = [{c:0,r:0}, {t:'n',v:0}, 0, 0];
if(opts.qpro && opts.vers != 0x5120) {
o[0].c = blob.read_shift(1);
blob.l++;
o[3] = blob.read_shift(1);
o[0].r = blob.read_shift(2);
blob.l+=2;
} else {
@ -357,7 +398,7 @@ var WK_ = /*#__PURE__*/ (function() {
function wk1_fmla_to_csf(blob, o) {
prep_blob(blob, 0);
var out = [], argc = 0, R = "", C = "";
var out = [], argc = 0, R = "", C = "", argL = "", argR = "";
while(blob.l < blob.length) {
var cc = blob[blob.l++];
switch(cc) {
@ -390,7 +431,7 @@ var WK_ = /*#__PURE__*/ (function() {
case 0x16: out.push("NOT(" + out.pop() + ")"); break;
case 0x14: case 0x15: {
var argR = out.pop(), argL = out.pop();
argR = out.pop(); argL = out.pop();
out.push(["AND", "OR"][cc - 0x14] + "(" + argL + "," + argR + ")");
} break;
@ -536,6 +577,27 @@ var WK_ = /*#__PURE__*/ (function() {
return o;
}
function parse_SHEETNAMECS(blob, length) {
return blob[blob.l + length - 1] == 0 ? blob.read_shift(length, 'cstr') : "";
}
function parse_SHEETNAMELP(blob, length) {
var len = blob[blob.l++];
if(len > length - 1) len = length - 1;
var o = ""; while(o.length < len) o += String.fromCharCode(blob[blob.l++]);
return o;
}
function parse_SHEETINFOQP(blob, length, opts) {
if(!opts.qpro || length < 21) return;
var id = blob.read_shift(1);
blob.l += 17;
var len = blob.read_shift(1);
blob.l += 2;
var nm = blob.read_shift(length - 21, 'cstr');
return [id, nm];
}
function parse_XFORMAT(blob, length) {
var o = {}, tgt = blob.l + length;
while(blob.l < tgt) {
@ -623,6 +685,8 @@ var WK_ = /*#__PURE__*/ (function() {
/*::[*/0x0067/*::]*/: { n:"RRANGES??" },
/*::[*/0x0068/*::]*/: { n:"FNAME??" },
/*::[*/0x0069/*::]*/: { n:"MRANGES??" },
/*::[*/0x00CC/*::]*/: { n:"SHEETNAMECS", f:parse_SHEETNAMECS },
/*::[*/0x00DE/*::]*/: { n:"SHEETNAMELP", f:parse_SHEETNAMELP },
/*::[*/0xFFFF/*::]*/: { n:"" }
};
@ -688,6 +752,7 @@ var WK_ = /*#__PURE__*/ (function() {
/*::[*/0x00BC/*::]*/: { n:"??" },
/*::[*/0x00C3/*::]*/: { n:"??" },
/*::[*/0x00C9/*::]*/: { n:"??" },
/*::[*/0x00CC/*::]*/: { n:"SHEETNAMECS", f:parse_SHEETNAMECS },
/*::[*/0x00CD/*::]*/: { n:"??" },
/*::[*/0x00CE/*::]*/: { n:"??" },
/*::[*/0x00CF/*::]*/: { n:"??" },
@ -732,6 +797,7 @@ var WK_ = /*#__PURE__*/ (function() {
/*::[*/0x029A/*::]*/: { n:"??" },
/*::[*/0x0300/*::]*/: { n:"??" },
/*::[*/0x0304/*::]*/: { n:"??" },
/*::[*/0x0601/*::]*/: { n:"SHEETINFOQP", f:parse_SHEETINFOQP },
/*::[*/0x0640/*::]*/: { n:"??" },
/*::[*/0x0642/*::]*/: { n:"??" },
/*::[*/0x0701/*::]*/: { n:"??" },

@ -855,7 +855,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(!options.bookSheets) wb.Sheets=Sheets;
if(!wb.SheetNames.length && Preamble["!ref"]) {
wb.SheetNames.push("Sheet1");
/*jshint -W069 */
if(wb.Sheets) wb.Sheets["Sheet1"] = Preamble;
/*jshint +W069 */
} else wb.Preamble=Preamble;
if(wb.Sheets) FilterDatabases.forEach(function(r,i) { wb.Sheets[wb.SheetNames[i]]['!autofilter'] = r; });
wb.Strings = sst;
@ -950,6 +952,8 @@ else/*:: if(cfb instanceof CFBContainer) */ {
else if((_data=CFB.find(cfb, 'PerfectOffice_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options));
/* Quattro Pro 9 */
else if((_data=CFB.find(cfb, 'NativeContent_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options));
/* Works 4 for Mac */
else if((_data=CFB.find(cfb, 'MN0')) && _data.content) throw new Error("Unsupported Works 4 for Mac file");
else throw new Error("Cannot find Workbook stream");
if(options.bookVBA && cfb.FullPaths && CFB.find(cfb, '/_VBA_PROJECT_CUR/VBA/dir')) WorkbookP.vbaraw = make_vba_xls(cfb);
}

@ -842,7 +842,9 @@ var XLSBRecordEnum = {
};
var XLSBRE = evert_key(XLSBRecordEnum, 'n');
/*jshint -W069 */
XLSBRE["BrtFRTArchID$"] = 0x0010;
/*jshint +W069 */
/* [MS-XLS] 2.3 Record Enumeration (and other sources) */
var XLSRecordEnum = {

@ -1,13 +1,15 @@
/* OpenDocument */
var write_styles_ods/*:{(wb:any, opts:any):string}*/ = (function() {
var master_styles = '<office:master-styles>'
+ '<style:master-page style:name="mp1" style:page-layout-name="mp1">'
+ '<style:header/>'
+ '<style:header-left style:display="false"/>'
+ '<style:footer/>'
+ '<style:footer-left style:display="false"/>'
+ '</style:master-page>'
+ '</office:master-styles>';
var master_styles = [
'<office:master-styles>',
'<style:master-page style:name="mp1" style:page-layout-name="mp1">',
'<style:header/>',
'<style:header-left style:display="false"/>',
'<style:footer/>',
'<style:footer-left style:display="false"/>',
'</style:master-page>',
'</office:master-styles>'
].join("");
var payload = '<office:document-styles ' + wxt_helper({
'xmlns:office': "urn:oasis:names:tc:opendocument:xmlns:office:1.0",

@ -91,12 +91,20 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
case 0x54: if(n[1] === 0x41 && n[2] === 0x42 && n[3] === 0x4C) return DIF.to_workbook(d, o); break;
case 0x50: return (n[1] === 0x4B && n[2] < 0x09 && n[3] < 0x09) ? read_zip(d, o) : read_prn(data, d, o, str);
case 0xEF: return n[3] === 0x3C ? parse_xlml(d, o) : read_prn(data, d, o, str);
case 0xFF: if(n[1] === 0xFE) { return read_utf16(d, o); } break;
case 0x00: if(n[1] === 0x00 && n[2] >= 0x02 && n[3] === 0x00) return WK_.to_workbook(d, o); break;
case 0xFF:
if(n[1] === 0xFE) { return read_utf16(d, o); }
else if(n[1] === 0x00 && n[2] === 0x02 && n[3] === 0x00) return WK_.to_workbook(d, o);
break;
case 0x00:
if(n[1] === 0x00) {
if(n[2] >= 0x02 && n[3] === 0x00) return WK_.to_workbook(d, o);
if(n[2] === 0x00 && (n[3] === 0x08 || n[3] === 0x09)) return WK_.to_workbook(d, o);
}
break;
case 0x03: case 0x83: case 0x8B: case 0x8C: return DBF.to_workbook(d, o);
case 0x7B: if(n[1] === 0x5C && n[2] === 0x72 && n[3] === 0x74) return RTF.to_workbook(d, o); break;
case 0x0A: case 0x0D: case 0x20: return read_plaintext_raw(d, o);
case 0x89: if(n[1] === 0x50 && n[2] === 0x4E && n[3] === 0x47) throw new Error("PNG Image File is not a spreadsheet"); break;
case 0x89: if(n[1] === 0x50 && n[2] === 0x4E && n[3] === 0x47) throw new Error("PNG Image File is not a spreadsheet"); break;
}
if(DBF.versions.indexOf(n[0]) > -1 && n[2] <= 12 && n[3] <= 31) return DBF.to_workbook(d, o);
return read_prn(data, d, o, str);

@ -1,5 +1,8 @@
if(has_buf && typeof require != 'undefined') (function() {
var Readable = require('stream').Readable;
var strmod = require('stream');
if(!strmod) return;
var Readable = strmod.Readable;
if(!Readable) return;
var write_csv_stream = function(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
var stream = Readable();

@ -6,6 +6,6 @@ else if(typeof module !== 'undefined' && module.exports) make_xlsx_lib(module.ex
else if(typeof define === 'function' && define.amd) define('xlsx', function() { if(!XLSX.version) make_xlsx_lib(XLSX); return XLSX; });
else make_xlsx_lib(XLSX);
/* NOTE: the following extra line is needed for "Lightning Locker Service" */
if(typeof window !== 'undefined' && !window.XLSX) window.XLSX = XLSX;
if(typeof window !== 'undefined' && !window.XLSX) try { window.XLSX = XLSX; } catch(e) {}
/*exported XLS, ODS */
var XLS = XLSX, ODS = XLSX;

@ -85,7 +85,7 @@ file but Excel will know how to handle it. This library applies similar logic:
| Byte 0 | Raw File Type | Spreadsheet Types |
|:-------|:--------------|:----------------------------------------------------|
| `0xD0` | CFB Container | BIFF 5/8 or password-protected XLSX/XLSB or WQ3/QPW |
| `0xD0` | CFB Container | BIFF 5/8 or protected XLSX/XLSB or WQ3/QPW or XLR |
| `0x09` | BIFF Stream | BIFF 2/3/4/5 |
| `0x3C` | XML/HTML | SpreadsheetML / Flat ODS / UOS1 / HTML / plain text |
| `0x50` | ZIP Archive | XLSB or XLSX/M or ODS or UOS2 or plain text |
@ -102,6 +102,8 @@ file but Excel will know how to handle it. This library applies similar logic:
DBF files are detected based on the first byte as well as the third and fourth
bytes (corresponding to month and day of the file date)
Works for Windows files are detected based on the BOF record with type `0xFF`
Plain text format guessing follows the priority order:
| Format | Test |

@ -27,6 +27,8 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| Lotus 1-2-3 (WK1/WK3) | ✔ | ✔ |
| Lotus 1-2-3 (WKS/WK2/WK4/123) | ✔ | |
| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | |
| Works 1.x-3.x DOS / 2.x-5.x Windows Spreadsheet (WKS) | ✔ | |
| Works 6.x-9.x Spreadsheet (XLR) | ✔ | |
| **Other Common Spreadsheet Output Formats** |:-----:|:-----:|
| HTML Tables | ✔ | ✔ |
| Rich Text Format tables (RTF) | | ✔ |
@ -44,6 +46,8 @@ range limits will be silently truncated:
| Excel 4.0 (XLS BIFF4) | IV16384 | 256 | 16384 |
| Excel 3.0 (XLS BIFF3) | IV16384 | 256 | 16384 |
| Excel 2.0/2.1 (XLS BIFF2) | IV16384 | 256 | 16384 |
| Lotus 1-2-3 R2 - R5 (WK1/WK3/WK4) | IV8192 | 256 | 8192 |
| Lotus 1-2-3 R1 (WKS) | IV2048 | 256 | 2048 |
Excel 2003 SpreadsheetML range limits are governed by the version of Excel and
are not enforced by the writer.
@ -180,6 +184,27 @@ BIFF8 XLS.
</details>
#### Works for DOS / Windows Spreadsheet (WKS/XLR)
<details>
<summary>(click to show)</summary>
All versions of Works were limited to a single worksheet.
Works for DOS 1.x - 3.x and Works for Windows 2.x extends the Lotus WKS format
with additional record types.
Works for Windows 3.x - 5.x uses the same format and WKS extension. The BOF
record has type `FF`
Works for Windows 6.x - 9.x use the XLR format. XLR is nearly identical to
BIFF8 XLS: it uses the CFB container with a Workbook stream. Works 9 saves the
exact Workbook stream for the XLR and the 97-2003 XLS export. Works 6 XLS
includes two empty worksheets but the main worksheet has an identical encoding.
XLR also includes a `WksSSWorkBook` stream similar to Lotus FM3/FMT files.
</details>
#### OpenDocument Spreadsheet (ODS/FODS)
<details>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 KiB

After

Width:  |  Height:  |  Size: 191 KiB

@ -2,8 +2,12 @@
const fs = require('fs');
const coarse = require('coarse');
const svg = fs.readFileSync(process.argv[2]);
const roughened = coarse(svg);
const svg = fs.readFileSync(process.argv[2], "utf8");
let roughened = coarse(svg);
const viewbox = roughened.match(/viewBox="(.*?)"/)[1].split(/\s+/);
const v = viewbox.map(x => parseFloat(x));
v[0] -= 40; v[1] += 40; v[2] += 80; v[3] += 80;
roughened = roughened.replace(/<title>G<\/title>/, `$&<polygon fill="white" stroke="" points="${v[0]},${v[1]} ${v[0]},${v[1]-v[3]} ${v[0]+v[2]},${v[1]-v[3]} ${v[0]+v[2]},${v[1]} ${v[0]},${v[1]}"/>`);
fs.writeFileSync(process.argv[3], roughened);

@ -27,9 +27,15 @@ digraph G {
slk [label="SYLK"];
prn [label="PRN"];
rtf [label="RTF"];
wk1 [label="WK1/2\n123"];
wk3 [label="WK3/4"];
wqb [label="WQ*\nWB*"];
wk1 [label="WK1"];
wksl [label="WKS\nLotus"];
wk3 [label="WK3"];
wk4 [label="WK4"]
123 [label="123"];
wksm [label="WKS\nWorks"];
xlr [label="XLR"];
wq1 [label="WQ1"];
wq2 [label="WQ2\nWB*"];
qpw [label="QPW"];
eth [label="ETH"];
}
@ -46,6 +52,7 @@ digraph G {
xls5 -> csf
csf -> xls8
xls8 -> csf
wq2 -> csf
ods -> csf
csf -> ods
fods -> csf
@ -53,6 +60,8 @@ digraph G {
uos -> csf
wk3 -> csf
csf -> wk3
wk4 -> csf
123 -> csf
qpw -> csf
}
subgraph WORKSHEET {
@ -68,7 +77,10 @@ digraph G {
csf -> dif
wk1 -> csf
csf -> wk1
wqb -> csf
xlr -> csf
wq1 -> csf
wksl -> csf
wksm -> csf
dif -> csf
csf -> rtf
prn -> csf

@ -524,9 +524,11 @@ describe('parse options', function() {
} } };
var str = X.write(wb, {bookType: "xlsx", type: "binary"});
var wb2 = X.read(str, {type: "binary"});
/*jshint -W069 */
assert.equal(wb2.Sheets.Sheet1["A1"].f, "IFS(2>3,1,3>2,2)");
var wb3 = X.read(str, {type: "binary", xlfn: true});
assert.equal(wb3.Sheets.Sheet1["A1"].f, "_xlfn.IFS(2>3,1,3>2,2)");
/*jshint +W069 */
});
});
describe('sheet', function() {

2
tests/core.js generated

@ -524,9 +524,11 @@ describe('parse options', function() {
} } };
var str = X.write(wb, {bookType: "xlsx", type: "binary"});
var wb2 = X.read(str, {type: "binary"});
/*jshint -W069 */
assert.equal(wb2.Sheets.Sheet1["A1"].f, "IFS(2>3,1,3>2,2)");
var wb3 = X.read(str, {type: "binary", xlfn: true});
assert.equal(wb3.Sheets.Sheet1["A1"].f, "_xlfn.IFS(2>3,1,3>2,2)");
/*jshint +W069 */
});
});
describe('sheet', function() {