version bump 0.8.6: module flush

- fixes #571 h/t @FredrikL @mooyoul
- pin npm version in travis, see https://github.com/npm/npm/issues/15855
- updated CFB (fixes #569 h/t @e12009)
- remove BOM for some SpreadsheetML files

case insensitive zip file search fixes:
- fixes #439 h/t @chikh
- fixes #440 h/t @chikh
- fixes #567 h/t @duzun
This commit is contained in:
SheetJS 2017-02-24 02:33:01 -08:00
parent 7683e213ea
commit 5ae6b1965b
28 changed files with 302 additions and 152 deletions

View File

@ -9,7 +9,7 @@ node_js:
- "0.9"
- "0.8"
before_install:
- "npm install -g npm@next"
- "npm install -g npm@4.3.0"
- "npm install -g mocha@2.x voc"
- "npm install blanket"
- "npm install xlsjs"

View File

@ -30,7 +30,7 @@ $(FLOWTARGET): $(DEPS)
bits/01_version.js: package.json
echo "$(ULIB).version = '"`grep version package.json | awk '{gsub(/[^0-9a-z\.-]/,"",$$2); print $$2}'`"';" > $@
bits/18_cfb.js: node_modules/cfb/dist/xlscfb.js
bits/18_cfb.js: node_modules/cfb/xlscfb.flow.js
cp $^ $@
.PHONY: clean
@ -76,7 +76,7 @@ ods: ods.js
ODSDEPS=$(sort $(wildcard odsbits/*.js))
ods.flow.js: $(ODSDEPS) ## Build ODS support library
cat $(ODSDEPS) | tr -d '\15\32' > $@
cat $^ | tr -d '\15\32' > $@
## Testing

View File

@ -559,6 +559,7 @@ file but Excel will know how to handle it. This library applies similar logic:
| `0x09` | BIFF Stream | BIFF 2/3/4/5 |
| `0x3C` | XML | SpreadsheetML or Flat ODS or UOS1 |
| `0x50` | ZIP Archive | XLSB or XLSX/M or ODS or UOS2 |
| `0xFE` | UTF8 Text | SpreadsheetML or Flat ODS or UOS1 |
## Writing Options

View File

@ -1 +1 @@
XLSX.version = '0.8.5';
XLSX.version = '0.8.6';

View File

@ -7,12 +7,18 @@ function reset_cp() { set_cp(1200); }
var set_cp = function(cp) { current_codepage = cp; };
function char_codes(data) { var o = []; for(var i = 0, len = data.length; i < len; ++i) o[i] = data.charCodeAt(i); return o; }
var debom_xml = function(data) { return data; };
var debom = function(data/*:string*/)/*:string*/ {
var c1 = data.charCodeAt(0), c2 = data.charCodeAt(1);
if(c1 == 0xFF && c2 == 0xFE) return data.substr(2);
if(c1 == 0xFE && c2 == 0xFF) return data.substr(2);
if(c1 == 0xFEFF) return data.substr(1);
return data;
};
var _getchar = function _gc1(x) { return String.fromCharCode(x); };
if(typeof cptable !== 'undefined') {
set_cp = function(cp) { current_codepage = cp; current_cptable = cptable[cp]; };
debom_xml = function(data) {
debom = function(data) {
if(data.charCodeAt(0) === 0xFF && data.charCodeAt(1) === 0xFE) { return cptable.utils.decode(1200, char_codes(data.substr(2))); }
return data;
};

View File

@ -1,7 +1,7 @@
var Base64 = (function make_b64(){
var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
return {
encode: function(input, utf8) {
encode: function(input/*:string*/, utf8)/*:string*/ {
var o = "";
var c1, c2, c3, e1, e2, e3, e4;
for(var i = 0; i < input.length; ) {
@ -18,7 +18,7 @@ var Base64 = (function make_b64(){
}
return o;
},
decode: function b64_decode(input, utf8) {
decode: function b64_decode(input/*:string*/, utf8)/*:string*/ {
var o = "";
var c1, c2, c3;
var e1, e2, e3, e4;

View File

@ -1,12 +1,36 @@
var DO_NOT_EXPORT_CFB = true;
/* cfb.js (C) 2013-2014 SheetJS -- http://sheetjs.com */
/*::
declare var Base64:any;
declare var ReadShift:any;
declare var CheckField:any;
declare var prep_blob:any;
declare var __readUInt32LE:any;
declare var __readInt32LE:any;
declare var __toBuffer:any;
declare var __utf16le:any;
declare var bconcat:any;
declare var s2a:any;
declare var chr0:any;
declare var chr1:any;
*/
/* cfb.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
/*jshint eqnull:true */
/*::
declare var DO_NOT_EXPORT_CFB:any;
type SectorEntry = any;
type SectorList = {
(k:string|number):SectorEntry;
name:?string;
fat_addrs:any;
ssz:number;
}
*/
/* [MS-CFB] v20130118 */
var CFB = (function _CFB(){
var exports = {};
exports.version = '0.10.2';
exports.version = '0.11.0';
function parse(file) {
var mver = 3; // major version
var ssz = 512; // sector size
@ -19,7 +43,7 @@ var difat_start = 0; // first mini FAT sector location
var fat_addrs = []; // locations of FAT sectors
/* [MS-CFB] 2.2 Compound File Header */
var blob = file.slice(0,512);
var blob/*:any*/ = file.slice(0,512);
prep_blob(blob, 0);
/* major version */
@ -79,7 +103,7 @@ var sectors = sectorify(file, ssz);
sleuth_fat(difat_start, ndfs, sectors, ssz, fat_addrs);
/** Chains */
var sector_list = make_sector_list(sectors, dir_start, fat_addrs, ssz);
var sector_list/*:SectorList*/ = make_sector_list(sectors, dir_start, fat_addrs, ssz);
sector_list[dir_start].name = "!Directory";
if(nmfs > 0 && minifat_start !== ENDOFCHAIN) sector_list[minifat_start].name = "!MiniFAT";
@ -88,7 +112,7 @@ sector_list.fat_addrs = fat_addrs;
sector_list.ssz = ssz;
/* [MS-CFB] 2.6.1 Compound File Directory Entry */
var files = {}, Paths = [], FileIndex = [], FullPaths = [], FullPathDir = {};
var files = {}, Paths/*:any*/ = [], FileIndex = [], FullPaths = [], FullPathDir = {};
read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, FileIndex);
build_full_paths(FileIndex, FullPathDir, FullPaths, Paths);
@ -197,7 +221,7 @@ function make_find_path(FullPaths, Paths, FileIndex, files, root_name) {
var UCPaths = new Array(Paths.length), i;
for(i = 0; i < FullPaths.length; ++i) UCFullPaths[i] = FullPaths[i].toUpperCase().replace(chr0,'').replace(chr1,'!');
for(i = 0; i < Paths.length; ++i) UCPaths[i] = Paths[i].toUpperCase().replace(chr0,'').replace(chr1,'!');
return function find_path(path) {
return function find_path(path/*:string*/) {
var k;
if(path.charCodeAt(0) === 47 /* "/" */) { k=true; path = root_name + path; }
else k = path.indexOf("/") !== -1;
@ -216,6 +240,7 @@ function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) {
if(cnt !== 0) throw "DIFAT chain shorter than expected";
} else if(idx !== -1 /*FREESECT*/) {
var sector = sectors[idx], m = (ssz>>>2)-1;
if(!sector) return;
for(var i = 0; i < m; ++i) {
if((q = __readInt32LE(sector,i*4)) === ENDOFCHAIN) break;
fat_addrs.push(q);
@ -239,13 +264,14 @@ function get_sector_list(sectors, start, fat_addrs, ssz, chkd) {
var addr = fat_addrs[Math.floor(j*4/ssz)];
jj = ((j*4) & modulus);
if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz;
if(!sectors[addr]) break;
j = __readInt32LE(sectors[addr], jj);
}
return {nodes: buf, data:__toBuffer([buf_chain])};
}
/** Chase down the sector linked lists */
function make_sector_list(sectors, dir_start, fat_addrs, ssz) {
function make_sector_list(sectors, dir_start, fat_addrs, ssz/*:number*/)/*:any*/ {
var sl = sectors.length, sector_list = new Array(sl);
var chkd = new Array(sl), buf, buf_chain;
var modulus = ssz - 1, i, j, k, jj;
@ -261,6 +287,7 @@ function make_sector_list(sectors, dir_start, fat_addrs, ssz) {
var addr = fat_addrs[Math.floor(j*4/ssz)];
jj = ((j*4) & modulus);
if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz;
if(!sectors[addr]) break;
j = __readInt32LE(sectors[addr], jj);
}
sector_list[k] = {nodes: buf, data:__toBuffer([buf_chain])};
@ -281,7 +308,7 @@ function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, Fil
if(namelen === 0) continue;
name = __utf16le(blob,0,namelen-pl);
Paths.push(name);
o = {
o = ({
name: name,
type: blob.read_shift(1),
color: blob.read_shift(1),
@ -290,7 +317,7 @@ function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, Fil
C: blob.read_shift(4, 'i'),
clsid: blob.read_shift(16),
state: blob.read_shift(4, 'i')
};
}/*:any*/);
ctime = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2);
if(ctime !== 0) {
o.ctime = ctime; o.ct = read_date(blob, blob.l-8);
@ -328,12 +355,12 @@ function read_date(blob, offset) {
}
var fs;
function readFileSync(filename, options) {
function readFileSync(filename/*:string*/, options/*:any*/) {
if(fs === undefined) fs = require('fs');
return parse(fs.readFileSync(filename), options);
}
function readSync(blob, options) {
function readSync(blob/*:any*/, options/*:any*/) {
switch(options !== undefined && options.type !== undefined ? options.type : "base64") {
case "file": return readFileSync(blob, options);
case "base64": return parse(s2a(Base64.decode(blob)), options);

View File

@ -1,9 +1,9 @@
function getdatastr(data)/*:?string*/ {
if(!data) return null;
if(data.data) return debom_xml(data.data);
if(data.asNodeBuffer && has_buf) return debom_xml(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom_xml(data.asBinary());
if(data._data && data._data.getContent) return debom_xml(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
if(data.data) return debom(data.data);
if(data.asNodeBuffer && has_buf) return debom(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom(data.asBinary());
if(data._data && data._data.getContent) return debom(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
return null;
}
@ -21,10 +21,14 @@ function getdatabin(data) {
function getdata(data) { return (data && data.name.slice(-4) === ".bin") ? getdatabin(data) : getdatastr(data); }
/* Part 2 Section 10.1.2 "Mapping Content Types" Names are case-insensitive */
function safegetzipfile(zip, file/*:string*/) {
var f = file; if(zip.files[f]) return zip.files[f];
f = file.toLowerCase(); if(zip.files[f]) return zip.files[f];
f = f.replace(/\//g,'\\'); if(zip.files[f]) return zip.files[f];
var k = keys(zip.files);
var f = file.toLowerCase(), g = f.replace(/\//g,'\\');
for(var i=0; i<k.length; ++i) {
var n = k[i].toLowerCase();
if(f == n || g == n) return zip.files[k[i]];
}
return null;
}

View File

@ -158,7 +158,7 @@ function xlml_normalize(d)/*:string*/ {
/* TODO: Everything */
var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg;
function parse_xlml_xml(d, opts) {
var str = xlml_normalize(d);
var str = debom(xlml_normalize(d));
var Rn;
var state = [], tmp;
var sheets = {}, sheetnames = [], cursheet = {}, sheetname = "";

View File

@ -32,6 +32,7 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
case 0x09: return parse_xlscfb(s2a(o.type === 'base64' ? Base64.decode(d) : d), o);
case 0x3C: return parse_xlml(d, o);
case 0x50: return read_zip(d, o);
case 0xEF: return parse_xlml(d, o);
default: throw new Error("Unsupported file " + n);
}
}

View File

@ -14,23 +14,28 @@ function write_zip_type(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
return z.generate(oopts);
}
/* TODO: test consistency */
function write_string_type(out/*:string*/, opts/*:WriteOpts*/) {
switch(opts.type) {
case "base64": break; // TODO
case "binary": break; // TODO
case "base64": return Base64.encode(out);
case "binary": return out;
case "file": return _fs.writeFileSync(opts.file, out, {encoding:'utf8'});
case "buffer": break; // TODO
case "buffer": {
if(has_buf) return new Buffer(out, 'utf8');
else return out.split("").map(function(c) { return c.charCodeAt(0); });
} break;
default: return out;
}
}
/* TODO: test consistency */
function write_binary_type(out, opts/*:WriteOpts*/) {
switch(opts.type) {
case "base64": break; // TODO
case "base64":
case "binary":
var bstr = "";
for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
return bstr;
return opts.type == 'base64' ? Base64.encode(bstr) : bstr;
case "file": return _fs.writeFileSync(opts.file, out);
case "buffer": return out;
default: throw new Error("Unrecognized type " + opts.type);
@ -39,12 +44,16 @@ function write_binary_type(out, opts/*:WriteOpts*/) {
function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
var o = opts||{};
switch(o.bookType) {
switch(o.bookType || 'xlsx') {
case 'xml': return write_string_type(write_xlml(wb, o), o);
case 'csv': return write_string_type(write_csv_str(wb, o), o);
case 'fods': return write_string_type(write_ods(wb, o), o);
case 'biff2': return write_binary_type(write_biff_buf(wb, o), o);
default: return write_zip_type(wb, o);
case 'xlsx':
case 'xlsm':
case 'xlsb':
case 'ods': return write_zip_type(wb, o);
default: throw new Error ("Unrecognized bookType |" + o.bookType + "|");
}
}

2
dist/ods.js vendored
View File

@ -290,7 +290,7 @@ var parse_content_xml = (function() {
var rept = 1, isstub = false;
var i = 0;
xlmlregex.lastIndex = 0;
while((Rn = xlmlregex.exec(str))) switch(Rn[3]=Rn[3].replace(/_.*$/,"")) {
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
case 'table': case '工作表': // 9.1.2 <table:table>
if(Rn[1]==='/') {

2
dist/ods.min.map vendored

File diff suppressed because one or more lines are too long

22
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

22
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

67
dist/xlsx.js vendored
View File

@ -4,7 +4,7 @@
/*jshint funcscope:true, eqnull:true */
var XLSX = {};
(function make_xlsx(XLSX){
XLSX.version = '0.8.5';
XLSX.version = '0.8.6';
var current_codepage = 1200, current_cptable;
if(typeof module !== "undefined" && typeof require !== 'undefined') {
if(typeof cptable === 'undefined') cptable = require('./dist/cpexcel');
@ -14,12 +14,18 @@ function reset_cp() { set_cp(1200); }
var set_cp = function(cp) { current_codepage = cp; };
function char_codes(data) { var o = []; for(var i = 0, len = data.length; i < len; ++i) o[i] = data.charCodeAt(i); return o; }
var debom_xml = function(data) { return data; };
var debom = function(data) {
var c1 = data.charCodeAt(0), c2 = data.charCodeAt(1);
if(c1 == 0xFF && c2 == 0xFE) return data.substr(2);
if(c1 == 0xFE && c2 == 0xFF) return data.substr(2);
if(c1 == 0xFEFF) return data.substr(1);
return data;
};
var _getchar = function _gc1(x) { return String.fromCharCode(x); };
if(typeof cptable !== 'undefined') {
set_cp = function(cp) { current_codepage = cp; current_cptable = cptable[cp]; };
debom_xml = function(data) {
debom = function(data) {
if(data.charCodeAt(0) === 0xFF && data.charCodeAt(1) === 0xFE) { return cptable.utils.decode(1200, char_codes(data.substr(2))); }
return data;
};
@ -871,14 +877,14 @@ var XLMLFormatMap/*{[string]:string}*/ = ({
});
var DO_NOT_EXPORT_CFB = true;
/* cfb.js (C) 2013-2014 SheetJS -- http://sheetjs.com */
/* cfb.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
/*jshint eqnull:true */
/* [MS-CFB] v20130118 */
var CFB = (function _CFB(){
var exports = {};
exports.version = '0.10.2';
exports.version = '0.11.0';
function parse(file) {
var mver = 3; // major version
var ssz = 512; // sector size
@ -1088,6 +1094,7 @@ function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) {
if(cnt !== 0) throw "DIFAT chain shorter than expected";
} else if(idx !== -1 /*FREESECT*/) {
var sector = sectors[idx], m = (ssz>>>2)-1;
if(!sector) return;
for(var i = 0; i < m; ++i) {
if((q = __readInt32LE(sector,i*4)) === ENDOFCHAIN) break;
fat_addrs.push(q);
@ -1111,6 +1118,7 @@ function get_sector_list(sectors, start, fat_addrs, ssz, chkd) {
var addr = fat_addrs[Math.floor(j*4/ssz)];
jj = ((j*4) & modulus);
if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz;
if(!sectors[addr]) break;
j = __readInt32LE(sectors[addr], jj);
}
return {nodes: buf, data:__toBuffer([buf_chain])};
@ -1133,6 +1141,7 @@ function make_sector_list(sectors, dir_start, fat_addrs, ssz) {
var addr = fat_addrs[Math.floor(j*4/ssz)];
jj = ((j*4) & modulus);
if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz;
if(!sectors[addr]) break;
j = __readInt32LE(sectors[addr], jj);
}
sector_list[k] = {nodes: buf, data:__toBuffer([buf_chain])};
@ -1153,7 +1162,7 @@ function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, Fil
if(namelen === 0) continue;
name = __utf16le(blob,0,namelen-pl);
Paths.push(name);
o = {
o = ({
name: name,
type: blob.read_shift(1),
color: blob.read_shift(1),
@ -1162,7 +1171,7 @@ function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, Fil
C: blob.read_shift(4, 'i'),
clsid: blob.read_shift(16),
state: blob.read_shift(4, 'i')
};
});
ctime = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2);
if(ctime !== 0) {
o.ctime = ctime; o.ct = read_date(blob, blob.l-8);
@ -1314,10 +1323,10 @@ function dup(o) {
function fill(c,l) { var o = ""; while(o.length < l) o+=c; return o; }
function getdatastr(data) {
if(!data) return null;
if(data.data) return debom_xml(data.data);
if(data.asNodeBuffer && has_buf) return debom_xml(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom_xml(data.asBinary());
if(data._data && data._data.getContent) return debom_xml(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
if(data.data) return debom(data.data);
if(data.asNodeBuffer && has_buf) return debom(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom(data.asBinary());
if(data._data && data._data.getContent) return debom(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
return null;
}
@ -1335,10 +1344,14 @@ function getdatabin(data) {
function getdata(data) { return (data && data.name.slice(-4) === ".bin") ? getdatabin(data) : getdatastr(data); }
/* Part 2 Section 10.1.2 "Mapping Content Types" Names are case-insensitive */
function safegetzipfile(zip, file) {
var f = file; if(zip.files[f]) return zip.files[f];
f = file.toLowerCase(); if(zip.files[f]) return zip.files[f];
f = f.replace(/\//g,'\\'); if(zip.files[f]) return zip.files[f];
var k = keys(zip.files);
var f = file.toLowerCase(), g = f.replace(/\//g,'\\');
for(var i=0; i<k.length; ++i) {
var n = k[i].toLowerCase();
if(f == n || g == n) return zip.files[k[i]];
}
return null;
}
@ -9464,7 +9477,7 @@ function xlml_normalize(d) {
/* TODO: Everything */
var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg;
function parse_xlml_xml(d, opts) {
var str = xlml_normalize(d);
var str = debom(xlml_normalize(d));
var Rn;
var state = [], tmp;
var sheets = {}, sheetnames = [], cursheet = {}, sheetname = "";
@ -12418,6 +12431,7 @@ function readSync(data, opts) {
case 0x09: return parse_xlscfb(s2a(o.type === 'base64' ? Base64.decode(d) : d), o);
case 0x3C: return parse_xlml(d, o);
case 0x50: return read_zip(d, o);
case 0xEF: return parse_xlml(d, o);
default: throw new Error("Unsupported file " + n);
}
}
@ -12442,23 +12456,28 @@ function write_zip_type(wb, opts) {
return z.generate(oopts);
}
/* TODO: test consistency */
function write_string_type(out, opts) {
switch(opts.type) {
case "base64": break; // TODO
case "binary": break; // TODO
case "base64": return Base64.encode(out);
case "binary": return out;
case "file": return _fs.writeFileSync(opts.file, out, {encoding:'utf8'});
case "buffer": break; // TODO
case "buffer": {
if(has_buf) return new Buffer(out, 'utf8');
else return out.split("").map(function(c) { return c.charCodeAt(0); });
} break;
default: return out;
}
}
/* TODO: test consistency */
function write_binary_type(out, opts) {
switch(opts.type) {
case "base64": break; // TODO
case "base64":
case "binary":
var bstr = "";
for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
return bstr;
return opts.type == 'base64' ? Base64.encode(bstr) : bstr;
case "file": return _fs.writeFileSync(opts.file, out);
case "buffer": return out;
default: throw new Error("Unrecognized type " + opts.type);
@ -12467,12 +12486,16 @@ function write_binary_type(out, opts) {
function writeSync(wb, opts) {
var o = opts||{};
switch(o.bookType) {
switch(o.bookType || 'xlsx') {
case 'xml': return write_string_type(write_xlml(wb, o), o);
case 'csv': return write_string_type(write_csv_str(wb, o), o);
case 'fods': return write_string_type(write_ods(wb, o), o);
case 'biff2': return write_binary_type(write_biff_buf(wb, o), o);
default: return write_zip_type(wb, o);
case 'xlsx':
case 'xlsm':
case 'xlsb':
case 'ods': return write_zip_type(wb, o);
default: throw new Error ("Unrecognized bookType |" + o.bookType + "|");
}
}

20
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

View File

@ -227,7 +227,7 @@ function handleDrop(e) {
if(rABS) {
wb = X.read(data, {type: 'binary'});
} else {
var arr = fixdata(data);
var arr = fixdata(data);
wb = X.read(btoa(arr), {type: 'base64'});
}
process_wb(wb);

View File

@ -292,7 +292,7 @@ var parse_content_xml = (function() {
var rept = 1, isstub = false;
var i = 0;
xlmlregex.lastIndex = 0;
while((Rn = xlmlregex.exec(str))) switch(Rn[3]=Rn[3].replace(/_.*$/,"")) {
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
case 'table': case '工作表': // 9.1.2 <table:table>
if(Rn[1]==='/') {

2
ods.js
View File

@ -290,7 +290,7 @@ var parse_content_xml = (function() {
var rept = 1, isstub = false;
var i = 0;
xlmlregex.lastIndex = 0;
while((Rn = xlmlregex.exec(str))) switch(Rn[3]=Rn[3].replace(/_.*$/,"")) {
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
case 'table': case '工作表': // 9.1.2 <table:table>
if(Rn[1]==='/') {

View File

@ -30,7 +30,7 @@ var parse_content_xml = (function() {
var rept = 1, isstub = false;
var i = 0;
xlmlregex.lastIndex = 0;
while((Rn = xlmlregex.exec(str))) switch(Rn[3]=Rn[3].replace(/_.*$/,"")) {
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
case 'table': case '工作表': // 9.1.2 <table:table>
if(Rn[1]==='/') {

View File

@ -1,6 +1,6 @@
{
"name": "xlsx",
"version": "0.8.5",
"version": "0.8.6",
"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" ],
@ -12,7 +12,7 @@
"exit-on-epipe":"",
"ssf":"~0.8.1",
"codepage":"",
"cfb":">=0.10.0",
"cfb":"~0.11.0",
"crc-32":"",
"adler-32":"",
"commander":""

View File

@ -91,3 +91,12 @@ XLSX.writeFile(wb, 'sheetjs.xls', {bookType:'biff2'});
XLSX.writeFile(wb, 'sheetjs.ods');
XLSX.writeFile(wb, 'sheetjs.fods');
XLSX.writeFile(wb, 'sheetjs.csv');
/* test by reading back files */
XLSX.readFile('sheetjs.xlsx');
XLSX.readFile('sheetjs.xlsm');
XLSX.readFile('sheetjs.xlsb');
XLSX.readFile('sheetjs.xls');
XLSX.readFile('sheetjs.ods');
XLSX.readFile('sheetjs.fods');
//XLSX.readFile('sheetjs.csv');

View File

@ -4,7 +4,7 @@
/*jshint funcscope:true, eqnull:true */
var XLSX = {};
(function make_xlsx(XLSX){
XLSX.version = '0.8.5';
XLSX.version = '0.8.6';
var current_codepage = 1200, current_cptable;
if(typeof module !== "undefined" && typeof require !== 'undefined') {
if(typeof cptable === 'undefined') cptable = require('./dist/cpexcel');
@ -14,12 +14,18 @@ function reset_cp() { set_cp(1200); }
var set_cp = function(cp) { current_codepage = cp; };
function char_codes(data) { var o = []; for(var i = 0, len = data.length; i < len; ++i) o[i] = data.charCodeAt(i); return o; }
var debom_xml = function(data) { return data; };
var debom = function(data/*:string*/)/*:string*/ {
var c1 = data.charCodeAt(0), c2 = data.charCodeAt(1);
if(c1 == 0xFF && c2 == 0xFE) return data.substr(2);
if(c1 == 0xFE && c2 == 0xFF) return data.substr(2);
if(c1 == 0xFEFF) return data.substr(1);
return data;
};
var _getchar = function _gc1(x) { return String.fromCharCode(x); };
if(typeof cptable !== 'undefined') {
set_cp = function(cp) { current_codepage = cp; current_cptable = cptable[cp]; };
debom_xml = function(data) {
debom = function(data) {
if(data.charCodeAt(0) === 0xFF && data.charCodeAt(1) === 0xFE) { return cptable.utils.decode(1200, char_codes(data.substr(2))); }
return data;
};
@ -31,7 +37,7 @@ if(typeof cptable !== 'undefined') {
var Base64 = (function make_b64(){
var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
return {
encode: function(input, utf8) {
encode: function(input/*:string*/, utf8)/*:string*/ {
var o = "";
var c1, c2, c3, e1, e2, e3, e4;
for(var i = 0; i < input.length; ) {
@ -48,7 +54,7 @@ var Base64 = (function make_b64(){
}
return o;
},
decode: function b64_decode(input, utf8) {
decode: function b64_decode(input/*:string*/, utf8)/*:string*/ {
var o = "";
var c1, c2, c3;
var e1, e2, e3, e4;
@ -888,14 +894,38 @@ var XLMLFormatMap/*{[string]:string}*/ = ({
}/*:any*/);
var DO_NOT_EXPORT_CFB = true;
/* cfb.js (C) 2013-2014 SheetJS -- http://sheetjs.com */
/*::
declare var Base64:any;
declare var ReadShift:any;
declare var CheckField:any;
declare var prep_blob:any;
declare var __readUInt32LE:any;
declare var __readInt32LE:any;
declare var __toBuffer:any;
declare var __utf16le:any;
declare var bconcat:any;
declare var s2a:any;
declare var chr0:any;
declare var chr1:any;
*/
/* cfb.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
/*jshint eqnull:true */
/*::
declare var DO_NOT_EXPORT_CFB:any;
type SectorEntry = any;
type SectorList = {
(k:string|number):SectorEntry;
name:?string;
fat_addrs:any;
ssz:number;
}
*/
/* [MS-CFB] v20130118 */
var CFB = (function _CFB(){
var exports = {};
exports.version = '0.10.2';
exports.version = '0.11.0';
function parse(file) {
var mver = 3; // major version
var ssz = 512; // sector size
@ -908,7 +938,7 @@ var difat_start = 0; // first mini FAT sector location
var fat_addrs = []; // locations of FAT sectors
/* [MS-CFB] 2.2 Compound File Header */
var blob = file.slice(0,512);
var blob/*:any*/ = file.slice(0,512);
prep_blob(blob, 0);
/* major version */
@ -968,7 +998,7 @@ var sectors = sectorify(file, ssz);
sleuth_fat(difat_start, ndfs, sectors, ssz, fat_addrs);
/** Chains */
var sector_list = make_sector_list(sectors, dir_start, fat_addrs, ssz);
var sector_list/*:SectorList*/ = make_sector_list(sectors, dir_start, fat_addrs, ssz);
sector_list[dir_start].name = "!Directory";
if(nmfs > 0 && minifat_start !== ENDOFCHAIN) sector_list[minifat_start].name = "!MiniFAT";
@ -977,7 +1007,7 @@ sector_list.fat_addrs = fat_addrs;
sector_list.ssz = ssz;
/* [MS-CFB] 2.6.1 Compound File Directory Entry */
var files = {}, Paths = [], FileIndex = [], FullPaths = [], FullPathDir = {};
var files = {}, Paths/*:any*/ = [], FileIndex = [], FullPaths = [], FullPathDir = {};
read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, FileIndex);
build_full_paths(FileIndex, FullPathDir, FullPaths, Paths);
@ -1086,7 +1116,7 @@ function make_find_path(FullPaths, Paths, FileIndex, files, root_name) {
var UCPaths = new Array(Paths.length), i;
for(i = 0; i < FullPaths.length; ++i) UCFullPaths[i] = FullPaths[i].toUpperCase().replace(chr0,'').replace(chr1,'!');
for(i = 0; i < Paths.length; ++i) UCPaths[i] = Paths[i].toUpperCase().replace(chr0,'').replace(chr1,'!');
return function find_path(path) {
return function find_path(path/*:string*/) {
var k;
if(path.charCodeAt(0) === 47 /* "/" */) { k=true; path = root_name + path; }
else k = path.indexOf("/") !== -1;
@ -1105,6 +1135,7 @@ function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) {
if(cnt !== 0) throw "DIFAT chain shorter than expected";
} else if(idx !== -1 /*FREESECT*/) {
var sector = sectors[idx], m = (ssz>>>2)-1;
if(!sector) return;
for(var i = 0; i < m; ++i) {
if((q = __readInt32LE(sector,i*4)) === ENDOFCHAIN) break;
fat_addrs.push(q);
@ -1128,13 +1159,14 @@ function get_sector_list(sectors, start, fat_addrs, ssz, chkd) {
var addr = fat_addrs[Math.floor(j*4/ssz)];
jj = ((j*4) & modulus);
if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz;
if(!sectors[addr]) break;
j = __readInt32LE(sectors[addr], jj);
}
return {nodes: buf, data:__toBuffer([buf_chain])};
}
/** Chase down the sector linked lists */
function make_sector_list(sectors, dir_start, fat_addrs, ssz) {
function make_sector_list(sectors, dir_start, fat_addrs, ssz/*:number*/)/*:any*/ {
var sl = sectors.length, sector_list = new Array(sl);
var chkd = new Array(sl), buf, buf_chain;
var modulus = ssz - 1, i, j, k, jj;
@ -1150,6 +1182,7 @@ function make_sector_list(sectors, dir_start, fat_addrs, ssz) {
var addr = fat_addrs[Math.floor(j*4/ssz)];
jj = ((j*4) & modulus);
if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz;
if(!sectors[addr]) break;
j = __readInt32LE(sectors[addr], jj);
}
sector_list[k] = {nodes: buf, data:__toBuffer([buf_chain])};
@ -1170,7 +1203,7 @@ function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, Fil
if(namelen === 0) continue;
name = __utf16le(blob,0,namelen-pl);
Paths.push(name);
o = {
o = ({
name: name,
type: blob.read_shift(1),
color: blob.read_shift(1),
@ -1179,7 +1212,7 @@ function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, Fil
C: blob.read_shift(4, 'i'),
clsid: blob.read_shift(16),
state: blob.read_shift(4, 'i')
};
}/*:any*/);
ctime = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2);
if(ctime !== 0) {
o.ctime = ctime; o.ct = read_date(blob, blob.l-8);
@ -1217,12 +1250,12 @@ function read_date(blob, offset) {
}
var fs;
function readFileSync(filename, options) {
function readFileSync(filename/*:string*/, options/*:any*/) {
if(fs === undefined) fs = require('fs');
return parse(fs.readFileSync(filename), options);
}
function readSync(blob, options) {
function readSync(blob/*:any*/, options/*:any*/) {
switch(options !== undefined && options.type !== undefined ? options.type : "base64") {
case "file": return readFileSync(blob, options);
case "base64": return parse(s2a(Base64.decode(blob)), options);
@ -1331,10 +1364,10 @@ function dup(o/*:any*/)/*:any*/ {
function fill(c/*:string*/,l/*:number*/)/*:string*/ { var o = ""; while(o.length < l) o+=c; return o; }
function getdatastr(data)/*:?string*/ {
if(!data) return null;
if(data.data) return debom_xml(data.data);
if(data.asNodeBuffer && has_buf) return debom_xml(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom_xml(data.asBinary());
if(data._data && data._data.getContent) return debom_xml(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
if(data.data) return debom(data.data);
if(data.asNodeBuffer && has_buf) return debom(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom(data.asBinary());
if(data._data && data._data.getContent) return debom(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
return null;
}
@ -1352,10 +1385,14 @@ function getdatabin(data) {
function getdata(data) { return (data && data.name.slice(-4) === ".bin") ? getdatabin(data) : getdatastr(data); }
/* Part 2 Section 10.1.2 "Mapping Content Types" Names are case-insensitive */
function safegetzipfile(zip, file/*:string*/) {
var f = file; if(zip.files[f]) return zip.files[f];
f = file.toLowerCase(); if(zip.files[f]) return zip.files[f];
f = f.replace(/\//g,'\\'); if(zip.files[f]) return zip.files[f];
var k = keys(zip.files);
var f = file.toLowerCase(), g = f.replace(/\//g,'\\');
for(var i=0; i<k.length; ++i) {
var n = k[i].toLowerCase();
if(f == n || g == n) return zip.files[k[i]];
}
return null;
}
@ -9484,7 +9521,7 @@ function xlml_normalize(d)/*:string*/ {
/* TODO: Everything */
var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg;
function parse_xlml_xml(d, opts) {
var str = xlml_normalize(d);
var str = debom(xlml_normalize(d));
var Rn;
var state = [], tmp;
var sheets = {}, sheetnames = [], cursheet = {}, sheetname = "";
@ -12440,6 +12477,7 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
case 0x09: return parse_xlscfb(s2a(o.type === 'base64' ? Base64.decode(d) : d), o);
case 0x3C: return parse_xlml(d, o);
case 0x50: return read_zip(d, o);
case 0xEF: return parse_xlml(d, o);
default: throw new Error("Unsupported file " + n);
}
}
@ -12464,23 +12502,28 @@ function write_zip_type(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
return z.generate(oopts);
}
/* TODO: test consistency */
function write_string_type(out/*:string*/, opts/*:WriteOpts*/) {
switch(opts.type) {
case "base64": break; // TODO
case "binary": break; // TODO
case "base64": return Base64.encode(out);
case "binary": return out;
case "file": return _fs.writeFileSync(opts.file, out, {encoding:'utf8'});
case "buffer": break; // TODO
case "buffer": {
if(has_buf) return new Buffer(out, 'utf8');
else return out.split("").map(function(c) { return c.charCodeAt(0); });
} break;
default: return out;
}
}
/* TODO: test consistency */
function write_binary_type(out, opts/*:WriteOpts*/) {
switch(opts.type) {
case "base64": break; // TODO
case "base64":
case "binary":
var bstr = "";
for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
return bstr;
return opts.type == 'base64' ? Base64.encode(bstr) : bstr;
case "file": return _fs.writeFileSync(opts.file, out);
case "buffer": return out;
default: throw new Error("Unrecognized type " + opts.type);
@ -12489,12 +12532,16 @@ function write_binary_type(out, opts/*:WriteOpts*/) {
function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
var o = opts||{};
switch(o.bookType) {
switch(o.bookType || 'xlsx') {
case 'xml': return write_string_type(write_xlml(wb, o), o);
case 'csv': return write_string_type(write_csv_str(wb, o), o);
case 'fods': return write_string_type(write_ods(wb, o), o);
case 'biff2': return write_binary_type(write_biff_buf(wb, o), o);
default: return write_zip_type(wb, o);
case 'xlsx':
case 'xlsm':
case 'xlsb':
case 'ods': return write_zip_type(wb, o);
default: throw new Error ("Unrecognized bookType |" + o.bookType + "|");
}
}

67
xlsx.js
View File

@ -4,7 +4,7 @@
/*jshint funcscope:true, eqnull:true */
var XLSX = {};
(function make_xlsx(XLSX){
XLSX.version = '0.8.5';
XLSX.version = '0.8.6';
var current_codepage = 1200, current_cptable;
if(typeof module !== "undefined" && typeof require !== 'undefined') {
if(typeof cptable === 'undefined') cptable = require('./dist/cpexcel');
@ -14,12 +14,18 @@ function reset_cp() { set_cp(1200); }
var set_cp = function(cp) { current_codepage = cp; };
function char_codes(data) { var o = []; for(var i = 0, len = data.length; i < len; ++i) o[i] = data.charCodeAt(i); return o; }
var debom_xml = function(data) { return data; };
var debom = function(data) {
var c1 = data.charCodeAt(0), c2 = data.charCodeAt(1);
if(c1 == 0xFF && c2 == 0xFE) return data.substr(2);
if(c1 == 0xFE && c2 == 0xFF) return data.substr(2);
if(c1 == 0xFEFF) return data.substr(1);
return data;
};
var _getchar = function _gc1(x) { return String.fromCharCode(x); };
if(typeof cptable !== 'undefined') {
set_cp = function(cp) { current_codepage = cp; current_cptable = cptable[cp]; };
debom_xml = function(data) {
debom = function(data) {
if(data.charCodeAt(0) === 0xFF && data.charCodeAt(1) === 0xFE) { return cptable.utils.decode(1200, char_codes(data.substr(2))); }
return data;
};
@ -871,14 +877,14 @@ var XLMLFormatMap/*{[string]:string}*/ = ({
});
var DO_NOT_EXPORT_CFB = true;
/* cfb.js (C) 2013-2014 SheetJS -- http://sheetjs.com */
/* cfb.js (C) 2013-present SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
/*jshint eqnull:true */
/* [MS-CFB] v20130118 */
var CFB = (function _CFB(){
var exports = {};
exports.version = '0.10.2';
exports.version = '0.11.0';
function parse(file) {
var mver = 3; // major version
var ssz = 512; // sector size
@ -1088,6 +1094,7 @@ function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) {
if(cnt !== 0) throw "DIFAT chain shorter than expected";
} else if(idx !== -1 /*FREESECT*/) {
var sector = sectors[idx], m = (ssz>>>2)-1;
if(!sector) return;
for(var i = 0; i < m; ++i) {
if((q = __readInt32LE(sector,i*4)) === ENDOFCHAIN) break;
fat_addrs.push(q);
@ -1111,6 +1118,7 @@ function get_sector_list(sectors, start, fat_addrs, ssz, chkd) {
var addr = fat_addrs[Math.floor(j*4/ssz)];
jj = ((j*4) & modulus);
if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz;
if(!sectors[addr]) break;
j = __readInt32LE(sectors[addr], jj);
}
return {nodes: buf, data:__toBuffer([buf_chain])};
@ -1133,6 +1141,7 @@ function make_sector_list(sectors, dir_start, fat_addrs, ssz) {
var addr = fat_addrs[Math.floor(j*4/ssz)];
jj = ((j*4) & modulus);
if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz;
if(!sectors[addr]) break;
j = __readInt32LE(sectors[addr], jj);
}
sector_list[k] = {nodes: buf, data:__toBuffer([buf_chain])};
@ -1153,7 +1162,7 @@ function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, Fil
if(namelen === 0) continue;
name = __utf16le(blob,0,namelen-pl);
Paths.push(name);
o = {
o = ({
name: name,
type: blob.read_shift(1),
color: blob.read_shift(1),
@ -1162,7 +1171,7 @@ function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, Fil
C: blob.read_shift(4, 'i'),
clsid: blob.read_shift(16),
state: blob.read_shift(4, 'i')
};
});
ctime = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2);
if(ctime !== 0) {
o.ctime = ctime; o.ct = read_date(blob, blob.l-8);
@ -1314,10 +1323,10 @@ function dup(o) {
function fill(c,l) { var o = ""; while(o.length < l) o+=c; return o; }
function getdatastr(data) {
if(!data) return null;
if(data.data) return debom_xml(data.data);
if(data.asNodeBuffer && has_buf) return debom_xml(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom_xml(data.asBinary());
if(data._data && data._data.getContent) return debom_xml(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
if(data.data) return debom(data.data);
if(data.asNodeBuffer && has_buf) return debom(data.asNodeBuffer().toString('binary'));
if(data.asBinary) return debom(data.asBinary());
if(data._data && data._data.getContent) return debom(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
return null;
}
@ -1335,10 +1344,14 @@ function getdatabin(data) {
function getdata(data) { return (data && data.name.slice(-4) === ".bin") ? getdatabin(data) : getdatastr(data); }
/* Part 2 Section 10.1.2 "Mapping Content Types" Names are case-insensitive */
function safegetzipfile(zip, file) {
var f = file; if(zip.files[f]) return zip.files[f];
f = file.toLowerCase(); if(zip.files[f]) return zip.files[f];
f = f.replace(/\//g,'\\'); if(zip.files[f]) return zip.files[f];
var k = keys(zip.files);
var f = file.toLowerCase(), g = f.replace(/\//g,'\\');
for(var i=0; i<k.length; ++i) {
var n = k[i].toLowerCase();
if(f == n || g == n) return zip.files[k[i]];
}
return null;
}
@ -9464,7 +9477,7 @@ function xlml_normalize(d) {
/* TODO: Everything */
var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg;
function parse_xlml_xml(d, opts) {
var str = xlml_normalize(d);
var str = debom(xlml_normalize(d));
var Rn;
var state = [], tmp;
var sheets = {}, sheetnames = [], cursheet = {}, sheetname = "";
@ -12418,6 +12431,7 @@ function readSync(data, opts) {
case 0x09: return parse_xlscfb(s2a(o.type === 'base64' ? Base64.decode(d) : d), o);
case 0x3C: return parse_xlml(d, o);
case 0x50: return read_zip(d, o);
case 0xEF: return parse_xlml(d, o);
default: throw new Error("Unsupported file " + n);
}
}
@ -12442,23 +12456,28 @@ function write_zip_type(wb, opts) {
return z.generate(oopts);
}
/* TODO: test consistency */
function write_string_type(out, opts) {
switch(opts.type) {
case "base64": break; // TODO
case "binary": break; // TODO
case "base64": return Base64.encode(out);
case "binary": return out;
case "file": return _fs.writeFileSync(opts.file, out, {encoding:'utf8'});
case "buffer": break; // TODO
case "buffer": {
if(has_buf) return new Buffer(out, 'utf8');
else return out.split("").map(function(c) { return c.charCodeAt(0); });
} break;
default: return out;
}
}
/* TODO: test consistency */
function write_binary_type(out, opts) {
switch(opts.type) {
case "base64": break; // TODO
case "base64":
case "binary":
var bstr = "";
for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
return bstr;
return opts.type == 'base64' ? Base64.encode(bstr) : bstr;
case "file": return _fs.writeFileSync(opts.file, out);
case "buffer": return out;
default: throw new Error("Unrecognized type " + opts.type);
@ -12467,12 +12486,16 @@ function write_binary_type(out, opts) {
function writeSync(wb, opts) {
var o = opts||{};
switch(o.bookType) {
switch(o.bookType || 'xlsx') {
case 'xml': return write_string_type(write_xlml(wb, o), o);
case 'csv': return write_string_type(write_csv_str(wb, o), o);
case 'fods': return write_string_type(write_ods(wb, o), o);
case 'biff2': return write_binary_type(write_biff_buf(wb, o), o);
default: return write_zip_type(wb, o);
case 'xlsx':
case 'xlsm':
case 'xlsb':
case 'ods': return write_zip_type(wb, o);
default: throw new Error ("Unrecognized bookType |" + o.bookType + "|");
}
}