1/*! xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
2/* vim: set ts=2: */
3/*exported XLSX */
4/*global process:false, Buffer:false, ArrayBuffer:false, DataView:false, Deno:false */
5var XLSX = {};
6XLSX.version = '0.18.12';
7var current_codepage = 1200, current_ansi = 1252;
8/*:: declare var cptable:any; */
9/*global cptable:true, window */
10var $cptable;
11
12var VALID_ANSI = [ 874, 932, 936, 949, 950, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, 10000 ];
13/* ECMA-376 Part I 18.4.1 charset to codepage mapping */
14var CS2CP = ({
15	/*::[*/0/*::]*/:    1252, /* ANSI */
16	/*::[*/1/*::]*/:   65001, /* DEFAULT */
17	/*::[*/2/*::]*/:   65001, /* SYMBOL */
18	/*::[*/77/*::]*/:  10000, /* MAC */
19	/*::[*/128/*::]*/:   932, /* SHIFTJIS */
20	/*::[*/129/*::]*/:   949, /* HANGUL */
21	/*::[*/130/*::]*/:  1361, /* JOHAB */
22	/*::[*/134/*::]*/:   936, /* GB2312 */
23	/*::[*/136/*::]*/:   950, /* CHINESEBIG5 */
24	/*::[*/161/*::]*/:  1253, /* GREEK */
25	/*::[*/162/*::]*/:  1254, /* TURKISH */
26	/*::[*/163/*::]*/:  1258, /* VIETNAMESE */
27	/*::[*/177/*::]*/:  1255, /* HEBREW */
28	/*::[*/178/*::]*/:  1256, /* ARABIC */
29	/*::[*/186/*::]*/:  1257, /* BALTIC */
30	/*::[*/204/*::]*/:  1251, /* RUSSIAN */
31	/*::[*/222/*::]*/:   874, /* THAI */
32	/*::[*/238/*::]*/:  1250, /* EASTEUROPE */
33	/*::[*/255/*::]*/:  1252, /* OEM */
34	/*::[*/69/*::]*/:   6969  /* MISC */
35}/*:any*/);
36
37var set_ansi = function(cp/*:number*/) { if(VALID_ANSI.indexOf(cp) == -1) return; current_ansi = CS2CP[0] = cp; };
38function reset_ansi() { set_ansi(1252); }
39
40var set_cp = function(cp/*:number*/) { current_codepage = cp; set_ansi(cp); };
41function reset_cp() { set_cp(1200); reset_ansi(); }
42
43function char_codes(data/*:string*/)/*:Array<number>*/ { var o/*:Array<number>*/ = []; for(var i = 0, len = data.length; i < len; ++i) o[i] = data.charCodeAt(i); return o; }
44
45function utf16leread(data/*:string*/)/*:string*/ {
46	var o/*:Array<string>*/ = [];
47	for(var i = 0; i < (data.length>>1); ++i) o[i] = String.fromCharCode(data.charCodeAt(2*i) + (data.charCodeAt(2*i+1)<<8));
48	return o.join("");
49}
50function utf16beread(data/*:string*/)/*:string*/ {
51	var o/*:Array<string>*/ = [];
52	for(var i = 0; i < (data.length>>1); ++i) o[i] = String.fromCharCode(data.charCodeAt(2*i+1) + (data.charCodeAt(2*i)<<8));
53	return o.join("");
54}
55
56var debom = function(data/*:string*/)/*:string*/ {
57	var c1 = data.charCodeAt(0), c2 = data.charCodeAt(1);
58	if(c1 == 0xFF && c2 == 0xFE) return utf16leread(data.slice(2));
59	if(c1 == 0xFE && c2 == 0xFF) return utf16beread(data.slice(2));
60	if(c1 == 0xFEFF) return data.slice(1);
61	return data;
62};
63
64var _getchar = function _gc1(x/*:number*/)/*:string*/ { return String.fromCharCode(x); };
65var _getansi = function _ga1(x/*:number*/)/*:string*/ { return String.fromCharCode(x); };
66
67function set_cptable(cptable) {
68	$cptable = cptable;
69	set_cp = function(cp/*:number*/) { current_codepage = cp; set_ansi(cp); };
70	debom = function(data/*:string*/) {
71		if(data.charCodeAt(0) === 0xFF && data.charCodeAt(1) === 0xFE) { return $cptable.utils.decode(1200, char_codes(data.slice(2))); }
72		return data;
73	};
74	_getchar = function _gc2(x/*:number*/)/*:string*/ {
75		if(current_codepage === 1200) return String.fromCharCode(x);
76		return $cptable.utils.decode(current_codepage, [x&255,x>>8])[0];
77	};
78	_getansi = function _ga2(x/*:number*/)/*:string*/ {
79		return $cptable.utils.decode(current_ansi, [x])[0];
80	};
81	cpdoit();
82}
83var DENSE = null;
84var DIF_XL = true;
85var Base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
86function Base64_encode(input) {
87  var o = "";
88  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
89  for (var i = 0; i < input.length; ) {
90    c1 = input.charCodeAt(i++);
91    e1 = c1 >> 2;
92    c2 = input.charCodeAt(i++);
93    e2 = (c1 & 3) << 4 | c2 >> 4;
94    c3 = input.charCodeAt(i++);
95    e3 = (c2 & 15) << 2 | c3 >> 6;
96    e4 = c3 & 63;
97    if (isNaN(c2)) {
98      e3 = e4 = 64;
99    } else if (isNaN(c3)) {
100      e4 = 64;
101    }
102    o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4);
103  }
104  return o;
105}
106function Base64_encode_pass(input) {
107  var o = "";
108  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
109  for (var i = 0; i < input.length; ) {
110    c1 = input.charCodeAt(i++);
111    if (c1 > 255)
112      c1 = 95;
113    e1 = c1 >> 2;
114    c2 = input.charCodeAt(i++);
115    if (c2 > 255)
116      c2 = 95;
117    e2 = (c1 & 3) << 4 | c2 >> 4;
118    c3 = input.charCodeAt(i++);
119    if (c3 > 255)
120      c3 = 95;
121    e3 = (c2 & 15) << 2 | c3 >> 6;
122    e4 = c3 & 63;
123    if (isNaN(c2)) {
124      e3 = e4 = 64;
125    } else if (isNaN(c3)) {
126      e4 = 64;
127    }
128    o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4);
129  }
130  return o;
131}
132function Base64_decode(input) {
133  var o = "";
134  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
135  input = input.replace(/^data:([^\/]+\/[^\/]+)?;base64\,/, "").replace(/[^\w\+\/\=]/g, "");
136  for (var i = 0; i < input.length; ) {
137    e1 = Base64_map.indexOf(input.charAt(i++));
138    e2 = Base64_map.indexOf(input.charAt(i++));
139    c1 = e1 << 2 | e2 >> 4;
140    o += String.fromCharCode(c1);
141    e3 = Base64_map.indexOf(input.charAt(i++));
142    c2 = (e2 & 15) << 4 | e3 >> 2;
143    if (e3 !== 64) {
144      o += String.fromCharCode(c2);
145    }
146    e4 = Base64_map.indexOf(input.charAt(i++));
147    c3 = (e3 & 3) << 6 | e4;
148    if (e4 !== 64) {
149      o += String.fromCharCode(c3);
150    }
151  }
152  return o;
153}
154var has_buf = /*#__PURE__*/(function() { return typeof Buffer !== 'undefined' && typeof process !== 'undefined' && typeof process.versions !== 'undefined' && !!process.versions.node; })();
155
156var Buffer_from = /*#__PURE__*/(function() {
157	if(typeof Buffer !== 'undefined') {
158		var nbfs = !Buffer.from;
159		if(!nbfs) try { Buffer.from("foo", "utf8"); } catch(e) { nbfs = true; }
160		return nbfs ? function(buf, enc) { return (enc) ? new Buffer(buf, enc) : new Buffer(buf); } : Buffer.from.bind(Buffer);
161	}
162	return function() {};
163})();
164var buf_utf16le = /*#__PURE__*/(function() {
165	if(typeof Buffer === 'undefined') return false;
166	var x = Buffer_from([65,0]);
167	if(!x) return false;
168	var o = x.toString("utf16le");
169	return o.length == 1;
170})();
171
172
173function new_raw_buf(len/*:number*/) {
174	/* jshint -W056 */
175	if(has_buf) return Buffer.alloc ? Buffer.alloc(len) : new Buffer(len);
176	return typeof Uint8Array != "undefined" ? new Uint8Array(len) : new Array(len);
177	/* jshint +W056 */
178}
179
180function new_unsafe_buf(len/*:number*/) {
181	/* jshint -W056 */
182	if(has_buf) return Buffer.allocUnsafe ? Buffer.allocUnsafe(len) : new Buffer(len);
183	return typeof Uint8Array != "undefined" ? new Uint8Array(len) : new Array(len);
184	/* jshint +W056 */
185}
186
187var s2a = function s2a(s/*:string*/)/*:any*/ {
188	if(has_buf) return Buffer_from(s, "binary");
189	return s.split("").map(function(x/*:string*/)/*:number*/{ return x.charCodeAt(0) & 0xff; });
190};
191
192function s2ab(s/*:string*/)/*:any*/ {
193	if(typeof ArrayBuffer === 'undefined') return s2a(s);
194	var buf = new ArrayBuffer(s.length), view = new Uint8Array(buf);
195	for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
196	return buf;
197}
198
199function a2s(data/*:any*/)/*:string*/ {
200	if(Array.isArray(data)) return data.map(function(c) { return String.fromCharCode(c); }).join("");
201	var o/*:Array<string>*/ = []; for(var i = 0; i < data.length; ++i) o[i] = String.fromCharCode(data[i]); return o.join("");
202}
203
204function a2u(data/*:Array<number>*/)/*:Uint8Array*/ {
205	if(typeof Uint8Array === 'undefined') throw new Error("Unsupported");
206	return new Uint8Array(data);
207}
208
209function ab2a(data/*:ArrayBuffer|Uint8Array*/)/*:Array<number>*/ {
210	if(typeof ArrayBuffer == 'undefined') throw new Error("Unsupported");
211	if(data instanceof ArrayBuffer) return ab2a(new Uint8Array(data));
212	/*:: if(data instanceof ArrayBuffer) throw new Error("unreachable"); */
213	var o = new Array(data.length);
214	for(var i = 0; i < data.length; ++i) o[i] = data[i];
215	return o;
216}
217
218var bconcat = has_buf ? function(bufs) { return Buffer.concat(bufs.map(function(buf) { return Buffer.isBuffer(buf) ? buf : Buffer_from(buf); })); } : function(bufs) {
219	if(typeof Uint8Array !== "undefined") {
220		var i = 0, maxlen = 0;
221		for(i = 0; i < bufs.length; ++i) maxlen += bufs[i].length;
222		var o = new Uint8Array(maxlen);
223		var len = 0;
224		for(i = 0, maxlen = 0; i < bufs.length; maxlen += len, ++i) {
225			len = bufs[i].length;
226			if(bufs[i] instanceof Uint8Array) o.set(bufs[i], maxlen);
227			else if(typeof bufs[i] == "string") o.set(new Uint8Array(s2a(bufs[i])), maxlen);
228			else o.set(new Uint8Array(bufs[i]), maxlen);
229		}
230		return o;
231	}
232	return [].concat.apply([], bufs.map(function(buf) { return Array.isArray(buf) ? buf : [].slice.call(buf); }));
233};
234
235function utf8decode(content/*:string*/) {
236	var out = [], widx = 0, L = content.length + 250;
237	var o = new_raw_buf(content.length + 255);
238	for(var ridx = 0; ridx < content.length; ++ridx) {
239		var c = content.charCodeAt(ridx);
240		if(c < 0x80) o[widx++] = c;
241		else if(c < 0x800) {
242			o[widx++] = (192|((c>>6)&31));
243			o[widx++] = (128|(c&63));
244		} else if(c >= 0xD800 && c < 0xE000) {
245			c = (c&1023)+64;
246			var d = content.charCodeAt(++ridx)&1023;
247			o[widx++] = (240|((c>>8)&7));
248			o[widx++] = (128|((c>>2)&63));
249			o[widx++] = (128|((d>>6)&15)|((c&3)<<4));
250			o[widx++] = (128|(d&63));
251		} else {
252			o[widx++] = (224|((c>>12)&15));
253			o[widx++] = (128|((c>>6)&63));
254			o[widx++] = (128|(c&63));
255		}
256		if(widx > L) {
257			out.push(o.slice(0, widx));
258			widx = 0;
259			o = new_raw_buf(65535);
260			L = 65530;
261		}
262	}
263	out.push(o.slice(0, widx));
264	return bconcat(out);
265}
266
267var chr0 = /\u0000/g, chr1 = /[\u0001-\u0006]/g;
268/*::
269declare type Block = any;
270declare type BufArray = {
271	newblk(sz:number):Block;
272	next(sz:number):Block;
273	end():any;
274	push(buf:Block):void;
275};
276
277type RecordHopperCB = {(d:any, Rn:string, RT:number):?boolean;};
278
279type EvertType = {[string]:string};
280type EvertNumType = {[string]:number};
281type EvertArrType = {[string]:Array<string>};
282
283type StringConv = {(string):string};
284
285*/
286/* ssf.js (C) 2013-present SheetJS -- http://sheetjs.com */
287/*jshint -W041 */
288function _strrev(x/*:string*/)/*:string*/ { var o = "", i = x.length-1; while(i>=0) o += x.charAt(i--); return o; }
289function pad0(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>=d?t:fill('0',d-t.length)+t;}
290function pad_(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v;return t.length>=d?t:fill(' ',d-t.length)+t;}
291function rpad_(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>=d?t:t+fill(' ',d-t.length);}
292function pad0r1(v/*:any*/,d/*:number*/)/*:string*/{var t=""+Math.round(v); return t.length>=d?t:fill('0',d-t.length)+t;}
293function pad0r2(v/*:any*/,d/*:number*/)/*:string*/{var t=""+v; return t.length>=d?t:fill('0',d-t.length)+t;}
294var p2_32 = /*#__PURE__*/Math.pow(2,32);
295function pad0r(v/*:any*/,d/*:number*/)/*:string*/{if(v>p2_32||v<-p2_32) return pad0r1(v,d); var i = Math.round(v); return pad0r2(i,d); }
296/* yes, in 2022 this is still faster than string compare */
297function SSF_isgeneral(s/*:string*/, i/*:?number*/)/*:boolean*/ { i = i || 0; return s.length >= 7 + i && (s.charCodeAt(i)|32) === 103 && (s.charCodeAt(i+1)|32) === 101 && (s.charCodeAt(i+2)|32) === 110 && (s.charCodeAt(i+3)|32) === 101 && (s.charCodeAt(i+4)|32) === 114 && (s.charCodeAt(i+5)|32) === 97 && (s.charCodeAt(i+6)|32) === 108; }
298var days/*:Array<Array<string> >*/ = [
299	['Sun', 'Sunday'],
300	['Mon', 'Monday'],
301	['Tue', 'Tuesday'],
302	['Wed', 'Wednesday'],
303	['Thu', 'Thursday'],
304	['Fri', 'Friday'],
305	['Sat', 'Saturday']
306];
307var months/*:Array<Array<string> >*/ = [
308	['J', 'Jan', 'January'],
309	['F', 'Feb', 'February'],
310	['M', 'Mar', 'March'],
311	['A', 'Apr', 'April'],
312	['M', 'May', 'May'],
313	['J', 'Jun', 'June'],
314	['J', 'Jul', 'July'],
315	['A', 'Aug', 'August'],
316	['S', 'Sep', 'September'],
317	['O', 'Oct', 'October'],
318	['N', 'Nov', 'November'],
319	['D', 'Dec', 'December']
320];
321function SSF_init_table(t/*:any*/) {
322	if(!t) t = {};
323	t[0]=  'General';
324	t[1]=  '0';
325	t[2]=  '0.00';
326	t[3]=  '#,##0';
327	t[4]=  '#,##0.00';
328	t[9]=  '0%';
329	t[10]= '0.00%';
330	t[11]= '0.00E+00';
331	t[12]= '# ?/?';
332	t[13]= '# ??/??';
333	t[14]= 'm/d/yy';
334	t[15]= 'd-mmm-yy';
335	t[16]= 'd-mmm';
336	t[17]= 'mmm-yy';
337	t[18]= 'h:mm AM/PM';
338	t[19]= 'h:mm:ss AM/PM';
339	t[20]= 'h:mm';
340	t[21]= 'h:mm:ss';
341	t[22]= 'm/d/yy h:mm';
342	t[37]= '#,##0 ;(#,##0)';
343	t[38]= '#,##0 ;[Red](#,##0)';
344	t[39]= '#,##0.00;(#,##0.00)';
345	t[40]= '#,##0.00;[Red](#,##0.00)';
346	t[45]= 'mm:ss';
347	t[46]= '[h]:mm:ss';
348	t[47]= 'mmss.0';
349	t[48]= '##0.0E+0';
350	t[49]= '@';
351	t[56]= '"上午/下午 "hh"時"mm"分"ss"秒 "';
352	return t;
353}
354/* repeated to satiate webpack */
355var table_fmt = {
356	0:  'General',
357	1:  '0',
358	2:  '0.00',
359	3:  '#,##0',
360	4:  '#,##0.00',
361	9:  '0%',
362	10: '0.00%',
363	11: '0.00E+00',
364	12: '# ?/?',
365	13: '# ??/??',
366	14: 'm/d/yy',
367	15: 'd-mmm-yy',
368	16: 'd-mmm',
369	17: 'mmm-yy',
370	18: 'h:mm AM/PM',
371	19: 'h:mm:ss AM/PM',
372	20: 'h:mm',
373	21: 'h:mm:ss',
374	22: 'm/d/yy h:mm',
375	37: '#,##0 ;(#,##0)',
376	38: '#,##0 ;[Red](#,##0)',
377	39: '#,##0.00;(#,##0.00)',
378	40: '#,##0.00;[Red](#,##0.00)',
379	45: 'mm:ss',
380	46: '[h]:mm:ss',
381	47: 'mmss.0',
382	48: '##0.0E+0',
383	49: '@',
384	56: '"上午/下午 "hh"時"mm"分"ss"秒 "'
385};
386
387/* Defaults determined by systematically testing in Excel 2019 */
388
389/* These formats appear to default to other formats in the table */
390var SSF_default_map = {
391	5:  37, 6:  38, 7:  39, 8:  40,         //  5 -> 37 ...  8 -> 40
392
393	23:  0, 24:  0, 25:  0, 26:  0,         // 23 ->  0 ... 26 ->  0
394
395	27: 14, 28: 14, 29: 14, 30: 14, 31: 14, // 27 -> 14 ... 31 -> 14
396
397	50: 14, 51: 14, 52: 14, 53: 14, 54: 14, // 50 -> 14 ... 58 -> 14
398	55: 14, 56: 14, 57: 14, 58: 14,
399	59:  1, 60:  2, 61:  3, 62:  4,         // 59 ->  1 ... 62 ->  4
400
401	67:  9, 68: 10,                         // 67 ->  9 ... 68 -> 10
402	69: 12, 70: 13, 71: 14,                 // 69 -> 12 ... 71 -> 14
403	72: 14, 73: 15, 74: 16, 75: 17,         // 72 -> 14 ... 75 -> 17
404	76: 20, 77: 21, 78: 22,                 // 76 -> 20 ... 78 -> 22
405	79: 45, 80: 46, 81: 47,                 // 79 -> 45 ... 81 -> 47
406	82: 0                                   // 82 ->  0 ... 65536 -> 0 (omitted)
407};
408
409
410/* These formats technically refer to Accounting formats with no equivalent */
411var SSF_default_str = {
412	//  5 -- Currency,   0 decimal, black negative
413	5:  '"$"#,##0_);\\("$"#,##0\\)',
414	63: '"$"#,##0_);\\("$"#,##0\\)',
415
416	//  6 -- Currency,   0 decimal, red   negative
417	6:  '"$"#,##0_);[Red]\\("$"#,##0\\)',
418	64: '"$"#,##0_);[Red]\\("$"#,##0\\)',
419
420	//  7 -- Currency,   2 decimal, black negative
421	7:  '"$"#,##0.00_);\\("$"#,##0.00\\)',
422	65: '"$"#,##0.00_);\\("$"#,##0.00\\)',
423
424	//  8 -- Currency,   2 decimal, red   negative
425	8:  '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)',
426	66: '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)',
427
428	// 41 -- Accounting, 0 decimal, No Symbol
429	41: '_(* #,##0_);_(* \\(#,##0\\);_(* "-"_);_(@_)',
430
431	// 42 -- Accounting, 0 decimal, $  Symbol
432	42: '_("$"* #,##0_);_("$"* \\(#,##0\\);_("$"* "-"_);_(@_)',
433
434	// 43 -- Accounting, 2 decimal, No Symbol
435	43: '_(* #,##0.00_);_(* \\(#,##0.00\\);_(* "-"??_);_(@_)',
436
437	// 44 -- Accounting, 2 decimal, $  Symbol
438	44: '_("$"* #,##0.00_);_("$"* \\(#,##0.00\\);_("$"* "-"??_);_(@_)'
439};
440
441function SSF_frac(x/*:number*/, D/*:number*/, mixed/*:?boolean*/)/*:Array<number>*/ {
442	var sgn = x < 0 ? -1 : 1;
443	var B = x * sgn;
444	var P_2 = 0, P_1 = 1, P = 0;
445	var Q_2 = 1, Q_1 = 0, Q = 0;
446	var A = Math.floor(B);
447	while(Q_1 < D) {
448		A = Math.floor(B);
449		P = A * P_1 + P_2;
450		Q = A * Q_1 + Q_2;
451		if((B - A) < 0.00000005) break;
452		B = 1 / (B - A);
453		P_2 = P_1; P_1 = P;
454		Q_2 = Q_1; Q_1 = Q;
455	}
456	if(Q > D) { if(Q_1 > D) { Q = Q_2; P = P_2; } else { Q = Q_1; P = P_1; } }
457	if(!mixed) return [0, sgn * P, Q];
458	var q = Math.floor(sgn * P/Q);
459	return [q, sgn*P - q*Q, Q];
460}
461function SSF_parse_date_code(v/*:number*/,opts/*:?any*/,b2/*:?boolean*/) {
462	if(v > 2958465 || v < 0) return null;
463	var date = (v|0), time = Math.floor(86400 * (v - date)), dow=0;
464	var dout=[];
465	var out={D:date, T:time, u:86400*(v-date)-time,y:0,m:0,d:0,H:0,M:0,S:0,q:0};
466	if(Math.abs(out.u) < 1e-6) out.u = 0;
467	if(opts && opts.date1904) date += 1462;
468	if(out.u > 0.9999) {
469		out.u = 0;
470		if(++time == 86400) { out.T = time = 0; ++date; ++out.D; }
471	}
472	if(date === 60) {dout = b2 ? [1317,10,29] : [1900,2,29]; dow=3;}
473	else if(date === 0) {dout = b2 ? [1317,8,29] : [1900,1,0]; dow=6;}
474	else {
475		if(date > 60) --date;
476		/* 1 = Jan 1 1900 in Gregorian */
477		var d = new Date(1900, 0, 1);
478		d.setDate(d.getDate() + date - 1);
479		dout = [d.getFullYear(), d.getMonth()+1,d.getDate()];
480		dow = d.getDay();
481		if(date < 60) dow = (dow + 6) % 7;
482		if(b2) dow = SSF_fix_hijri(d, dout);
483	}
484	out.y = dout[0]; out.m = dout[1]; out.d = dout[2];
485	out.S = time % 60; time = Math.floor(time / 60);
486	out.M = time % 60; time = Math.floor(time / 60);
487	out.H = time;
488	out.q = dow;
489	return out;
490}
491var SSFbasedate = /*#__PURE__*/new Date(1899, 11, 31, 0, 0, 0);
492var SSFdnthresh = /*#__PURE__*/SSFbasedate.getTime();
493var SSFbase1904 = /*#__PURE__*/new Date(1900, 2, 1, 0, 0, 0);
494function datenum_local(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
495	var epoch = /*#__PURE__*/v.getTime();
496	if(date1904) epoch -= 1461*24*60*60*1000;
497	else if(v >= SSFbase1904) epoch += 24*60*60*1000;
498	return (epoch - (SSFdnthresh + (/*#__PURE__*/v.getTimezoneOffset() - /*#__PURE__*/SSFbasedate.getTimezoneOffset()) * 60000)) / (24 * 60 * 60 * 1000);
499}
500/* ECMA-376 18.8.30 numFmt*/
501/* Note: `toPrecision` uses standard form when prec > E and E >= -6 */
502/* exponent >= -9 and <= 9 */
503function SSF_strip_decimal(o/*:string*/)/*:string*/ {
504	return (o.indexOf(".") == -1) ? o : o.replace(/(?:\.0*|(\.\d*[1-9])0+)$/, "$1");
505}
506
507/* General Exponential always shows 2 digits exp and trims the mantissa */
508function SSF_normalize_exp(o/*:string*/)/*:string*/ {
509	if(o.indexOf("E") == -1) return o;
510	return o.replace(/(?:\.0*|(\.\d*[1-9])0+)[Ee]/,"$1E").replace(/(E[+-])(\d)$/,"$10$2");
511}
512
513/* exponent >= -9 and <= 9 */
514function SSF_small_exp(v/*:number*/)/*:string*/ {
515	var w = (v<0?12:11);
516	var o = SSF_strip_decimal(v.toFixed(12)); if(o.length <= w) return o;
517	o = v.toPrecision(10); if(o.length <= w) return o;
518	return v.toExponential(5);
519}
520
521/* exponent >= 11 or <= -10 likely exponential */
522function SSF_large_exp(v/*:number*/)/*:string*/ {
523	var o = SSF_strip_decimal(v.toFixed(11));
524	return (o.length > (v<0?12:11) || o === "0" || o === "-0") ? v.toPrecision(6) : o;
525}
526
527function SSF_general_num(v/*:number*/)/*:string*/ {
528	var V = Math.floor(Math.log(Math.abs(v))*Math.LOG10E), o;
529
530	if(V >= -4 && V <= -1) o = v.toPrecision(10+V);
531	else if(Math.abs(V) <= 9) o = SSF_small_exp(v);
532	else if(V === 10) o = v.toFixed(10).substr(0,12);
533	else o = SSF_large_exp(v);
534
535	return SSF_strip_decimal(SSF_normalize_exp(o.toUpperCase()));
536}
537
538
539/*
540	"General" rules:
541	- text is passed through ("@")
542	- booleans are rendered as TRUE/FALSE
543	- "up to 11 characters" displayed for numbers
544	- Default date format (code 14) used for Dates
545
546	The longest 32-bit integer text is "-2147483648", exactly 11 chars
547	TODO: technically the display depends on the width of the cell
548*/
549function SSF_general(v/*:any*/, opts/*:any*/) {
550	switch(typeof v) {
551		case 'string': return v;
552		case 'boolean': return v ? "TRUE" : "FALSE";
553		case 'number': return (v|0) === v ? v.toString(10) : SSF_general_num(v);
554		case 'undefined': return "";
555		case 'object':
556			if(v == null) return "";
557			if(v instanceof Date) return SSF_format(14, datenum_local(v, opts && opts.date1904), opts);
558	}
559	throw new Error("unsupported value in General format: " + v);
560}
561
562function SSF_fix_hijri(date/*:Date*/, o/*:[number, number, number]*/) {
563  /* TODO: properly adjust y/m/d and  */
564  o[0] -= 581;
565  var dow = date.getDay();
566  if(date < 60) dow = (dow + 6) % 7;
567  return dow;
568}
569//var THAI_DIGITS = "\u0E50\u0E51\u0E52\u0E53\u0E54\u0E55\u0E56\u0E57\u0E58\u0E59".split("");
570function SSF_write_date(type/*:number*/, fmt/*:string*/, val, ss0/*:?number*/)/*:string*/ {
571	var o="", ss=0, tt=0, y = val.y, out, outl = 0;
572	switch(type) {
573		case 98: /* 'b' buddhist year */
574			y = val.y + 543;
575			/* falls through */
576		case 121: /* 'y' year */
577		switch(fmt.length) {
578			case 1: case 2: out = y % 100; outl = 2; break;
579			default: out = y % 10000; outl = 4; break;
580		} break;
581		case 109: /* 'm' month */
582		switch(fmt.length) {
583			case 1: case 2: out = val.m; outl = fmt.length; break;
584			case 3: return months[val.m-1][1];
585			case 5: return months[val.m-1][0];
586			default: return months[val.m-1][2];
587		} break;
588		case 100: /* 'd' day */
589		switch(fmt.length) {
590			case 1: case 2: out = val.d; outl = fmt.length; break;
591			case 3: return days[val.q][0];
592			default: return days[val.q][1];
593		} break;
594		case 104: /* 'h' 12-hour */
595		switch(fmt.length) {
596			case 1: case 2: out = 1+(val.H+11)%12; outl = fmt.length; break;
597			default: throw 'bad hour format: ' + fmt;
598		} break;
599		case 72: /* 'H' 24-hour */
600		switch(fmt.length) {
601			case 1: case 2: out = val.H; outl = fmt.length; break;
602			default: throw 'bad hour format: ' + fmt;
603		} break;
604		case 77: /* 'M' minutes */
605		switch(fmt.length) {
606			case 1: case 2: out = val.M; outl = fmt.length; break;
607			default: throw 'bad minute format: ' + fmt;
608		} break;
609		case 115: /* 's' seconds */
610			if(fmt != 's' && fmt != 'ss' && fmt != '.0' && fmt != '.00' && fmt != '.000') throw 'bad second format: ' + fmt;
611			if(val.u === 0 && (fmt == "s" || fmt == "ss")) return pad0(val.S, fmt.length);
612			/*::if(!ss0) ss0 = 0; */
613			if(ss0 >= 2) tt = ss0 === 3 ? 1000 : 100;
614			else tt = ss0 === 1 ? 10 : 1;
615			ss = Math.round((tt)*(val.S + val.u));
616			if(ss >= 60*tt) ss = 0;
617			if(fmt === 's') return ss === 0 ? "0" : ""+ss/tt;
618			o = pad0(ss,2 + ss0);
619			if(fmt === 'ss') return o.substr(0,2);
620			return "." + o.substr(2,fmt.length-1);
621		case 90: /* 'Z' absolute time */
622		switch(fmt) {
623			case '[h]': case '[hh]': out = val.D*24+val.H; break;
624			case '[m]': case '[mm]': out = (val.D*24+val.H)*60+val.M; break;
625			case '[s]': case '[ss]': out = ((val.D*24+val.H)*60+val.M)*60+Math.round(val.S+val.u); break;
626			default: throw 'bad abstime format: ' + fmt;
627		} outl = fmt.length === 3 ? 1 : 2; break;
628		case 101: /* 'e' era */
629			out = y; outl = 1; break;
630	}
631	var outstr = outl > 0 ? pad0(out, outl) : "";
632	return outstr;
633}
634
635
636/*jshint -W086 */
637/*jshint +W086 */
638function commaify(s/*:string*/)/*:string*/ {
639	var w = 3;
640	if(s.length <= w) return s;
641	var j = (s.length % w), o = s.substr(0,j);
642	for(; j!=s.length; j+=w) o+=(o.length > 0 ? "," : "") + s.substr(j,w);
643	return o;
644}
645var pct1 = /%/g;
646function write_num_pct(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/{
647	var sfmt = fmt.replace(pct1,""), mul = fmt.length - sfmt.length;
648	return write_num(type, sfmt, val * Math.pow(10,2*mul)) + fill("%",mul);
649}
650
651function write_num_cm(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/{
652	var idx = fmt.length - 1;
653	while(fmt.charCodeAt(idx-1) === 44) --idx;
654	return write_num(type, fmt.substr(0,idx), val / Math.pow(10,3*(fmt.length-idx)));
655}
656
657function write_num_exp(fmt/*:string*/, val/*:number*/)/*:string*/{
658	var o/*:string*/;
659	var idx = fmt.indexOf("E") - fmt.indexOf(".") - 1;
660	if(fmt.match(/^#+0.0E\+0$/)) {
661		if(val == 0) return "0.0E+0";
662		else if(val < 0) return "-" + write_num_exp(fmt, -val);
663		var period = fmt.indexOf("."); if(period === -1) period=fmt.indexOf('E');
664		var ee = Math.floor(Math.log(val)*Math.LOG10E)%period;
665		if(ee < 0) ee += period;
666		o = (val/Math.pow(10,ee)).toPrecision(idx+1+(period+ee)%period);
667		if(o.indexOf("e") === -1) {
668			var fakee = Math.floor(Math.log(val)*Math.LOG10E);
669			if(o.indexOf(".") === -1) o = o.charAt(0) + "." + o.substr(1) + "E+" + (fakee - o.length+ee);
670			else o += "E+" + (fakee - ee);
671			while(o.substr(0,2) === "0.") {
672				o = o.charAt(0) + o.substr(2,period) + "." + o.substr(2+period);
673				o = o.replace(/^0+([1-9])/,"$1").replace(/^0+\./,"0.");
674			}
675			o = o.replace(/\+-/,"-");
676		}
677		o = o.replace(/^([+-]?)(\d*)\.(\d*)[Ee]/,function($$,$1,$2,$3) { return $1 + $2 + $3.substr(0,(period+ee)%period) + "." + $3.substr(ee) + "E"; });
678	} else o = val.toExponential(idx);
679	if(fmt.match(/E\+00$/) && o.match(/e[+-]\d$/)) o = o.substr(0,o.length-1) + "0" + o.charAt(o.length-1);
680	if(fmt.match(/E\-/) && o.match(/e\+/)) o = o.replace(/e\+/,"e");
681	return o.replace("e","E");
682}
683var frac1 = /# (\?+)( ?)\/( ?)(\d+)/;
684function write_num_f1(r/*:Array<string>*/, aval/*:number*/, sign/*:string*/)/*:string*/ {
685	var den = parseInt(r[4],10), rr = Math.round(aval * den), base = Math.floor(rr/den);
686	var myn = (rr - base*den), myd = den;
687	return sign + (base === 0 ? "" : ""+base) + " " + (myn === 0 ? fill(" ", r[1].length + 1 + r[4].length) : pad_(myn,r[1].length) + r[2] + "/" + r[3] + pad0(myd,r[4].length));
688}
689function write_num_f2(r/*:Array<string>*/, aval/*:number*/, sign/*:string*/)/*:string*/ {
690	return sign + (aval === 0 ? "" : ""+aval) + fill(" ", r[1].length + 2 + r[4].length);
691}
692var dec1 = /^#*0*\.([0#]+)/;
693var closeparen = /\).*[0#]/;
694var phone = /\(###\) ###\\?-####/;
695function hashq(str/*:string*/)/*:string*/ {
696	var o = "", cc;
697	for(var i = 0; i != str.length; ++i) switch((cc=str.charCodeAt(i))) {
698		case 35: break;
699		case 63: o+= " "; break;
700		case 48: o+= "0"; break;
701		default: o+= String.fromCharCode(cc);
702	}
703	return o;
704}
705function rnd(val/*:number*/, d/*:number*/)/*:string*/ { var dd = Math.pow(10,d); return ""+(Math.round(val * dd)/dd); }
706function dec(val/*:number*/, d/*:number*/)/*:number*/ {
707	var _frac = val - Math.floor(val), dd = Math.pow(10,d);
708	if (d < ('' + Math.round(_frac * dd)).length) return 0;
709	return Math.round(_frac * dd);
710}
711function carry(val/*:number*/, d/*:number*/)/*:number*/ {
712	if (d < ('' + Math.round((val-Math.floor(val))*Math.pow(10,d))).length) {
713		return 1;
714	}
715	return 0;
716}
717function flr(val/*:number*/)/*:string*/ {
718	if(val < 2147483647 && val > -2147483648) return ""+(val >= 0 ? (val|0) : (val-1|0));
719	return ""+Math.floor(val);
720}
721function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/ {
722	if(type.charCodeAt(0) === 40 && !fmt.match(closeparen)) {
723		var ffmt = fmt.replace(/\( */,"").replace(/ \)/,"").replace(/\)/,"");
724		if(val >= 0) return write_num_flt('n', ffmt, val);
725		return '(' + write_num_flt('n', ffmt, -val) + ')';
726	}
727	if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm(type, fmt, val);
728	if(fmt.indexOf('%') !== -1) return write_num_pct(type, fmt, val);
729	if(fmt.indexOf('E') !== -1) return write_num_exp(fmt, val);
730	if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt.charAt(1)==' '?2:1),val);
731	var o;
732	var r/*:?Array<string>*/, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : "";
733	if(fmt.match(/^00+$/)) return sign + pad0r(aval,fmt.length);
734	if(fmt.match(/^[#?]+$/)) {
735		o = pad0r(val,0); if(o === "0") o = "";
736		return o.length > fmt.length ? o : hashq(fmt.substr(0,fmt.length-o.length)) + o;
737	}
738	if((r = fmt.match(frac1))) return write_num_f1(r, aval, sign);
739	if(fmt.match(/^#+0+$/)) return sign + pad0r(aval,fmt.length - fmt.indexOf("0"));
740	if((r = fmt.match(dec1))) {
741		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); });
742		return fmt.indexOf("0.") !== -1 ? o : o.replace(/^0\./,".");
743	}
744	fmt = fmt.replace(/^#+([0.])/, "$1");
745	if((r = fmt.match(/^(0*)\.(#*)$/))) {
746		return sign + rnd(aval, r[2].length).replace(/\.(\d*[1-9])0*$/,".$1").replace(/^(-?\d*)$/,"$1.").replace(/^0\./,r[1].length?"0.":".");
747	}
748	if((r = fmt.match(/^#{1,3},##0(\.?)$/))) return sign + commaify(pad0r(aval,0));
749	if((r = fmt.match(/^#,##0\.([#0]*0)$/))) {
750		return val < 0 ? "-" + write_num_flt(type, fmt, -val) : commaify(""+(Math.floor(val) + carry(val, r[1].length))) + "." + pad0(dec(val, r[1].length),r[1].length);
751	}
752	if((r = fmt.match(/^#,#*,#0/))) return write_num_flt(type,fmt.replace(/^#,#*,/,""),val);
753	if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
754		o = _strrev(write_num_flt(type, fmt.replace(/[\\-]/g,""), val));
755		ri = 0;
756		return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o.charAt(ri++):x==='0'?'0':"";}));
757	}
758	if(fmt.match(phone)) {
759		o = write_num_flt(type, "##########", val);
760		return "(" + o.substr(0,3) + ") " + o.substr(3, 3) + "-" + o.substr(6);
761	}
762	var oa = "";
763	if((r = fmt.match(/^([#0?]+)( ?)\/( ?)([#0?]+)/))) {
764		ri = Math.min(/*::String(*/r[4]/*::)*/.length,7);
765		ff = SSF_frac(aval, Math.pow(10,ri)-1, false);
766		o = "" + sign;
767		oa = write_num("n", /*::String(*/r[1]/*::)*/, ff[1]);
768		if(oa.charAt(oa.length-1) == " ") oa = oa.substr(0,oa.length-1) + "0";
769		o += oa + /*::String(*/r[2]/*::)*/ + "/" + /*::String(*/r[3]/*::)*/;
770		oa = rpad_(ff[2],ri);
771		if(oa.length < r[4].length) oa = hashq(r[4].substr(r[4].length-oa.length)) + oa;
772		o += oa;
773		return o;
774	}
775	if((r = fmt.match(/^# ([#0?]+)( ?)\/( ?)([#0?]+)/))) {
776		ri = Math.min(Math.max(r[1].length, r[4].length),7);
777		ff = SSF_frac(aval, Math.pow(10,ri)-1, true);
778		return sign + (ff[0]||(ff[1] ? "" : "0")) + " " + (ff[1] ? pad_(ff[1],ri) + r[2] + "/" + r[3] + rpad_(ff[2],ri): fill(" ", 2*ri+1 + r[2].length + r[3].length));
779	}
780	if((r = fmt.match(/^[#0?]+$/))) {
781		o = pad0r(val, 0);
782		if(fmt.length <= o.length) return o;
783		return hashq(fmt.substr(0,fmt.length-o.length)) + o;
784	}
785	if((r = fmt.match(/^([#0?]+)\.([#0]+)$/))) {
786		o = "" + val.toFixed(Math.min(r[2].length,10)).replace(/([^0])0+$/,"$1");
787		ri = o.indexOf(".");
788		var lres = fmt.indexOf(".") - ri, rres = fmt.length - o.length - lres;
789		return hashq(fmt.substr(0,lres) + o + fmt.substr(fmt.length-rres));
790	}
791	if((r = fmt.match(/^00,000\.([#0]*0)$/))) {
792		ri = dec(val, r[1].length);
793		return val < 0 ? "-" + write_num_flt(type, fmt, -val) : commaify(flr(val)).replace(/^\d,\d{3}$/,"0$&").replace(/^\d*$/,function($$) { return "00," + ($$.length < 3 ? pad0(0,3-$$.length) : "") + $$; }) + "." + pad0(ri,r[1].length);
794	}
795	switch(fmt) {
796		case "###,##0.00": return write_num_flt(type, "#,##0.00", val);
797		case "###,###":
798		case "##,###":
799		case "#,###": var x = commaify(pad0r(aval,0)); return x !== "0" ? sign + x : "";
800		case "###,###.00": return write_num_flt(type, "###,##0.00",val).replace(/^0\./,".");
801		case "#,###.00": return write_num_flt(type, "#,##0.00",val).replace(/^0\./,".");
802		default:
803	}
804	throw new Error("unsupported format |" + fmt + "|");
805}
806function write_num_cm2(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/{
807	var idx = fmt.length - 1;
808	while(fmt.charCodeAt(idx-1) === 44) --idx;
809	return write_num(type, fmt.substr(0,idx), val / Math.pow(10,3*(fmt.length-idx)));
810}
811function write_num_pct2(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/{
812	var sfmt = fmt.replace(pct1,""), mul = fmt.length - sfmt.length;
813	return write_num(type, sfmt, val * Math.pow(10,2*mul)) + fill("%",mul);
814}
815function write_num_exp2(fmt/*:string*/, val/*:number*/)/*:string*/{
816	var o/*:string*/;
817	var idx = fmt.indexOf("E") - fmt.indexOf(".") - 1;
818	if(fmt.match(/^#+0.0E\+0$/)) {
819		if(val == 0) return "0.0E+0";
820		else if(val < 0) return "-" + write_num_exp2(fmt, -val);
821		var period = fmt.indexOf("."); if(period === -1) period=fmt.indexOf('E');
822		var ee = Math.floor(Math.log(val)*Math.LOG10E)%period;
823		if(ee < 0) ee += period;
824		o = (val/Math.pow(10,ee)).toPrecision(idx+1+(period+ee)%period);
825		if(!o.match(/[Ee]/)) {
826			var fakee = Math.floor(Math.log(val)*Math.LOG10E);
827			if(o.indexOf(".") === -1) o = o.charAt(0) + "." + o.substr(1) + "E+" + (fakee - o.length+ee);
828			else o += "E+" + (fakee - ee);
829			o = o.replace(/\+-/,"-");
830		}
831		o = o.replace(/^([+-]?)(\d*)\.(\d*)[Ee]/,function($$,$1,$2,$3) { return $1 + $2 + $3.substr(0,(period+ee)%period) + "." + $3.substr(ee) + "E"; });
832	} else o = val.toExponential(idx);
833	if(fmt.match(/E\+00$/) && o.match(/e[+-]\d$/)) o = o.substr(0,o.length-1) + "0" + o.charAt(o.length-1);
834	if(fmt.match(/E\-/) && o.match(/e\+/)) o = o.replace(/e\+/,"e");
835	return o.replace("e","E");
836}
837function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/ {
838	if(type.charCodeAt(0) === 40 && !fmt.match(closeparen)) {
839		var ffmt = fmt.replace(/\( */,"").replace(/ \)/,"").replace(/\)/,"");
840		if(val >= 0) return write_num_int('n', ffmt, val);
841		return '(' + write_num_int('n', ffmt, -val) + ')';
842	}
843	if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm2(type, fmt, val);
844	if(fmt.indexOf('%') !== -1) return write_num_pct2(type, fmt, val);
845	if(fmt.indexOf('E') !== -1) return write_num_exp2(fmt, val);
846	if(fmt.charCodeAt(0) === 36) return "$"+write_num_int(type,fmt.substr(fmt.charAt(1)==' '?2:1),val);
847	var o;
848	var r/*:?Array<string>*/, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : "";
849	if(fmt.match(/^00+$/)) return sign + pad0(aval,fmt.length);
850	if(fmt.match(/^[#?]+$/)) {
851		o = (""+val); if(val === 0) o = "";
852		return o.length > fmt.length ? o : hashq(fmt.substr(0,fmt.length-o.length)) + o;
853	}
854	if((r = fmt.match(frac1))) return write_num_f2(r, aval, sign);
855	if(fmt.match(/^#+0+$/)) return sign + pad0(aval,fmt.length - fmt.indexOf("0"));
856	if((r = fmt.match(dec1))) {
857		/*:: if(!Array.isArray(r)) throw new Error("unreachable"); */
858		o = (""+val).replace(/^([^\.]+)$/,"$1."+hashq(r[1])).replace(/\.$/,"."+hashq(r[1]));
859		o = o.replace(/\.(\d*)$/,function($$, $1) {
860		/*:: if(!Array.isArray(r)) throw new Error("unreachable"); */
861			return "." + $1 + fill("0", hashq(r[1]).length-$1.length); });
862		return fmt.indexOf("0.") !== -1 ? o : o.replace(/^0\./,".");
863	}
864	fmt = fmt.replace(/^#+([0.])/, "$1");
865	if((r = fmt.match(/^(0*)\.(#*)$/))) {
866		return sign + (""+aval).replace(/\.(\d*[1-9])0*$/,".$1").replace(/^(-?\d*)$/,"$1.").replace(/^0\./,r[1].length?"0.":".");
867	}
868	if((r = fmt.match(/^#{1,3},##0(\.?)$/))) return sign + commaify((""+aval));
869	if((r = fmt.match(/^#,##0\.([#0]*0)$/))) {
870		return val < 0 ? "-" + write_num_int(type, fmt, -val) : commaify((""+val)) + "." + fill('0',r[1].length);
871	}
872	if((r = fmt.match(/^#,#*,#0/))) return write_num_int(type,fmt.replace(/^#,#*,/,""),val);
873	if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
874		o = _strrev(write_num_int(type, fmt.replace(/[\\-]/g,""), val));
875		ri = 0;
876		return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o.charAt(ri++):x==='0'?'0':"";}));
877	}
878	if(fmt.match(phone)) {
879		o = write_num_int(type, "##########", val);
880		return "(" + o.substr(0,3) + ") " + o.substr(3, 3) + "-" + o.substr(6);
881	}
882	var oa = "";
883	if((r = fmt.match(/^([#0?]+)( ?)\/( ?)([#0?]+)/))) {
884		ri = Math.min(/*::String(*/r[4]/*::)*/.length,7);
885		ff = SSF_frac(aval, Math.pow(10,ri)-1, false);
886		o = "" + sign;
887		oa = write_num("n", /*::String(*/r[1]/*::)*/, ff[1]);
888		if(oa.charAt(oa.length-1) == " ") oa = oa.substr(0,oa.length-1) + "0";
889		o += oa + /*::String(*/r[2]/*::)*/ + "/" + /*::String(*/r[3]/*::)*/;
890		oa = rpad_(ff[2],ri);
891		if(oa.length < r[4].length) oa = hashq(r[4].substr(r[4].length-oa.length)) + oa;
892		o += oa;
893		return o;
894	}
895	if((r = fmt.match(/^# ([#0?]+)( ?)\/( ?)([#0?]+)/))) {
896		ri = Math.min(Math.max(r[1].length, r[4].length),7);
897		ff = SSF_frac(aval, Math.pow(10,ri)-1, true);
898		return sign + (ff[0]||(ff[1] ? "" : "0")) + " " + (ff[1] ? pad_(ff[1],ri) + r[2] + "/" + r[3] + rpad_(ff[2],ri): fill(" ", 2*ri+1 + r[2].length + r[3].length));
899	}
900	if((r = fmt.match(/^[#0?]+$/))) {
901		o = "" + val;
902		if(fmt.length <= o.length) return o;
903		return hashq(fmt.substr(0,fmt.length-o.length)) + o;
904	}
905	if((r = fmt.match(/^([#0]+)\.([#0]+)$/))) {
906		o = "" + val.toFixed(Math.min(r[2].length,10)).replace(/([^0])0+$/,"$1");
907		ri = o.indexOf(".");
908		var lres = fmt.indexOf(".") - ri, rres = fmt.length - o.length - lres;
909		return hashq(fmt.substr(0,lres) + o + fmt.substr(fmt.length-rres));
910	}
911	if((r = fmt.match(/^00,000\.([#0]*0)$/))) {
912		return val < 0 ? "-" + write_num_int(type, fmt, -val) : commaify(""+val).replace(/^\d,\d{3}$/,"0$&").replace(/^\d*$/,function($$) { return "00," + ($$.length < 3 ? pad0(0,3-$$.length) : "") + $$; }) + "." + pad0(0,r[1].length);
913	}
914	switch(fmt) {
915		case "###,###":
916		case "##,###":
917		case "#,###": var x = commaify(""+aval); return x !== "0" ? sign + x : "";
918		default:
919			if(fmt.match(/\.[0#?]*$/)) return write_num_int(type, fmt.slice(0,fmt.lastIndexOf(".")), val) + hashq(fmt.slice(fmt.lastIndexOf(".")));
920	}
921	throw new Error("unsupported format |" + fmt + "|");
922}
923function write_num(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string*/ {
924	return (val|0) === val ? write_num_int(type, fmt, val) : write_num_flt(type, fmt, val);
925}
926function SSF_split_fmt(fmt/*:string*/)/*:Array<string>*/ {
927	var out/*:Array<string>*/ = [];
928	var in_str = false/*, cc*/;
929	for(var i = 0, j = 0; i < fmt.length; ++i) switch((/*cc=*/fmt.charCodeAt(i))) {
930		case 34: /* '"' */
931			in_str = !in_str; break;
932		case 95: case 42: case 92: /* '_' '*' '\\' */
933			++i; break;
934		case 59: /* ';' */
935			out[out.length] = fmt.substr(j,i-j);
936			j = i+1;
937	}
938	out[out.length] = fmt.substr(j);
939	if(in_str === true) throw new Error("Format |" + fmt + "| unterminated string ");
940	return out;
941}
942
943var SSF_abstime = /\[[HhMmSs\u0E0A\u0E19\u0E17]*\]/;
944function fmt_is_date(fmt/*:string*/)/*:boolean*/ {
945	var i = 0, /*cc = 0,*/ c = "", o = "";
946	while(i < fmt.length) {
947		switch((c = fmt.charAt(i))) {
948			case 'G': if(SSF_isgeneral(fmt, i)) i+= 6; i++; break;
949			case '"': for(;(/*cc=*/fmt.charCodeAt(++i)) !== 34 && i < fmt.length;){/*empty*/} ++i; break;
950			case '\\': i+=2; break;
951			case '_': i+=2; break;
952			case '@': ++i; break;
953			case 'B': case 'b':
954				if(fmt.charAt(i+1) === "1" || fmt.charAt(i+1) === "2") return true;
955				/* falls through */
956			case 'M': case 'D': case 'Y': case 'H': case 'S': case 'E':
957				/* falls through */
958			case 'm': case 'd': case 'y': case 'h': case 's': case 'e': case 'g': return true;
959			case 'A': case 'a': case '上':
960				if(fmt.substr(i, 3).toUpperCase() === "A/P") return true;
961				if(fmt.substr(i, 5).toUpperCase() === "AM/PM") return true;
962				if(fmt.substr(i, 5).toUpperCase() === "上午/下午") return true;
963				++i; break;
964			case '[':
965				o = c;
966				while(fmt.charAt(i++) !== ']' && i < fmt.length) o += fmt.charAt(i);
967				if(o.match(SSF_abstime)) return true;
968				break;
969			case '.':
970				/* falls through */
971			case '0': case '#':
972				while(i < fmt.length && ("0#?.,E+-%".indexOf(c=fmt.charAt(++i)) > -1 || (c=='\\' && fmt.charAt(i+1) == "-" && "0#".indexOf(fmt.charAt(i+2))>-1))){/* empty */}
973				break;
974			case '?': while(fmt.charAt(++i) === c){/* empty */} break;
975			case '*': ++i; if(fmt.charAt(i) == ' ' || fmt.charAt(i) == '*') ++i; break;
976			case '(': case ')': ++i; break;
977			case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
978				while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1){/* empty */} break;
979			case ' ': ++i; break;
980			default: ++i; break;
981		}
982	}
983	return false;
984}
985
986function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
987	var out = [], o = "", i = 0, c = "", lst='t', dt, j, cc;
988	var hr='H';
989	/* Tokenize */
990	while(i < fmt.length) {
991		switch((c = fmt.charAt(i))) {
992			case 'G': /* General */
993				if(!SSF_isgeneral(fmt, i)) throw new Error('unrecognized character ' + c + ' in ' +fmt);
994				out[out.length] = {t:'G', v:'General'}; i+=7; break;
995			case '"': /* Literal text */
996				for(o="";(cc=fmt.charCodeAt(++i)) !== 34 && i < fmt.length;) o += String.fromCharCode(cc);
997				out[out.length] = {t:'t', v:o}; ++i; break;
998			case '\\': var w = fmt.charAt(++i), t = (w === "(" || w === ")") ? w : 't';
999				out[out.length] = {t:t, v:w}; ++i; break;
1000			case '_': out[out.length] = {t:'t', v:" "}; i+=2; break;
1001			case '@': /* Text Placeholder */
1002				out[out.length] = {t:'T', v:v}; ++i; break;
1003			case 'B': case 'b':
1004				if(fmt.charAt(i+1) === "1" || fmt.charAt(i+1) === "2") {
1005					if(dt==null) { dt=SSF_parse_date_code(v, opts, fmt.charAt(i+1) === "2"); if(dt==null) return ""; }
1006					out[out.length] = {t:'X', v:fmt.substr(i,2)}; lst = c; i+=2; break;
1007				}
1008				/* falls through */
1009			case 'M': case 'D': case 'Y': case 'H': case 'S': case 'E':
1010				c = c.toLowerCase();
1011				/* falls through */
1012			case 'm': case 'd': case 'y': case 'h': case 's': case 'e': case 'g':
1013				if(v < 0) return "";
1014				if(dt==null) { dt=SSF_parse_date_code(v, opts); if(dt==null) return ""; }
1015				o = c; while(++i < fmt.length && fmt.charAt(i).toLowerCase() === c) o+=c;
1016				if(c === 'm' && lst.toLowerCase() === 'h') c = 'M';
1017				if(c === 'h') c = hr;
1018				out[out.length] = {t:c, v:o}; lst = c; break;
1019			case 'A': case 'a': case '上':
1020				var q={t:c, v:c};
1021				if(dt==null) dt=SSF_parse_date_code(v, opts);
1022				if(fmt.substr(i, 3).toUpperCase() === "A/P") { if(dt!=null) q.v = dt.H >= 12 ? fmt.charAt(i+2) : c; q.t = 'T'; hr='h';i+=3;}
1023				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'; }
1024				else if(fmt.substr(i,5).toUpperCase() === "上午/下午") { if(dt!=null) q.v = dt.H >= 12 ? "下午" : "上午"; q.t = 'T'; i+=5; hr='h'; }
1025				else { q.t = "t"; ++i; }
1026				if(dt==null && q.t === 'T') return "";
1027				out[out.length] = q; lst = c; break;
1028			case '[':
1029				o = c;
1030				while(fmt.charAt(i++) !== ']' && i < fmt.length) o += fmt.charAt(i);
1031				if(o.slice(-1) !== ']') throw 'unterminated "[" block: |' + o + '|';
1032				if(o.match(SSF_abstime)) {
1033					if(dt==null) { dt=SSF_parse_date_code(v, opts); if(dt==null) return ""; }
1034					out[out.length] = {t:'Z', v:o.toLowerCase()};
1035					lst = o.charAt(1);
1036				} else if(o.indexOf("$") > -1) {
1037					o = (o.match(/\$([^-\[\]]*)/)||[])[1]||"$";
1038					if(!fmt_is_date(fmt)) out[out.length] = {t:'t',v:o};
1039				}
1040				break;
1041			/* Numbers */
1042			case '.':
1043				if(dt != null) {
1044					o = c; while(++i < fmt.length && (c=fmt.charAt(i)) === "0") o += c;
1045					out[out.length] = {t:'s', v:o}; break;
1046				}
1047				/* falls through */
1048			case '0': case '#':
1049				o = c; while(++i < fmt.length && "0#?.,E+-%".indexOf(c=fmt.charAt(i)) > -1) o += c;
1050				out[out.length] = {t:'n', v:o}; break;
1051			case '?':
1052				o = c; while(fmt.charAt(++i) === c) o+=c;
1053				out[out.length] = {t:c, v:o}; lst = c; break;
1054			case '*': ++i; if(fmt.charAt(i) == ' ' || fmt.charAt(i) == '*') ++i; break; // **
1055			case '(': case ')': out[out.length] = {t:(flen===1?'t':c), v:c}; ++i; break;
1056			case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
1057				o = c; while(i < fmt.length && "0123456789".indexOf(fmt.charAt(++i)) > -1) o+=fmt.charAt(i);
1058				out[out.length] = {t:'D', v:o}; break;
1059			case ' ': out[out.length] = {t:c, v:c}; ++i; break;
1060			case '$': out[out.length] = {t:'t', v:'$'}; ++i; break;
1061			default:
1062				if(",$-+/():!^&'~{}<>=€acfijklopqrtuvwxzP".indexOf(c) === -1) throw new Error('unrecognized character ' + c + ' in ' + fmt);
1063				out[out.length] = {t:'t', v:c}; ++i; break;
1064		}
1065	}
1066
1067	/* Scan for date/time parts */
1068	var bt = 0, ss0 = 0, ssm;
1069	for(i=out.length-1, lst='t'; i >= 0; --i) {
1070		switch(out[i].t) {
1071			case 'h': case 'H': out[i].t = hr; lst='h'; if(bt < 1) bt = 1; break;
1072			case 's':
1073				if((ssm=out[i].v.match(/\.0+$/))) ss0=Math.max(ss0,ssm[0].length-1);
1074				if(bt < 3) bt = 3;
1075			/* falls through */
1076			case 'd': case 'y': case 'M': case 'e': lst=out[i].t; break;
1077			case 'm': if(lst === 's') { out[i].t = 'M'; if(bt < 2) bt = 2; } break;
1078			case 'X': /*if(out[i].v === "B2");*/
1079				break;
1080			case 'Z':
1081				if(bt < 1 && out[i].v.match(/[Hh]/)) bt = 1;
1082				if(bt < 2 && out[i].v.match(/[Mm]/)) bt = 2;
1083				if(bt < 3 && out[i].v.match(/[Ss]/)) bt = 3;
1084		}
1085	}
1086	/* time rounding depends on presence of minute / second / usec fields */
1087	switch(bt) {
1088		case 0: break;
1089		case 1:
1090			/*::if(!dt) break;*/
1091			if(dt.u >= 0.5) { dt.u = 0; ++dt.S; }
1092			if(dt.S >=  60) { dt.S = 0; ++dt.M; }
1093			if(dt.M >=  60) { dt.M = 0; ++dt.H; }
1094			break;
1095		case 2:
1096			/*::if(!dt) break;*/
1097			if(dt.u >= 0.5) { dt.u = 0; ++dt.S; }
1098			if(dt.S >=  60) { dt.S = 0; ++dt.M; }
1099			break;
1100	}
1101
1102	/* replace fields */
1103	var nstr = "", jj;
1104	for(i=0; i < out.length; ++i) {
1105		switch(out[i].t) {
1106			case 't': case 'T': case ' ': case 'D': break;
1107			case 'X': out[i].v = ""; out[i].t = ";"; break;
1108			case 'd': case 'm': case 'y': case 'h': case 'H': case 'M': case 's': case 'e': case 'b': case 'Z':
1109				/*::if(!dt) throw "unreachable"; */
1110				out[i].v = SSF_write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
1111				out[i].t = 't'; break;
1112			case 'n': case '?':
1113				jj = i+1;
1114				while(out[jj] != null && (
1115					(c=out[jj].t) === "?" || c === "D" ||
1116					((c === " " || c === "t") && out[jj+1] != null && (out[jj+1].t === '?' || out[jj+1].t === "t" && out[jj+1].v === '/')) ||
1117					(out[i].t === '(' && (c === ' ' || c === 'n' || c === ')')) ||
1118					(c === 't' && (out[jj].v === '/' || out[jj].v === ' ' && out[jj+1] != null && out[jj+1].t == '?'))
1119				)) {
1120					out[i].v += out[jj].v;
1121					out[jj] = {v:"", t:";"}; ++jj;
1122				}
1123				nstr += out[i].v;
1124				i = jj-1; break;
1125			case 'G': out[i].t = 't'; out[i].v = SSF_general(v,opts); break;
1126		}
1127	}
1128	var vv = "", myv, ostr;
1129	if(nstr.length > 0) {
1130		if(nstr.charCodeAt(0) == 40) /* '(' */ {
1131			myv = (v<0&&nstr.charCodeAt(0) === 45 ? -v : v);
1132			ostr = write_num('n', nstr, myv);
1133		} else {
1134			myv = (v<0 && flen > 1 ? -v : v);
1135			ostr = write_num('n', nstr, myv);
1136			if(myv < 0 && out[0] && out[0].t == 't') {
1137				ostr = ostr.substr(1);
1138				out[0].v = "-" + out[0].v;
1139			}
1140		}
1141		jj=ostr.length-1;
1142		var decpt = out.length;
1143		for(i=0; i < out.length; ++i) if(out[i] != null && out[i].t != 't' && out[i].v.indexOf(".") > -1) { decpt = i; break; }
1144		var lasti=out.length;
1145		if(decpt === out.length && ostr.indexOf("E") === -1) {
1146			for(i=out.length-1; i>= 0;--i) {
1147				if(out[i] == null || 'n?'.indexOf(out[i].t) === -1) continue;
1148				if(jj>=out[i].v.length-1) { jj -= out[i].v.length; out[i].v = ostr.substr(jj+1, out[i].v.length); }
1149				else if(jj < 0) out[i].v = "";
1150				else { out[i].v = ostr.substr(0, jj+1); jj = -1; }
1151				out[i].t = 't';
1152				lasti = i;
1153			}
1154			if(jj>=0 && lasti<out.length) out[lasti].v = ostr.substr(0,jj+1) + out[lasti].v;
1155		}
1156		else if(decpt !== out.length && ostr.indexOf("E") === -1) {
1157			jj = ostr.indexOf(".")-1;
1158			for(i=decpt; i>= 0; --i) {
1159				if(out[i] == null || 'n?'.indexOf(out[i].t) === -1) continue;
1160				j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")-1:out[i].v.length-1;
1161				vv = out[i].v.substr(j+1);
1162				for(; j>=0; --j) {
1163					if(jj>=0 && (out[i].v.charAt(j) === "0" || out[i].v.charAt(j) === "#")) vv = ostr.charAt(jj--) + vv;
1164				}
1165				out[i].v = vv;
1166				out[i].t = 't';
1167				lasti = i;
1168			}
1169			if(jj>=0 && lasti<out.length) out[lasti].v = ostr.substr(0,jj+1) + out[lasti].v;
1170			jj = ostr.indexOf(".")+1;
1171			for(i=decpt; i<out.length; ++i) {
1172				if(out[i] == null || ('n?('.indexOf(out[i].t) === -1 && i !== decpt)) continue;
1173				j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")+1:0;
1174				vv = out[i].v.substr(0,j);
1175				for(; j<out[i].v.length; ++j) {
1176					if(jj<ostr.length) vv += ostr.charAt(jj++);
1177				}
1178				out[i].v = vv;
1179				out[i].t = 't';
1180				lasti = i;
1181			}
1182		}
1183	}
1184	for(i=0; i<out.length; ++i) if(out[i] != null && 'n?'.indexOf(out[i].t)>-1) {
1185		myv = (flen >1 && v < 0 && i>0 && out[i-1].v === "-" ? -v:v);
1186		out[i].v = write_num(out[i].t, out[i].v, myv);
1187		out[i].t = 't';
1188	}
1189	var retval = "";
1190	for(i=0; i !== out.length; ++i) if(out[i] != null) retval += out[i].v;
1191	return retval;
1192}
1193
1194var cfregex2 = /\[(=|>[=]?|<[>=]?)(-?\d+(?:\.\d*)?)\]/;
1195function chkcond(v, rr) {
1196	if(rr == null) return false;
1197	var thresh = parseFloat(rr[2]);
1198	switch(rr[1]) {
1199		case "=":  if(v == thresh) return true; break;
1200		case ">":  if(v >  thresh) return true; break;
1201		case "<":  if(v <  thresh) return true; break;
1202		case "<>": if(v != thresh) return true; break;
1203		case ">=": if(v >= thresh) return true; break;
1204		case "<=": if(v <= thresh) return true; break;
1205	}
1206	return false;
1207}
1208function choose_fmt(f/*:string*/, v/*:any*/) {
1209	var fmt = SSF_split_fmt(f);
1210	var l = fmt.length, lat = fmt[l-1].indexOf("@");
1211	if(l<4 && lat>-1) --l;
1212	if(fmt.length > 4) throw new Error("cannot find right format for |" + fmt.join("|") + "|");
1213	if(typeof v !== "number") return [4, fmt.length === 4 || lat>-1?fmt[fmt.length-1]:"@"];
1214	switch(fmt.length) {
1215		case 1: fmt = lat>-1 ? ["General", "General", "General", fmt[0]] : [fmt[0], fmt[0], fmt[0], "@"]; break;
1216		case 2: fmt = lat>-1 ? [fmt[0], fmt[0], fmt[0], fmt[1]] : [fmt[0], fmt[1], fmt[0], "@"]; break;
1217		case 3: fmt = lat>-1 ? [fmt[0], fmt[1], fmt[0], fmt[2]] : [fmt[0], fmt[1], fmt[2], "@"]; break;
1218		case 4: break;
1219	}
1220	var ff = v > 0 ? fmt[0] : v < 0 ? fmt[1] : fmt[2];
1221	if(fmt[0].indexOf("[") === -1 && fmt[1].indexOf("[") === -1) return [l, ff];
1222	if(fmt[0].match(/\[[=<>]/) != null || fmt[1].match(/\[[=<>]/) != null) {
1223		var m1 = fmt[0].match(cfregex2);
1224		var m2 = fmt[1].match(cfregex2);
1225		return chkcond(v, m1) ? [l, fmt[0]] : chkcond(v, m2) ? [l, fmt[1]] : [l, fmt[m1 != null && m2 != null ? 2 : 1]];
1226	}
1227	return [l, ff];
1228}
1229function SSF_format(fmt/*:string|number*/,v/*:any*/,o/*:?any*/) {
1230	if(o == null) o = {};
1231	var sfmt = "";
1232	switch(typeof fmt) {
1233		case "string":
1234			if(fmt == "m/d/yy" && o.dateNF) sfmt = o.dateNF;
1235			else sfmt = fmt;
1236			break;
1237		case "number":
1238			if(fmt == 14 && o.dateNF) sfmt = o.dateNF;
1239			else sfmt = (o.table != null ? (o.table/*:any*/) : table_fmt)[fmt];
1240			if(sfmt == null) sfmt = (o.table && o.table[SSF_default_map[fmt]]) || table_fmt[SSF_default_map[fmt]];
1241			if(sfmt == null) sfmt = SSF_default_str[fmt] || "General";
1242			break;
1243	}
1244	if(SSF_isgeneral(sfmt,0)) return SSF_general(v, o);
1245	if(v instanceof Date) v = datenum_local(v, o.date1904);
1246	var f = choose_fmt(sfmt, v);
1247	if(SSF_isgeneral(f[1])) return SSF_general(v, o);
1248	if(v === true) v = "TRUE"; else if(v === false) v = "FALSE";
1249	else if(v === "" || v == null) return "";
1250	return eval_fmt(f[1], v, o, f[0]);
1251}
1252function SSF_load(fmt/*:string*/, idx/*:?number*/)/*:number*/ {
1253	if(typeof idx != 'number') {
1254		idx = +idx || -1;
1255/*::if(typeof idx != 'number') return 0x188; */
1256		for(var i = 0; i < 0x0188; ++i) {
1257/*::if(typeof idx != 'number') return 0x188; */
1258			if(table_fmt[i] == undefined) { if(idx < 0) idx = i; continue; }
1259			if(table_fmt[i] == fmt) { idx = i; break; }
1260		}
1261/*::if(typeof idx != 'number') return 0x188; */
1262		if(idx < 0) idx = 0x187;
1263	}
1264/*::if(typeof idx != 'number') return 0x188; */
1265	table_fmt[idx] = fmt;
1266	return idx;
1267}
1268function SSF_load_table(tbl/*:SSFTable*/)/*:void*/ {
1269	for(var i=0; i!=0x0188; ++i)
1270		if(tbl[i] !== undefined) SSF_load(tbl[i], i);
1271}
1272
1273function make_ssf() {
1274	table_fmt = SSF_init_table();
1275}
1276
1277var SSF = {
1278	format: SSF_format,
1279	load: SSF_load,
1280	_table: table_fmt,
1281	load_table: SSF_load_table,
1282	parse_date_code: SSF_parse_date_code,
1283	is_date: fmt_is_date,
1284	get_table: function get_table() { return SSF._table = table_fmt; }
1285};
1286
1287var SSFImplicit/*{[number]:string}*/ = ({
1288	"5": '"$"#,##0_);\\("$"#,##0\\)',
1289	"6": '"$"#,##0_);[Red]\\("$"#,##0\\)',
1290	"7": '"$"#,##0.00_);\\("$"#,##0.00\\)',
1291	"8": '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)',
1292	"23": 'General', "24": 'General', "25": 'General', "26": 'General',
1293	"27": 'm/d/yy', "28": 'm/d/yy', "29": 'm/d/yy', "30": 'm/d/yy', "31": 'm/d/yy',
1294	"32": 'h:mm:ss', "33": 'h:mm:ss', "34": 'h:mm:ss', "35": 'h:mm:ss',
1295	"36": 'm/d/yy',
1296	"41": '_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)',
1297	"42": '_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)',
1298	"43": '_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)',
1299	"44": '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)',
1300	"50": 'm/d/yy', "51": 'm/d/yy', "52": 'm/d/yy', "53": 'm/d/yy', "54": 'm/d/yy',
1301	"55": 'm/d/yy', "56": 'm/d/yy', "57": 'm/d/yy', "58": 'm/d/yy',
1302	"59": '0',
1303	"60": '0.00',
1304	"61": '#,##0',
1305	"62": '#,##0.00',
1306	"63": '"$"#,##0_);\\("$"#,##0\\)',
1307	"64": '"$"#,##0_);[Red]\\("$"#,##0\\)',
1308	"65": '"$"#,##0.00_);\\("$"#,##0.00\\)',
1309	"66": '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)',
1310	"67": '0%',
1311	"68": '0.00%',
1312	"69": '# ?/?',
1313	"70": '# ??/??',
1314	"71": 'm/d/yy',
1315	"72": 'm/d/yy',
1316	"73": 'd-mmm-yy',
1317	"74": 'd-mmm',
1318	"75": 'mmm-yy',
1319	"76": 'h:mm',
1320	"77": 'h:mm:ss',
1321	"78": 'm/d/yy h:mm',
1322	"79": 'mm:ss',
1323	"80": '[h]:mm:ss',
1324	"81": 'mmss.0'
1325}/*:any*/);
1326
1327/* dateNF parse TODO: move to SSF */
1328var dateNFregex = /[dD]+|[mM]+|[yYeE]+|[Hh]+|[Ss]+/g;
1329function dateNF_regex(dateNF/*:string|number*/)/*:RegExp*/ {
1330	var fmt = typeof dateNF == "number" ? table_fmt[dateNF] : dateNF;
1331	fmt = fmt.replace(dateNFregex, "(\\d+)");
1332	return new RegExp("^" + fmt + "$");
1333}
1334function dateNF_fix(str/*:string*/, dateNF/*:string*/, match/*:Array<string>*/)/*:string*/ {
1335	var Y = -1, m = -1, d = -1, H = -1, M = -1, S = -1;
1336	(dateNF.match(dateNFregex)||[]).forEach(function(n, i) {
1337		var v = parseInt(match[i+1], 10);
1338		switch(n.toLowerCase().charAt(0)) {
1339			case 'y': Y = v; break; case 'd': d = v; break;
1340			case 'h': H = v; break; case 's': S = v; break;
1341			case 'm': if(H >= 0) M = v; else m = v; break;
1342		}
1343	});
1344	if(S >= 0 && M == -1 && m >= 0) { M = m; m = -1; }
1345	var datestr = (("" + (Y>=0?Y: new Date().getFullYear())).slice(-4) + "-" + ("00" + (m>=1?m:1)).slice(-2) + "-" + ("00" + (d>=1?d:1)).slice(-2));
1346	if(datestr.length == 7) datestr = "0" + datestr;
1347	if(datestr.length == 8) datestr = "20" + datestr;
1348	var timestr = (("00" + (H>=0?H:0)).slice(-2) + ":" + ("00" + (M>=0?M:0)).slice(-2) + ":" + ("00" + (S>=0?S:0)).slice(-2));
1349	if(H == -1 && M == -1 && S == -1) return datestr;
1350	if(Y == -1 && m == -1 && d == -1) return timestr;
1351	return datestr + "T" + timestr;
1352}
1353
1354/* table of bad formats written by third-party tools */
1355var bad_formats = {
1356	"d.m": "d\\.m" // Issue #2571 Google Sheets writes invalid format 'd.m', correct format is 'd"."m' or 'd\\.m'
1357};
1358
1359function SSF__load(fmt, idx) {
1360	return SSF_load(bad_formats[fmt] || fmt, idx);
1361}
1362
1363/*::
1364declare var ReadShift:any;
1365declare var CheckField:any;
1366declare var prep_blob:any;
1367declare var __readUInt32LE:any;
1368declare var __readInt32LE:any;
1369declare var __toBuffer:any;
1370declare var __utf16le:any;
1371declare var bconcat:any;
1372declare var s2a:any;
1373declare var chr0:any;
1374declare var chr1:any;
1375declare var has_buf:boolean;
1376declare var new_buf:any;
1377declare var new_raw_buf:any;
1378declare var new_unsafe_buf:any;
1379declare var Buffer_from:any;
1380*/
1381/* cfb.js (C) 2013-present SheetJS -- http://sheetjs.com */
1382/* vim: set ts=2: */
1383/*jshint eqnull:true */
1384/*exported CFB */
1385/*global Uint8Array:false, Uint16Array:false */
1386
1387/*::
1388type SectorEntry = {
1389	name?:string;
1390	nodes?:Array<number>;
1391	data:RawBytes;
1392};
1393type SectorList = {
1394	[k:string|number]:SectorEntry;
1395	name:?string;
1396	fat_addrs:Array<number>;
1397	ssz:number;
1398}
1399type CFBFiles = {[n:string]:CFBEntry};
1400*/
1401/* crc32.js (C) 2014-present SheetJS -- http://sheetjs.com */
1402/* vim: set ts=2: */
1403/*exported CRC32 */
1404var CRC32 = /*#__PURE__*/(function() {
1405var CRC32 = {};
1406CRC32.version = '1.2.0';
1407/* see perf/crc32table.js */
1408/*global Int32Array */
1409function signed_crc_table()/*:any*/ {
1410	var c = 0, table/*:Array<number>*/ = new Array(256);
1411
1412	for(var n =0; n != 256; ++n){
1413		c = n;
1414		c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
1415		c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
1416		c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
1417		c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
1418		c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
1419		c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
1420		c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
1421		c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
1422		table[n] = c;
1423	}
1424
1425	return typeof Int32Array !== 'undefined' ? new Int32Array(table) : table;
1426}
1427
1428var T0 = signed_crc_table();
1429function slice_by_16_tables(T) {
1430	var c = 0, v = 0, n = 0, table/*:Array<number>*/ = typeof Int32Array !== 'undefined' ? new Int32Array(4096) : new Array(4096) ;
1431
1432	for(n = 0; n != 256; ++n) table[n] = T[n];
1433	for(n = 0; n != 256; ++n) {
1434		v = T[n];
1435		for(c = 256 + n; c < 4096; c += 256) v = table[c] = (v >>> 8) ^ T[v & 0xFF];
1436	}
1437	var out = [];
1438	for(n = 1; n != 16; ++n) out[n - 1] = typeof Int32Array !== 'undefined' ? table.subarray(n * 256, n * 256 + 256) : table.slice(n * 256, n * 256 + 256);
1439	return out;
1440}
1441var TT = slice_by_16_tables(T0);
1442var T1 = TT[0],  T2 = TT[1],  T3 = TT[2],  T4 = TT[3],  T5 = TT[4];
1443var T6 = TT[5],  T7 = TT[6],  T8 = TT[7],  T9 = TT[8],  Ta = TT[9];
1444var Tb = TT[10], Tc = TT[11], Td = TT[12], Te = TT[13], Tf = TT[14];
1445function crc32_bstr(bstr/*:string*/, seed/*:number*/)/*:number*/ {
1446	var C = seed/*:: ? 0 : 0 */ ^ -1;
1447	for(var i = 0, L = bstr.length; i < L;) C = (C>>>8) ^ T0[(C^bstr.charCodeAt(i++))&0xFF];
1448	return ~C;
1449}
1450
1451function crc32_buf(B/*:Uint8Array|Array<number>*/, seed/*:number*/)/*:number*/ {
1452	var C = seed/*:: ? 0 : 0 */ ^ -1, L = B.length - 15, i = 0;
1453	for(; i < L;) C =
1454		Tf[B[i++] ^ (C & 255)] ^
1455		Te[B[i++] ^ ((C >> 8) & 255)] ^
1456		Td[B[i++] ^ ((C >> 16) & 255)] ^
1457		Tc[B[i++] ^ (C >>> 24)] ^
1458		Tb[B[i++]] ^ Ta[B[i++]] ^ T9[B[i++]] ^ T8[B[i++]] ^
1459		T7[B[i++]] ^ T6[B[i++]] ^ T5[B[i++]] ^ T4[B[i++]] ^
1460		T3[B[i++]] ^ T2[B[i++]] ^ T1[B[i++]] ^ T0[B[i++]];
1461	L += 15;
1462	while(i < L) C = (C>>>8) ^ T0[(C^B[i++])&0xFF];
1463	return ~C;
1464}
1465
1466function crc32_str(str/*:string*/, seed/*:number*/)/*:number*/ {
1467	var C = seed ^ -1;
1468	for(var i = 0, L = str.length, c = 0, d = 0; i < L;) {
1469		c = str.charCodeAt(i++);
1470		if(c < 0x80) {
1471			C = (C>>>8) ^ T0[(C^c)&0xFF];
1472		} else if(c < 0x800) {
1473			C = (C>>>8) ^ T0[(C ^ (192|((c>>6)&31)))&0xFF];
1474			C = (C>>>8) ^ T0[(C ^ (128|(c&63)))&0xFF];
1475		} else if(c >= 0xD800 && c < 0xE000) {
1476			c = (c&1023)+64; d = str.charCodeAt(i++)&1023;
1477			C = (C>>>8) ^ T0[(C ^ (240|((c>>8)&7)))&0xFF];
1478			C = (C>>>8) ^ T0[(C ^ (128|((c>>2)&63)))&0xFF];
1479			C = (C>>>8) ^ T0[(C ^ (128|((d>>6)&15)|((c&3)<<4)))&0xFF];
1480			C = (C>>>8) ^ T0[(C ^ (128|(d&63)))&0xFF];
1481		} else {
1482			C = (C>>>8) ^ T0[(C ^ (224|((c>>12)&15)))&0xFF];
1483			C = (C>>>8) ^ T0[(C ^ (128|((c>>6)&63)))&0xFF];
1484			C = (C>>>8) ^ T0[(C ^ (128|(c&63)))&0xFF];
1485		}
1486	}
1487	return ~C;
1488}
1489CRC32.table = T0;
1490CRC32.bstr = crc32_bstr;
1491CRC32.buf = crc32_buf;
1492CRC32.str = crc32_str;
1493return CRC32;
1494})();
1495/* [MS-CFB] v20171201 */
1496var CFB = /*#__PURE__*/(function _CFB(){
1497var exports/*:CFBModule*/ = /*::(*/{}/*:: :any)*/;
1498exports.version = '1.2.2';
1499/* [MS-CFB] 2.6.4 */
1500function namecmp(l/*:string*/, r/*:string*/)/*:number*/ {
1501	var L = l.split("/"), R = r.split("/");
1502	for(var i = 0, c = 0, Z = Math.min(L.length, R.length); i < Z; ++i) {
1503		if((c = L[i].length - R[i].length)) return c;
1504		if(L[i] != R[i]) return L[i] < R[i] ? -1 : 1;
1505	}
1506	return L.length - R.length;
1507}
1508function dirname(p/*:string*/)/*:string*/ {
1509	if(p.charAt(p.length - 1) == "/") return (p.slice(0,-1).indexOf("/") === -1) ? p : dirname(p.slice(0, -1));
1510	var c = p.lastIndexOf("/");
1511	return (c === -1) ? p : p.slice(0, c+1);
1512}
1513
1514function filename(p/*:string*/)/*:string*/ {
1515	if(p.charAt(p.length - 1) == "/") return filename(p.slice(0, -1));
1516	var c = p.lastIndexOf("/");
1517	return (c === -1) ? p : p.slice(c+1);
1518}
1519/* -------------------------------------------------------------------------- */
1520/* DOS Date format:
1521   high|YYYYYYYm.mmmddddd.HHHHHMMM.MMMSSSSS|low
1522   add 1980 to stored year
1523   stored second should be doubled
1524*/
1525
1526/* write JS date to buf as a DOS date */
1527function write_dos_date(buf/*:CFBlob*/, date/*:Date|string*/) {
1528	if(typeof date === "string") date = new Date(date);
1529	var hms/*:number*/ = date.getHours();
1530	hms = hms << 6 | date.getMinutes();
1531	hms = hms << 5 | (date.getSeconds()>>>1);
1532	buf.write_shift(2, hms);
1533	var ymd/*:number*/ = (date.getFullYear() - 1980);
1534	ymd = ymd << 4 | (date.getMonth()+1);
1535	ymd = ymd << 5 | date.getDate();
1536	buf.write_shift(2, ymd);
1537}
1538
1539/* read four bytes from buf and interpret as a DOS date */
1540function parse_dos_date(buf/*:CFBlob*/)/*:Date*/ {
1541	var hms = buf.read_shift(2) & 0xFFFF;
1542	var ymd = buf.read_shift(2) & 0xFFFF;
1543	var val = new Date();
1544	var d = ymd & 0x1F; ymd >>>= 5;
1545	var m = ymd & 0x0F; ymd >>>= 4;
1546	val.setMilliseconds(0);
1547	val.setFullYear(ymd + 1980);
1548	val.setMonth(m-1);
1549	val.setDate(d);
1550	var S = hms & 0x1F; hms >>>= 5;
1551	var M = hms & 0x3F; hms >>>= 6;
1552	val.setHours(hms);
1553	val.setMinutes(M);
1554	val.setSeconds(S<<1);
1555	return val;
1556}
1557function parse_extra_field(blob/*:CFBlob*/)/*:any*/ {
1558	prep_blob(blob, 0);
1559	var o = /*::(*/{}/*:: :any)*/;
1560	var flags = 0;
1561	while(blob.l <= blob.length - 4) {
1562		var type = blob.read_shift(2);
1563		var sz = blob.read_shift(2), tgt = blob.l + sz;
1564		var p = {};
1565		switch(type) {
1566			/* UNIX-style Timestamps */
1567			case 0x5455: {
1568				flags = blob.read_shift(1);
1569				if(flags & 1) p.mtime = blob.read_shift(4);
1570				/* for some reason, CD flag corresponds to LFH */
1571				if(sz > 5) {
1572					if(flags & 2) p.atime = blob.read_shift(4);
1573					if(flags & 4) p.ctime = blob.read_shift(4);
1574				}
1575				if(p.mtime) p.mt = new Date(p.mtime*1000);
1576			} break;
1577			/* ZIP64 Extended Information Field */
1578			case 0x0001: {
1579				var sz1 = blob.read_shift(4), sz2 = blob.read_shift(4);
1580				p.usz = (sz2 * Math.pow(2,32) + sz1);
1581				sz1 = blob.read_shift(4); sz2 = blob.read_shift(4);
1582				p.csz = (sz2 * Math.pow(2,32) + sz1);
1583				// NOTE: volume fields are skipped
1584			} break;
1585		}
1586		blob.l = tgt;
1587		o[type] = p;
1588	}
1589	return o;
1590}
1591var fs/*:: = require('fs'); */;
1592function get_fs() { return fs || (fs = _fs); }
1593function parse(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ {
1594if(file[0] == 0x50 && file[1] == 0x4b) return parse_zip(file, options);
1595if((file[0] | 0x20) == 0x6d && (file[1]|0x20) == 0x69) return parse_mad(file, options);
1596if(file.length < 512) throw new Error("CFB file size " + file.length + " < 512");
1597var mver = 3;
1598var ssz = 512;
1599var nmfs = 0; // number of mini FAT sectors
1600var difat_sec_cnt = 0;
1601var dir_start = 0;
1602var minifat_start = 0;
1603var difat_start = 0;
1604
1605var fat_addrs/*:Array<number>*/ = []; // locations of FAT sectors
1606
1607/* [MS-CFB] 2.2 Compound File Header */
1608var blob/*:CFBlob*/ = /*::(*/file.slice(0,512)/*:: :any)*/;
1609prep_blob(blob, 0);
1610
1611/* major version */
1612var mv = check_get_mver(blob);
1613mver = mv[0];
1614switch(mver) {
1615	case 3: ssz = 512; break; case 4: ssz = 4096; break;
1616	case 0: if(mv[1] == 0) return parse_zip(file, options);
1617	/* falls through */
1618	default: throw new Error("Major Version: Expected 3 or 4 saw " + mver);
1619}
1620
1621/* reprocess header */
1622if(ssz !== 512) { blob = /*::(*/file.slice(0,ssz)/*:: :any)*/; prep_blob(blob, 28 /* blob.l */); }
1623/* Save header for final object */
1624var header/*:RawBytes*/ = file.slice(0,ssz);
1625
1626check_shifts(blob, mver);
1627
1628// Number of Directory Sectors
1629var dir_cnt/*:number*/ = blob.read_shift(4, 'i');
1630if(mver === 3 && dir_cnt !== 0) throw new Error('# Directory Sectors: Expected 0 saw ' + dir_cnt);
1631
1632// Number of FAT Sectors
1633blob.l += 4;
1634
1635// First Directory Sector Location
1636dir_start = blob.read_shift(4, 'i');
1637
1638// Transaction Signature
1639blob.l += 4;
1640
1641// Mini Stream Cutoff Size
1642blob.chk('00100000', 'Mini Stream Cutoff Size: ');
1643
1644// First Mini FAT Sector Location
1645minifat_start = blob.read_shift(4, 'i');
1646
1647// Number of Mini FAT Sectors
1648nmfs = blob.read_shift(4, 'i');
1649
1650// First DIFAT sector location
1651difat_start = blob.read_shift(4, 'i');
1652
1653// Number of DIFAT Sectors
1654difat_sec_cnt = blob.read_shift(4, 'i');
1655
1656// Grab FAT Sector Locations
1657for(var q = -1, j = 0; j < 109; ++j) { /* 109 = (512 - blob.l)>>>2; */
1658	q = blob.read_shift(4, 'i');
1659	if(q<0) break;
1660	fat_addrs[j] = q;
1661}
1662
1663/** Break the file up into sectors */
1664var sectors/*:Array<RawBytes>*/ = sectorify(file, ssz);
1665
1666sleuth_fat(difat_start, difat_sec_cnt, sectors, ssz, fat_addrs);
1667
1668/** Chains */
1669var sector_list/*:SectorList*/ = make_sector_list(sectors, dir_start, fat_addrs, ssz);
1670
1671sector_list[dir_start].name = "!Directory";
1672if(nmfs > 0 && minifat_start !== ENDOFCHAIN) sector_list[minifat_start].name = "!MiniFAT";
1673sector_list[fat_addrs[0]].name = "!FAT";
1674sector_list.fat_addrs = fat_addrs;
1675sector_list.ssz = ssz;
1676
1677/* [MS-CFB] 2.6.1 Compound File Directory Entry */
1678var files/*:CFBFiles*/ = {}, Paths/*:Array<string>*/ = [], FileIndex/*:CFBFileIndex*/ = [], FullPaths/*:Array<string>*/ = [];
1679read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, FileIndex, minifat_start);
1680
1681build_full_paths(FileIndex, FullPaths, Paths);
1682Paths.shift();
1683
1684var o = {
1685	FileIndex: FileIndex,
1686	FullPaths: FullPaths
1687};
1688
1689// $FlowIgnore
1690if(options && options.raw) o.raw = {header: header, sectors: sectors};
1691return o;
1692} // parse
1693
1694/* [MS-CFB] 2.2 Compound File Header -- read up to major version */
1695function check_get_mver(blob/*:CFBlob*/)/*:[number, number]*/ {
1696	if(blob[blob.l] == 0x50 && blob[blob.l + 1] == 0x4b) return [0, 0];
1697	// header signature 8
1698	blob.chk(HEADER_SIGNATURE, 'Header Signature: ');
1699
1700	// clsid 16
1701	//blob.chk(HEADER_CLSID, 'CLSID: ');
1702	blob.l += 16;
1703
1704	// minor version 2
1705	var mver/*:number*/ = blob.read_shift(2, 'u');
1706
1707	return [blob.read_shift(2,'u'), mver];
1708}
1709function check_shifts(blob/*:CFBlob*/, mver/*:number*/)/*:void*/ {
1710	var shift = 0x09;
1711
1712	// Byte Order
1713	//blob.chk('feff', 'Byte Order: '); // note: some writers put 0xffff
1714	blob.l += 2;
1715
1716	// Sector Shift
1717	switch((shift = blob.read_shift(2))) {
1718		case 0x09: if(mver != 3) throw new Error('Sector Shift: Expected 9 saw ' + shift); break;
1719		case 0x0c: if(mver != 4) throw new Error('Sector Shift: Expected 12 saw ' + shift); break;
1720		default: throw new Error('Sector Shift: Expected 9 or 12 saw ' + shift);
1721	}
1722
1723	// Mini Sector Shift
1724	blob.chk('0600', 'Mini Sector Shift: ');
1725
1726	// Reserved
1727	blob.chk('000000000000', 'Reserved: ');
1728}
1729
1730/** Break the file up into sectors */
1731function sectorify(file/*:RawBytes*/, ssz/*:number*/)/*:Array<RawBytes>*/ {
1732	var nsectors = Math.ceil(file.length/ssz)-1;
1733	var sectors/*:Array<RawBytes>*/ = [];
1734	for(var i=1; i < nsectors; ++i) sectors[i-1] = file.slice(i*ssz,(i+1)*ssz);
1735	sectors[nsectors-1] = file.slice(nsectors*ssz);
1736	return sectors;
1737}
1738
1739/* [MS-CFB] 2.6.4 Red-Black Tree */
1740function build_full_paths(FI/*:CFBFileIndex*/, FP/*:Array<string>*/, Paths/*:Array<string>*/)/*:void*/ {
1741	var i = 0, L = 0, R = 0, C = 0, j = 0, pl = Paths.length;
1742	var dad/*:Array<number>*/ = [], q/*:Array<number>*/ = [];
1743
1744	for(; i < pl; ++i) { dad[i]=q[i]=i; FP[i]=Paths[i]; }
1745
1746	for(; j < q.length; ++j) {
1747		i = q[j];
1748		L = FI[i].L; R = FI[i].R; C = FI[i].C;
1749		if(dad[i] === i) {
1750			if(L !== -1 /*NOSTREAM*/ && dad[L] !== L) dad[i] = dad[L];
1751			if(R !== -1 && dad[R] !== R) dad[i] = dad[R];
1752		}
1753		if(C !== -1 /*NOSTREAM*/) dad[C] = i;
1754		if(L !== -1 && i != dad[i]) { dad[L] = dad[i]; if(q.lastIndexOf(L) < j) q.push(L); }
1755		if(R !== -1 && i != dad[i]) { dad[R] = dad[i]; if(q.lastIndexOf(R) < j) q.push(R); }
1756	}
1757	for(i=1; i < pl; ++i) if(dad[i] === i) {
1758		if(R !== -1 /*NOSTREAM*/ && dad[R] !== R) dad[i] = dad[R];
1759		else if(L !== -1 && dad[L] !== L) dad[i] = dad[L];
1760	}
1761
1762	for(i=1; i < pl; ++i) {
1763		if(FI[i].type === 0 /* unknown */) continue;
1764		j = i;
1765		if(j != dad[j]) do {
1766			j = dad[j];
1767			FP[i] = FP[j] + "/" + FP[i];
1768		} while (j !== 0 && -1 !== dad[j] && j != dad[j]);
1769		dad[i] = -1;
1770	}
1771
1772	FP[0] += "/";
1773	for(i=1; i < pl; ++i) {
1774		if(FI[i].type !== 2 /* stream */) FP[i] += "/";
1775	}
1776}
1777
1778function get_mfat_entry(entry/*:CFBEntry*/, payload/*:RawBytes*/, mini/*:?RawBytes*/)/*:CFBlob*/ {
1779	var start = entry.start, size = entry.size;
1780	//return (payload.slice(start*MSSZ, start*MSSZ + size)/*:any*/);
1781	var o = [];
1782	var idx = start;
1783	while(mini && size > 0 && idx >= 0) {
1784		o.push(payload.slice(idx * MSSZ, idx * MSSZ + MSSZ));
1785		size -= MSSZ;
1786		idx = __readInt32LE(mini, idx * 4);
1787	}
1788	if(o.length === 0) return (new_buf(0)/*:any*/);
1789	return (bconcat(o).slice(0, entry.size)/*:any*/);
1790}
1791
1792/** Chase down the rest of the DIFAT chain to build a comprehensive list
1793    DIFAT chains by storing the next sector number as the last 32 bits */
1794function sleuth_fat(idx/*:number*/, cnt/*:number*/, sectors/*:Array<RawBytes>*/, ssz/*:number*/, fat_addrs)/*:void*/ {
1795	var q/*:number*/ = ENDOFCHAIN;
1796	if(idx === ENDOFCHAIN) {
1797		if(cnt !== 0) throw new Error("DIFAT chain shorter than expected");
1798	} else if(idx !== -1 /*FREESECT*/) {
1799		var sector = sectors[idx], m = (ssz>>>2)-1;
1800		if(!sector) return;
1801		for(var i = 0; i < m; ++i) {
1802			if((q = __readInt32LE(sector,i*4)) === ENDOFCHAIN) break;
1803			fat_addrs.push(q);
1804		}
1805		if(cnt >= 1) sleuth_fat(__readInt32LE(sector,ssz-4),cnt - 1, sectors, ssz, fat_addrs);
1806	}
1807}
1808
1809/** Follow the linked list of sectors for a given starting point */
1810function get_sector_list(sectors/*:Array<RawBytes>*/, start/*:number*/, fat_addrs/*:Array<number>*/, ssz/*:number*/, chkd/*:?Array<boolean>*/)/*:SectorEntry*/ {
1811	var buf/*:Array<number>*/ = [], buf_chain/*:Array<any>*/ = [];
1812	if(!chkd) chkd = [];
1813	var modulus = ssz - 1, j = 0, jj = 0;
1814	for(j=start; j>=0;) {
1815		chkd[j] = true;
1816		buf[buf.length] = j;
1817		buf_chain.push(sectors[j]);
1818		var addr = fat_addrs[Math.floor(j*4/ssz)];
1819		jj = ((j*4) & modulus);
1820		if(ssz < 4 + jj) throw new Error("FAT boundary crossed: " + j + " 4 "+ssz);
1821		if(!sectors[addr]) break;
1822		j = __readInt32LE(sectors[addr], jj);
1823	}
1824	return {nodes: buf, data:__toBuffer([buf_chain])};
1825}
1826
1827/** Chase down the sector linked lists */
1828function make_sector_list(sectors/*:Array<RawBytes>*/, dir_start/*:number*/, fat_addrs/*:Array<number>*/, ssz/*:number*/)/*:SectorList*/ {
1829	var sl = sectors.length, sector_list/*:SectorList*/ = ([]/*:any*/);
1830	var chkd/*:Array<boolean>*/ = [], buf/*:Array<number>*/ = [], buf_chain/*:Array<RawBytes>*/ = [];
1831	var modulus = ssz - 1, i=0, j=0, k=0, jj=0;
1832	for(i=0; i < sl; ++i) {
1833		buf = ([]/*:Array<number>*/);
1834		k = (i + dir_start); if(k >= sl) k-=sl;
1835		if(chkd[k]) continue;
1836		buf_chain = [];
1837		var seen = [];
1838		for(j=k; j>=0;) {
1839			seen[j] = true;
1840			chkd[j] = true;
1841			buf[buf.length] = j;
1842			buf_chain.push(sectors[j]);
1843			var addr/*:number*/ = fat_addrs[Math.floor(j*4/ssz)];
1844			jj = ((j*4) & modulus);
1845			if(ssz < 4 + jj) throw new Error("FAT boundary crossed: " + j + " 4 "+ssz);
1846			if(!sectors[addr]) break;
1847			j = __readInt32LE(sectors[addr], jj);
1848			if(seen[j]) break;
1849		}
1850		sector_list[k] = ({nodes: buf, data:__toBuffer([buf_chain])}/*:SectorEntry*/);
1851	}
1852	return sector_list;
1853}
1854
1855/* [MS-CFB] 2.6.1 Compound File Directory Entry */
1856function read_directory(dir_start/*:number*/, sector_list/*:SectorList*/, sectors/*:Array<RawBytes>*/, Paths/*:Array<string>*/, nmfs, files, FileIndex, mini) {
1857	var minifat_store = 0, pl = (Paths.length?2:0);
1858	var sector = sector_list[dir_start].data;
1859	var i = 0, namelen = 0, name;
1860	for(; i < sector.length; i+= 128) {
1861		var blob/*:CFBlob*/ = /*::(*/sector.slice(i, i+128)/*:: :any)*/;
1862		prep_blob(blob, 64);
1863		namelen = blob.read_shift(2);
1864		name = __utf16le(blob,0,namelen-pl);
1865		Paths.push(name);
1866		var o/*:CFBEntry*/ = ({
1867			name:  name,
1868			type:  blob.read_shift(1),
1869			color: blob.read_shift(1),
1870			L:     blob.read_shift(4, 'i'),
1871			R:     blob.read_shift(4, 'i'),
1872			C:     blob.read_shift(4, 'i'),
1873			clsid: blob.read_shift(16),
1874			state: blob.read_shift(4, 'i'),
1875			start: 0,
1876			size: 0
1877		});
1878		var ctime/*:number*/ = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2);
1879		if(ctime !== 0) o.ct = read_date(blob, blob.l-8);
1880		var mtime/*:number*/ = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2);
1881		if(mtime !== 0) o.mt = read_date(blob, blob.l-8);
1882		o.start = blob.read_shift(4, 'i');
1883		o.size = blob.read_shift(4, 'i');
1884		if(o.size < 0 && o.start < 0) { o.size = o.type = 0; o.start = ENDOFCHAIN; o.name = ""; }
1885		if(o.type === 5) { /* root */
1886			minifat_store = o.start;
1887			if(nmfs > 0 && minifat_store !== ENDOFCHAIN) sector_list[minifat_store].name = "!StreamData";
1888			/*minifat_size = o.size;*/
1889		} else if(o.size >= 4096 /* MSCSZ */) {
1890			o.storage = 'fat';
1891			if(sector_list[o.start] === undefined) sector_list[o.start] = get_sector_list(sectors, o.start, sector_list.fat_addrs, sector_list.ssz);
1892			sector_list[o.start].name = o.name;
1893			o.content = (sector_list[o.start].data.slice(0,o.size)/*:any*/);
1894		} else {
1895			o.storage = 'minifat';
1896			if(o.size < 0) o.size = 0;
1897			else if(minifat_store !== ENDOFCHAIN && o.start !== ENDOFCHAIN && sector_list[minifat_store]) {
1898				o.content = get_mfat_entry(o, sector_list[minifat_store].data, (sector_list[mini]||{}).data);
1899			}
1900		}
1901		if(o.content) prep_blob(o.content, 0);
1902		files[name] = o;
1903		FileIndex.push(o);
1904	}
1905}
1906
1907function read_date(blob/*:RawBytes|CFBlob*/, offset/*:number*/)/*:Date*/ {
1908	return new Date(( ( (__readUInt32LE(blob,offset+4)/1e7)*Math.pow(2,32)+__readUInt32LE(blob,offset)/1e7 ) - 11644473600)*1000);
1909}
1910
1911function read_file(filename/*:string*/, options/*:CFBReadOpts*/) {
1912	get_fs();
1913	return parse(fs.readFileSync(filename), options);
1914}
1915
1916function read(blob/*:RawBytes|string*/, options/*:CFBReadOpts*/) {
1917	var type = options && options.type;
1918	if(!type) {
1919		if(has_buf && Buffer.isBuffer(blob)) type = "buffer";
1920	}
1921	switch(type || "base64") {
1922		case "file": /*:: if(typeof blob !== 'string') throw "Must pass a filename when type='file'"; */return read_file(blob, options);
1923		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64_decode(blob)), options);
1924		case "binary": /*:: if(typeof blob !== 'string') throw "Must pass a binary string when type='file'"; */return parse(s2a(blob), options);
1925	}
1926	return parse(/*::typeof blob == 'string' ? new Buffer(blob, 'utf-8') : */blob, options);
1927}
1928
1929function init_cfb(cfb/*:CFBContainer*/, opts/*:?any*/)/*:void*/ {
1930	var o = opts || {}, root = o.root || "Root Entry";
1931	if(!cfb.FullPaths) cfb.FullPaths = [];
1932	if(!cfb.FileIndex) cfb.FileIndex = [];
1933	if(cfb.FullPaths.length !== cfb.FileIndex.length) throw new Error("inconsistent CFB structure");
1934	if(cfb.FullPaths.length === 0) {
1935		cfb.FullPaths[0] = root + "/";
1936		cfb.FileIndex[0] = ({ name: root, type: 5 }/*:any*/);
1937	}
1938	if(o.CLSID) cfb.FileIndex[0].clsid = o.CLSID;
1939	seed_cfb(cfb);
1940}
1941function seed_cfb(cfb/*:CFBContainer*/)/*:void*/ {
1942	var nm = "\u0001Sh33tJ5";
1943	if(CFB.find(cfb, "/" + nm)) return;
1944	var p = new_buf(4); p[0] = 55; p[1] = p[3] = 50; p[2] = 54;
1945	cfb.FileIndex.push(({ name: nm, type: 2, content:p, size:4, L:69, R:69, C:69 }/*:any*/));
1946	cfb.FullPaths.push(cfb.FullPaths[0] + nm);
1947	rebuild_cfb(cfb);
1948}
1949function rebuild_cfb(cfb/*:CFBContainer*/, f/*:?boolean*/)/*:void*/ {
1950	init_cfb(cfb);
1951	var gc = false, s = false;
1952	for(var i = cfb.FullPaths.length - 1; i >= 0; --i) {
1953		var _file = cfb.FileIndex[i];
1954		switch(_file.type) {
1955			case 0:
1956				if(s) gc = true;
1957				else { cfb.FileIndex.pop(); cfb.FullPaths.pop(); }
1958				break;
1959			case 1: case 2: case 5:
1960				s = true;
1961				if(isNaN(_file.R * _file.L * _file.C)) gc = true;
1962				if(_file.R > -1 && _file.L > -1 && _file.R == _file.L) gc = true;
1963				break;
1964			default: gc = true; break;
1965		}
1966	}
1967	if(!gc && !f) return;
1968
1969	var now = new Date(1987, 1, 19), j = 0;
1970	// Track which names exist
1971	var fullPaths = Object.create ? Object.create(null) : {};
1972	var data/*:Array<[string, CFBEntry]>*/ = [];
1973	for(i = 0; i < cfb.FullPaths.length; ++i) {
1974		fullPaths[cfb.FullPaths[i]] = true;
1975		if(cfb.FileIndex[i].type === 0) continue;
1976		data.push([cfb.FullPaths[i], cfb.FileIndex[i]]);
1977	}
1978	for(i = 0; i < data.length; ++i) {
1979		var dad = dirname(data[i][0]);
1980		s = fullPaths[dad];
1981		while(!s) {
1982			while(dirname(dad) && !fullPaths[dirname(dad)]) dad = dirname(dad);
1983
1984			data.push([dad, ({
1985				name: filename(dad).replace("/",""),
1986				type: 1,
1987				clsid: HEADER_CLSID,
1988				ct: now, mt: now,
1989				content: null
1990			}/*:any*/)]);
1991
1992			// Add name to set
1993			fullPaths[dad] = true;
1994
1995			dad = dirname(data[i][0]);
1996			s = fullPaths[dad];
1997		}
1998	}
1999
2000	data.sort(function(x,y) { return namecmp(x[0], y[0]); });
2001	cfb.FullPaths = []; cfb.FileIndex = [];
2002	for(i = 0; i < data.length; ++i) { cfb.FullPaths[i] = data[i][0]; cfb.FileIndex[i] = data[i][1]; }
2003	for(i = 0; i < data.length; ++i) {
2004		var elt = cfb.FileIndex[i];
2005		var nm = cfb.FullPaths[i];
2006
2007		elt.name =  filename(nm).replace("/","");
2008		elt.L = elt.R = elt.C = -(elt.color = 1);
2009		elt.size = elt.content ? elt.content.length : 0;
2010		elt.start = 0;
2011		elt.clsid = (elt.clsid || HEADER_CLSID);
2012		if(i === 0) {
2013			elt.C = data.length > 1 ? 1 : -1;
2014			elt.size = 0;
2015			elt.type = 5;
2016		} else if(nm.slice(-1) == "/") {
2017			for(j=i+1;j < data.length; ++j) if(dirname(cfb.FullPaths[j])==nm) break;
2018			elt.C = j >= data.length ? -1 : j;
2019			for(j=i+1;j < data.length; ++j) if(dirname(cfb.FullPaths[j])==dirname(nm)) break;
2020			elt.R = j >= data.length ? -1 : j;
2021			elt.type = 1;
2022		} else {
2023			if(dirname(cfb.FullPaths[i+1]||"") == dirname(nm)) elt.R = i + 1;
2024			elt.type = 2;
2025		}
2026	}
2027
2028}
2029
2030function _write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ {
2031	var _opts = options || {};
2032	/* MAD is order-sensitive, skip rebuild and sort */
2033	if(_opts.fileType == 'mad') return write_mad(cfb, _opts);
2034	rebuild_cfb(cfb);
2035	switch(_opts.fileType) {
2036		case 'zip': return write_zip(cfb, _opts);
2037		//case 'mad': return write_mad(cfb, _opts);
2038	}
2039	var L = (function(cfb/*:CFBContainer*/)/*:Array<number>*/{
2040		var mini_size = 0, fat_size = 0;
2041		for(var i = 0; i < cfb.FileIndex.length; ++i) {
2042			var file = cfb.FileIndex[i];
2043			if(!file.content) continue;
2044			var flen = file.content.length;
2045			if(flen > 0){
2046				if(flen < 0x1000) mini_size += (flen + 0x3F) >> 6;
2047				else fat_size += (flen + 0x01FF) >> 9;
2048			}
2049		}
2050		var dir_cnt = (cfb.FullPaths.length +3) >> 2;
2051		var mini_cnt = (mini_size + 7) >> 3;
2052		var mfat_cnt = (mini_size + 0x7F) >> 7;
2053		var fat_base = mini_cnt + fat_size + dir_cnt + mfat_cnt;
2054		var fat_cnt = (fat_base + 0x7F) >> 7;
2055		var difat_cnt = fat_cnt <= 109 ? 0 : Math.ceil((fat_cnt-109)/0x7F);
2056		while(((fat_base + fat_cnt + difat_cnt + 0x7F) >> 7) > fat_cnt) difat_cnt = ++fat_cnt <= 109 ? 0 : Math.ceil((fat_cnt-109)/0x7F);
2057		var L =  [1, difat_cnt, fat_cnt, mfat_cnt, dir_cnt, fat_size, mini_size, 0];
2058		cfb.FileIndex[0].size = mini_size << 6;
2059		L[7] = (cfb.FileIndex[0].start=L[0]+L[1]+L[2]+L[3]+L[4]+L[5])+((L[6]+7) >> 3);
2060		return L;
2061	})(cfb);
2062	var o = new_buf(L[7] << 9);
2063	var i = 0, T = 0;
2064	{
2065		for(i = 0; i < 8; ++i) o.write_shift(1, HEADER_SIG[i]);
2066		for(i = 0; i < 8; ++i) o.write_shift(2, 0);
2067		o.write_shift(2, 0x003E);
2068		o.write_shift(2, 0x0003);
2069		o.write_shift(2, 0xFFFE);
2070		o.write_shift(2, 0x0009);
2071		o.write_shift(2, 0x0006);
2072		for(i = 0; i < 3; ++i) o.write_shift(2, 0);
2073		o.write_shift(4, 0);
2074		o.write_shift(4, L[2]);
2075		o.write_shift(4, L[0] + L[1] + L[2] + L[3] - 1);
2076		o.write_shift(4, 0);
2077		o.write_shift(4, 1<<12);
2078		o.write_shift(4, L[3] ? L[0] + L[1] + L[2] - 1: ENDOFCHAIN);
2079		o.write_shift(4, L[3]);
2080		o.write_shift(-4, L[1] ? L[0] - 1: ENDOFCHAIN);
2081		o.write_shift(4, L[1]);
2082		for(i = 0; i < 109; ++i) o.write_shift(-4, i < L[2] ? L[1] + i : -1);
2083	}
2084	if(L[1]) {
2085		for(T = 0; T < L[1]; ++T) {
2086			for(; i < 236 + T * 127; ++i) o.write_shift(-4, i < L[2] ? L[1] + i : -1);
2087			o.write_shift(-4, T === L[1] - 1 ? ENDOFCHAIN : T + 1);
2088		}
2089	}
2090	var chainit = function(w/*:number*/)/*:void*/ {
2091		for(T += w; i<T-1; ++i) o.write_shift(-4, i+1);
2092		if(w) { ++i; o.write_shift(-4, ENDOFCHAIN); }
2093	};
2094	T = i = 0;
2095	for(T+=L[1]; i<T; ++i) o.write_shift(-4, consts.DIFSECT);
2096	for(T+=L[2]; i<T; ++i) o.write_shift(-4, consts.FATSECT);
2097	chainit(L[3]);
2098	chainit(L[4]);
2099	var j/*:number*/ = 0, flen/*:number*/ = 0;
2100	var file/*:CFBEntry*/ = cfb.FileIndex[0];
2101	for(; j < cfb.FileIndex.length; ++j) {
2102		file = cfb.FileIndex[j];
2103		if(!file.content) continue;
2104		/*:: if(file.content == null) throw new Error("unreachable"); */
2105		flen = file.content.length;
2106		if(flen < 0x1000) continue;
2107		file.start = T;
2108		chainit((flen + 0x01FF) >> 9);
2109	}
2110	chainit((L[6] + 7) >> 3);
2111	while(o.l & 0x1FF) o.write_shift(-4, consts.ENDOFCHAIN);
2112	T = i = 0;
2113	for(j = 0; j < cfb.FileIndex.length; ++j) {
2114		file = cfb.FileIndex[j];
2115		if(!file.content) continue;
2116		/*:: if(file.content == null) throw new Error("unreachable"); */
2117		flen = file.content.length;
2118		if(!flen || flen >= 0x1000) continue;
2119		file.start = T;
2120		chainit((flen + 0x3F) >> 6);
2121	}
2122	while(o.l & 0x1FF) o.write_shift(-4, consts.ENDOFCHAIN);
2123	for(i = 0; i < L[4]<<2; ++i) {
2124		var nm = cfb.FullPaths[i];
2125		if(!nm || nm.length === 0) {
2126			for(j = 0; j < 17; ++j) o.write_shift(4, 0);
2127			for(j = 0; j < 3; ++j) o.write_shift(4, -1);
2128			for(j = 0; j < 12; ++j) o.write_shift(4, 0);
2129			continue;
2130		}
2131		file = cfb.FileIndex[i];
2132		if(i === 0) file.start = file.size ? file.start - 1 : ENDOFCHAIN;
2133		var _nm/*:string*/ = (i === 0 && _opts.root) || file.name;
2134		if(_nm.length > 32) {
2135			console.error("Name " + _nm + " will be truncated to " + _nm.slice(0,32));
2136			_nm = _nm.slice(0, 32);
2137		}
2138		flen = 2*(_nm.length+1);
2139		o.write_shift(64, _nm, "utf16le");
2140		o.write_shift(2, flen);
2141		o.write_shift(1, file.type);
2142		o.write_shift(1, file.color);
2143		o.write_shift(-4, file.L);
2144		o.write_shift(-4, file.R);
2145		o.write_shift(-4, file.C);
2146		if(!file.clsid) for(j = 0; j < 4; ++j) o.write_shift(4, 0);
2147		else o.write_shift(16, file.clsid, "hex");
2148		o.write_shift(4, file.state || 0);
2149		o.write_shift(4, 0); o.write_shift(4, 0);
2150		o.write_shift(4, 0); o.write_shift(4, 0);
2151		o.write_shift(4, file.start);
2152		o.write_shift(4, file.size); o.write_shift(4, 0);
2153	}
2154	for(i = 1; i < cfb.FileIndex.length; ++i) {
2155		file = cfb.FileIndex[i];
2156		/*:: if(!file.content) throw new Error("unreachable"); */
2157		if(file.size >= 0x1000) {
2158			o.l = (file.start+1) << 9;
2159			if (has_buf && Buffer.isBuffer(file.content)) {
2160				file.content.copy(o, o.l, 0, file.size);
2161				// o is a 0-filled Buffer so just set next offset
2162				o.l += (file.size + 511) & -512;
2163			} else {
2164				for(j = 0; j < file.size; ++j) o.write_shift(1, file.content[j]);
2165				for(; j & 0x1FF; ++j) o.write_shift(1, 0);
2166			}
2167		}
2168	}
2169	for(i = 1; i < cfb.FileIndex.length; ++i) {
2170		file = cfb.FileIndex[i];
2171		/*:: if(!file.content) throw new Error("unreachable"); */
2172		if(file.size > 0 && file.size < 0x1000) {
2173			if (has_buf && Buffer.isBuffer(file.content)) {
2174				file.content.copy(o, o.l, 0, file.size);
2175				// o is a 0-filled Buffer so just set next offset
2176				o.l += (file.size + 63) & -64;
2177			} else {
2178				for(j = 0; j < file.size; ++j) o.write_shift(1, file.content[j]);
2179				for(; j & 0x3F; ++j) o.write_shift(1, 0);
2180			}
2181		}
2182	}
2183	if (has_buf) {
2184		o.l = o.length;
2185	} else {
2186		// When using Buffer, already 0-filled
2187		while(o.l < o.length) o.write_shift(1, 0);
2188	}
2189	return o;
2190}
2191/* [MS-CFB] 2.6.4 (Unicode 3.0.1 case conversion) */
2192function find(cfb/*:CFBContainer*/, path/*:string*/)/*:?CFBEntry*/ {
2193	var UCFullPaths/*:Array<string>*/ = cfb.FullPaths.map(function(x) { return x.toUpperCase(); });
2194	var UCPaths/*:Array<string>*/ = UCFullPaths.map(function(x) { var y = x.split("/"); return y[y.length - (x.slice(-1) == "/" ? 2 : 1)]; });
2195	var k/*:boolean*/ = false;
2196	if(path.charCodeAt(0) === 47 /* "/" */) { k = true; path = UCFullPaths[0].slice(0, -1) + path; }
2197	else k = path.indexOf("/") !== -1;
2198	var UCPath/*:string*/ = path.toUpperCase();
2199	var w/*:number*/ = k === true ? UCFullPaths.indexOf(UCPath) : UCPaths.indexOf(UCPath);
2200	if(w !== -1) return cfb.FileIndex[w];
2201
2202	var m = !UCPath.match(chr1);
2203	UCPath = UCPath.replace(chr0,'');
2204	if(m) UCPath = UCPath.replace(chr1,'!');
2205	for(w = 0; w < UCFullPaths.length; ++w) {
2206		if((m ? UCFullPaths[w].replace(chr1,'!') : UCFullPaths[w]).replace(chr0,'') == UCPath) return cfb.FileIndex[w];
2207		if((m ? UCPaths[w].replace(chr1,'!') : UCPaths[w]).replace(chr0,'') == UCPath) return cfb.FileIndex[w];
2208	}
2209	return null;
2210}
2211/** CFB Constants */
2212var MSSZ = 64; /* Mini Sector Size = 1<<6 */
2213//var MSCSZ = 4096; /* Mini Stream Cutoff Size */
2214/* 2.1 Compound File Sector Numbers and Types */
2215var ENDOFCHAIN = -2;
2216/* 2.2 Compound File Header */
2217var HEADER_SIGNATURE = 'd0cf11e0a1b11ae1';
2218var HEADER_SIG = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
2219var HEADER_CLSID = '00000000000000000000000000000000';
2220var consts = {
2221	/* 2.1 Compund File Sector Numbers and Types */
2222	MAXREGSECT: -6,
2223	DIFSECT: -4,
2224	FATSECT: -3,
2225	ENDOFCHAIN: ENDOFCHAIN,
2226	FREESECT: -1,
2227	/* 2.2 Compound File Header */
2228	HEADER_SIGNATURE: HEADER_SIGNATURE,
2229	HEADER_MINOR_VERSION: '3e00',
2230	MAXREGSID: -6,
2231	NOSTREAM: -1,
2232	HEADER_CLSID: HEADER_CLSID,
2233	/* 2.6.1 Compound File Directory Entry */
2234	EntryTypes: ['unknown','storage','stream','lockbytes','property','root']
2235};
2236
2237function write_file(cfb/*:CFBContainer*/, filename/*:string*/, options/*:CFBWriteOpts*/)/*:void*/ {
2238	get_fs();
2239	var o = _write(cfb, options);
2240	/*:: if(typeof Buffer == 'undefined' || !Buffer.isBuffer(o) || !(o instanceof Buffer)) throw new Error("unreachable"); */
2241	fs.writeFileSync(filename, o);
2242}
2243
2244function a2s(o/*:RawBytes*/)/*:string*/ {
2245	var out = new Array(o.length);
2246	for(var i = 0; i < o.length; ++i) out[i] = String.fromCharCode(o[i]);
2247	return out.join("");
2248}
2249
2250function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ {
2251	var o = _write(cfb, options);
2252	switch(options && options.type || "buffer") {
2253		case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o;
2254		case "binary": return typeof o == "string" ? o : a2s(o);
2255		case "base64": return Base64_encode(typeof o == "string" ? o : a2s(o));
2256		case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o);
2257			/* falls through */
2258		case "array": return typeof o == "string" ? s2a(o) : o;
2259	}
2260	return o;
2261}
2262/* node < 8.1 zlib does not expose bytesRead, so default to pure JS */
2263var _zlib;
2264function use_zlib(zlib) { try {
2265	var InflateRaw = zlib.InflateRaw;
2266	var InflRaw = new InflateRaw();
2267	InflRaw._processChunk(new Uint8Array([3, 0]), InflRaw._finishFlushFlag);
2268	if(InflRaw.bytesRead) _zlib = zlib;
2269	else throw new Error("zlib does not expose bytesRead");
2270} catch(e) {console.error("cannot use native zlib: " + (e.message || e)); } }
2271
2272function _inflateRawSync(payload, usz) {
2273	if(!_zlib) return _inflate(payload, usz);
2274	var InflateRaw = _zlib.InflateRaw;
2275	var InflRaw = new InflateRaw();
2276	var out = InflRaw._processChunk(payload.slice(payload.l), InflRaw._finishFlushFlag);
2277	payload.l += InflRaw.bytesRead;
2278	return out;
2279}
2280
2281function _deflateRawSync(payload) {
2282	return _zlib ? _zlib.deflateRawSync(payload) : _deflate(payload);
2283}
2284var CLEN_ORDER = [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];
2285
2286/*  LEN_ID = [ 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285 ]; */
2287var LEN_LN = [   3,   4,   5,   6,   7,   8,   9,  10,  11,  13 , 15,  17,  19,  23,  27,  31,  35,  43,  51,  59,  67,  83,  99, 115, 131, 163, 195, 227, 258 ];
2288
2289/*  DST_ID = [  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,  14,  15,  16,  17,  18,  19,   20,   21,   22,   23,   24,   25,   26,    27,    28,    29 ]; */
2290var DST_LN = [  1,  2,  3,  4,  5,  7,  9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 ];
2291
2292function bit_swap_8(n) { var t = (((((n<<1)|(n<<11)) & 0x22110) | (((n<<5)|(n<<15)) & 0x88440))); return ((t>>16) | (t>>8) |t)&0xFF; }
2293
2294var use_typed_arrays = typeof Uint8Array !== 'undefined';
2295
2296var bitswap8 = use_typed_arrays ? new Uint8Array(1<<8) : [];
2297for(var q = 0; q < (1<<8); ++q) bitswap8[q] = bit_swap_8(q);
2298
2299function bit_swap_n(n, b) {
2300	var rev = bitswap8[n & 0xFF];
2301	if(b <= 8) return rev >>> (8-b);
2302	rev = (rev << 8) | bitswap8[(n>>8)&0xFF];
2303	if(b <= 16) return rev >>> (16-b);
2304	rev = (rev << 8) | bitswap8[(n>>16)&0xFF];
2305	return rev >>> (24-b);
2306}
2307
2308/* helpers for unaligned bit reads */
2309function read_bits_2(buf, bl) { var w = (bl&7), h = (bl>>>3); return ((buf[h]|(w <= 6 ? 0 : buf[h+1]<<8))>>>w)& 0x03; }
2310function read_bits_3(buf, bl) { var w = (bl&7), h = (bl>>>3); return ((buf[h]|(w <= 5 ? 0 : buf[h+1]<<8))>>>w)& 0x07; }
2311function read_bits_4(buf, bl) { var w = (bl&7), h = (bl>>>3); return ((buf[h]|(w <= 4 ? 0 : buf[h+1]<<8))>>>w)& 0x0F; }
2312function read_bits_5(buf, bl) { var w = (bl&7), h = (bl>>>3); return ((buf[h]|(w <= 3 ? 0 : buf[h+1]<<8))>>>w)& 0x1F; }
2313function read_bits_7(buf, bl) { var w = (bl&7), h = (bl>>>3); return ((buf[h]|(w <= 1 ? 0 : buf[h+1]<<8))>>>w)& 0x7F; }
2314
2315/* works up to n = 3 * 8 + 1 = 25 */
2316function read_bits_n(buf, bl, n) {
2317	var w = (bl&7), h = (bl>>>3), f = ((1<<n)-1);
2318	var v = buf[h] >>> w;
2319	if(n < 8 - w) return v & f;
2320	v |= buf[h+1]<<(8-w);
2321	if(n < 16 - w) return v & f;
2322	v |= buf[h+2]<<(16-w);
2323	if(n < 24 - w) return v & f;
2324	v |= buf[h+3]<<(24-w);
2325	return v & f;
2326}
2327
2328/* helpers for unaligned bit writes */
2329function write_bits_3(buf, bl, v) { var w = bl & 7, h = bl >>> 3;
2330	if(w <= 5) buf[h] |= (v & 7) << w;
2331	else {
2332		buf[h] |= (v << w) & 0xFF;
2333		buf[h+1] = (v&7) >> (8-w);
2334	}
2335	return bl + 3;
2336}
2337
2338function write_bits_1(buf, bl, v) {
2339	var w = bl & 7, h = bl >>> 3;
2340	v = (v&1) << w;
2341	buf[h] |= v;
2342	return bl + 1;
2343}
2344function write_bits_8(buf, bl, v) {
2345	var w = bl & 7, h = bl >>> 3;
2346	v <<= w;
2347	buf[h] |=  v & 0xFF; v >>>= 8;
2348	buf[h+1] = v;
2349	return bl + 8;
2350}
2351function write_bits_16(buf, bl, v) {
2352	var w = bl & 7, h = bl >>> 3;
2353	v <<= w;
2354	buf[h] |=  v & 0xFF; v >>>= 8;
2355	buf[h+1] = v & 0xFF;
2356	buf[h+2] = v >>> 8;
2357	return bl + 16;
2358}
2359
2360/* until ArrayBuffer#realloc is a thing, fake a realloc */
2361function realloc(b, sz/*:number*/) {
2362	var L = b.length, M = 2*L > sz ? 2*L : sz + 5, i = 0;
2363	if(L >= sz) return b;
2364	if(has_buf) {
2365		var o = new_unsafe_buf(M);
2366		// $FlowIgnore
2367		if(b.copy) b.copy(o);
2368		else for(; i < b.length; ++i) o[i] = b[i];
2369		return o;
2370	} else if(use_typed_arrays) {
2371		var a = new Uint8Array(M);
2372		if(a.set) a.set(b);
2373		else for(; i < L; ++i) a[i] = b[i];
2374		return a;
2375	}
2376	b.length = M;
2377	return b;
2378}
2379
2380/* zero-filled arrays for older browsers */
2381function zero_fill_array(n) {
2382	var o = new Array(n);
2383	for(var i = 0; i < n; ++i) o[i] = 0;
2384	return o;
2385}
2386
2387/* build tree (used for literals and lengths) */
2388function build_tree(clens, cmap, MAX/*:number*/)/*:number*/ {
2389	var maxlen = 1, w = 0, i = 0, j = 0, ccode = 0, L = clens.length;
2390
2391	var bl_count  = use_typed_arrays ? new Uint16Array(32) : zero_fill_array(32);
2392	for(i = 0; i < 32; ++i) bl_count[i] = 0;
2393
2394	for(i = L; i < MAX; ++i) clens[i] = 0;
2395	L = clens.length;
2396
2397	var ctree = use_typed_arrays ? new Uint16Array(L) : zero_fill_array(L); // []
2398
2399	/* build code tree */
2400	for(i = 0; i < L; ++i) {
2401		bl_count[(w = clens[i])]++;
2402		if(maxlen < w) maxlen = w;
2403		ctree[i] = 0;
2404	}
2405	bl_count[0] = 0;
2406	for(i = 1; i <= maxlen; ++i) bl_count[i+16] = (ccode = (ccode + bl_count[i-1])<<1);
2407	for(i = 0; i < L; ++i) {
2408		ccode = clens[i];
2409		if(ccode != 0) ctree[i] = bl_count[ccode+16]++;
2410	}
2411
2412	/* cmap[maxlen + 4 bits] = (off&15) + (lit<<4) reverse mapping */
2413	var cleni = 0;
2414	for(i = 0; i < L; ++i) {
2415		cleni = clens[i];
2416		if(cleni != 0) {
2417			ccode = bit_swap_n(ctree[i], maxlen)>>(maxlen-cleni);
2418			for(j = (1<<(maxlen + 4 - cleni)) - 1; j>=0; --j)
2419				cmap[ccode|(j<<cleni)] = (cleni&15) | (i<<4);
2420		}
2421	}
2422	return maxlen;
2423}
2424
2425/* Fixed Huffman */
2426var fix_lmap = use_typed_arrays ? new Uint16Array(512) : zero_fill_array(512);
2427var fix_dmap = use_typed_arrays ? new Uint16Array(32)  : zero_fill_array(32);
2428if(!use_typed_arrays) {
2429	for(var i = 0; i < 512; ++i) fix_lmap[i] = 0;
2430	for(i = 0; i < 32; ++i) fix_dmap[i] = 0;
2431}
2432(function() {
2433	var dlens/*:Array<number>*/ = [];
2434	var i = 0;
2435	for(;i<32; i++) dlens.push(5);
2436	build_tree(dlens, fix_dmap, 32);
2437
2438	var clens/*:Array<number>*/ = [];
2439	i = 0;
2440	for(; i<=143; i++) clens.push(8);
2441	for(; i<=255; i++) clens.push(9);
2442	for(; i<=279; i++) clens.push(7);
2443	for(; i<=287; i++) clens.push(8);
2444	build_tree(clens, fix_lmap, 288);
2445})();var _deflateRaw = /*#__PURE__*/(function _deflateRawIIFE() {
2446	var DST_LN_RE = use_typed_arrays ? new Uint8Array(0x8000) : [];
2447	var j = 0, k = 0;
2448	for(; j < DST_LN.length - 1; ++j) {
2449		for(; k < DST_LN[j+1]; ++k) DST_LN_RE[k] = j;
2450	}
2451	for(;k < 32768; ++k) DST_LN_RE[k] = 29;
2452
2453	var LEN_LN_RE = use_typed_arrays ? new Uint8Array(0x103) : [];
2454	for(j = 0, k = 0; j < LEN_LN.length - 1; ++j) {
2455		for(; k < LEN_LN[j+1]; ++k) LEN_LN_RE[k] = j;
2456	}
2457
2458	function write_stored(data, out) {
2459		var boff = 0;
2460		while(boff < data.length) {
2461			var L = Math.min(0xFFFF, data.length - boff);
2462			var h = boff + L == data.length;
2463			out.write_shift(1, +h);
2464			out.write_shift(2, L);
2465			out.write_shift(2, (~L) & 0xFFFF);
2466			while(L-- > 0) out[out.l++] = data[boff++];
2467		}
2468		return out.l;
2469	}
2470
2471	/* Fixed Huffman */
2472	function write_huff_fixed(data, out) {
2473		var bl = 0;
2474		var boff = 0;
2475		var addrs = use_typed_arrays ? new Uint16Array(0x8000) : [];
2476		while(boff < data.length) {
2477			var L = /* data.length - boff; */ Math.min(0xFFFF, data.length - boff);
2478
2479			/* write a stored block for short data */
2480			if(L < 10) {
2481				bl = write_bits_3(out, bl, +!!(boff + L == data.length)); // jshint ignore:line
2482				if(bl & 7) bl += 8 - (bl & 7);
2483				out.l = (bl / 8) | 0;
2484				out.write_shift(2, L);
2485				out.write_shift(2, (~L) & 0xFFFF);
2486				while(L-- > 0) out[out.l++] = data[boff++];
2487				bl = out.l * 8;
2488				continue;
2489			}
2490
2491			bl = write_bits_3(out, bl, +!!(boff + L == data.length) + 2); // jshint ignore:line
2492			var hash = 0;
2493			while(L-- > 0) {
2494				var d = data[boff];
2495				hash = ((hash << 5) ^ d) & 0x7FFF;
2496
2497				var match = -1, mlen = 0;
2498
2499				if((match = addrs[hash])) {
2500					match |= boff & ~0x7FFF;
2501					if(match > boff) match -= 0x8000;
2502					if(match < boff) while(data[match + mlen] == data[boff + mlen] && mlen < 250) ++mlen;
2503				}
2504
2505				if(mlen > 2) {
2506					/* Copy Token  */
2507					d = LEN_LN_RE[mlen];
2508					if(d <= 22) bl = write_bits_8(out, bl, bitswap8[d+1]>>1) - 1;
2509					else {
2510						write_bits_8(out, bl, 3);
2511						bl += 5;
2512						write_bits_8(out, bl, bitswap8[d-23]>>5);
2513						bl += 3;
2514					}
2515					var len_eb = (d < 8) ? 0 : ((d - 4)>>2);
2516					if(len_eb > 0) {
2517						write_bits_16(out, bl, mlen - LEN_LN[d]);
2518						bl += len_eb;
2519					}
2520
2521					d = DST_LN_RE[boff - match];
2522					bl = write_bits_8(out, bl, bitswap8[d]>>3);
2523					bl -= 3;
2524
2525					var dst_eb = d < 4 ? 0 : (d-2)>>1;
2526					if(dst_eb > 0) {
2527						write_bits_16(out, bl, boff - match - DST_LN[d]);
2528						bl += dst_eb;
2529					}
2530					for(var q = 0; q < mlen; ++q) {
2531						addrs[hash] = boff & 0x7FFF;
2532						hash = ((hash << 5) ^ data[boff]) & 0x7FFF;
2533						++boff;
2534					}
2535					L-= mlen - 1;
2536				} else {
2537					/* Literal Token */
2538					if(d <= 143) d = d + 48;
2539					else bl = write_bits_1(out, bl, 1);
2540					bl = write_bits_8(out, bl, bitswap8[d]);
2541					addrs[hash] = boff & 0x7FFF;
2542					++boff;
2543				}
2544			}
2545
2546			bl = write_bits_8(out, bl, 0) - 1;
2547		}
2548		out.l = ((bl + 7)/8)|0;
2549		return out.l;
2550	}
2551	return function _deflateRaw(data, out) {
2552		if(data.length < 8) return write_stored(data, out);
2553		return write_huff_fixed(data, out);
2554	};
2555})();
2556
2557function _deflate(data) {
2558	var buf = new_buf(50+Math.floor(data.length*1.1));
2559	var off = _deflateRaw(data, buf);
2560	return buf.slice(0, off);
2561}
2562/* modified inflate function also moves original read head */
2563
2564var dyn_lmap = use_typed_arrays ? new Uint16Array(32768) : zero_fill_array(32768);
2565var dyn_dmap = use_typed_arrays ? new Uint16Array(32768) : zero_fill_array(32768);
2566var dyn_cmap = use_typed_arrays ? new Uint16Array(128)   : zero_fill_array(128);
2567var dyn_len_1 = 1, dyn_len_2 = 1;
2568
2569/* 5.5.3 Expanding Huffman Codes */
2570function dyn(data, boff/*:number*/) {
2571	/* nomenclature from RFC1951 refers to bit values; these are offset by the implicit constant */
2572	var _HLIT = read_bits_5(data, boff) + 257; boff += 5;
2573	var _HDIST = read_bits_5(data, boff) + 1; boff += 5;
2574	var _HCLEN = read_bits_4(data, boff) + 4; boff += 4;
2575	var w = 0;
2576
2577	/* grab and store code lengths */
2578	var clens = use_typed_arrays ? new Uint8Array(19) : zero_fill_array(19);
2579	var ctree = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
2580	var maxlen = 1;
2581	var bl_count =  use_typed_arrays ? new Uint8Array(8) : zero_fill_array(8);
2582	var next_code = use_typed_arrays ? new Uint8Array(8) : zero_fill_array(8);
2583	var L = clens.length; /* 19 */
2584	for(var i = 0; i < _HCLEN; ++i) {
2585		clens[CLEN_ORDER[i]] = w = read_bits_3(data, boff);
2586		if(maxlen < w) maxlen = w;
2587		bl_count[w]++;
2588		boff += 3;
2589	}
2590
2591	/* build code tree */
2592	var ccode = 0;
2593	bl_count[0] = 0;
2594	for(i = 1; i <= maxlen; ++i) next_code[i] = ccode = (ccode + bl_count[i-1])<<1;
2595	for(i = 0; i < L; ++i) if((ccode = clens[i]) != 0) ctree[i] = next_code[ccode]++;
2596	/* cmap[7 bits from stream] = (off&7) + (lit<<3) */
2597	var cleni = 0;
2598	for(i = 0; i < L; ++i) {
2599		cleni = clens[i];
2600		if(cleni != 0) {
2601			ccode = bitswap8[ctree[i]]>>(8-cleni);
2602			for(var j = (1<<(7-cleni))-1; j>=0; --j) dyn_cmap[ccode|(j<<cleni)] = (cleni&7) | (i<<3);
2603		}
2604	}
2605
2606	/* read literal and dist codes at once */
2607	var hcodes/*:Array<number>*/ = [];
2608	maxlen = 1;
2609	for(; hcodes.length < _HLIT + _HDIST;) {
2610		ccode = dyn_cmap[read_bits_7(data, boff)];
2611		boff += ccode & 7;
2612		switch((ccode >>>= 3)) {
2613			case 16:
2614				w = 3 + read_bits_2(data, boff); boff += 2;
2615				ccode = hcodes[hcodes.length - 1];
2616				while(w-- > 0) hcodes.push(ccode);
2617				break;
2618			case 17:
2619				w = 3 + read_bits_3(data, boff); boff += 3;
2620				while(w-- > 0) hcodes.push(0);
2621				break;
2622			case 18:
2623				w = 11 + read_bits_7(data, boff); boff += 7;
2624				while(w -- > 0) hcodes.push(0);
2625				break;
2626			default:
2627				hcodes.push(ccode);
2628				if(maxlen < ccode) maxlen = ccode;
2629				break;
2630		}
2631	}
2632
2633	/* build literal / length trees */
2634	var h1 = hcodes.slice(0, _HLIT), h2 = hcodes.slice(_HLIT);
2635	for(i = _HLIT; i < 286; ++i) h1[i] = 0;
2636	for(i = _HDIST; i < 30; ++i) h2[i] = 0;
2637	dyn_len_1 = build_tree(h1, dyn_lmap, 286);
2638	dyn_len_2 = build_tree(h2, dyn_dmap, 30);
2639	return boff;
2640}
2641
2642/* return [ data, bytesRead ] */
2643function inflate(data, usz/*:number*/) {
2644	/* shortcircuit for empty buffer [0x03, 0x00] */
2645	if(data[0] == 3 && !(data[1] & 0x3)) { return [new_raw_buf(usz), 2]; }
2646
2647	/* bit offset */
2648	var boff = 0;
2649
2650	/* header includes final bit and type bits */
2651	var header = 0;
2652
2653	var outbuf = new_unsafe_buf(usz ? usz : (1<<18));
2654	var woff = 0;
2655	var OL = outbuf.length>>>0;
2656	var max_len_1 = 0, max_len_2 = 0;
2657
2658	while((header&1) == 0) {
2659		header = read_bits_3(data, boff); boff += 3;
2660		if((header >>> 1) == 0) {
2661			/* Stored block */
2662			if(boff & 7) boff += 8 - (boff&7);
2663			/* 2 bytes sz, 2 bytes bit inverse */
2664			var sz = data[boff>>>3] | data[(boff>>>3)+1]<<8;
2665			boff += 32;
2666			/* push sz bytes */
2667			if(sz > 0) {
2668				if(!usz && OL < woff + sz) { outbuf = realloc(outbuf, woff + sz); OL = outbuf.length; }
2669				while(sz-- > 0) { outbuf[woff++] = data[boff>>>3]; boff += 8; }
2670			}
2671			continue;
2672		} else if((header >> 1) == 1) {
2673			/* Fixed Huffman */
2674			max_len_1 = 9; max_len_2 = 5;
2675		} else {
2676			/* Dynamic Huffman */
2677			boff = dyn(data, boff);
2678			max_len_1 = dyn_len_1; max_len_2 = dyn_len_2;
2679		}
2680		for(;;) { // while(true) is apparently out of vogue in modern JS circles
2681			if(!usz && (OL < woff + 32767)) { outbuf = realloc(outbuf, woff + 32767); OL = outbuf.length; }
2682			/* ingest code and move read head */
2683			var bits = read_bits_n(data, boff, max_len_1);
2684			var code = (header>>>1) == 1 ? fix_lmap[bits] : dyn_lmap[bits];
2685			boff += code & 15; code >>>= 4;
2686			/* 0-255 are literals, 256 is end of block token, 257+ are copy tokens */
2687			if(((code>>>8)&0xFF) === 0) outbuf[woff++] = code;
2688			else if(code == 256) break;
2689			else {
2690				code -= 257;
2691				var len_eb = (code < 8) ? 0 : ((code-4)>>2); if(len_eb > 5) len_eb = 0;
2692				var tgt = woff + LEN_LN[code];
2693				/* length extra bits */
2694				if(len_eb > 0) {
2695					tgt += read_bits_n(data, boff, len_eb);
2696					boff += len_eb;
2697				}
2698
2699				/* dist code */
2700				bits = read_bits_n(data, boff, max_len_2);
2701				code = (header>>>1) == 1 ? fix_dmap[bits] : dyn_dmap[bits];
2702				boff += code & 15; code >>>= 4;
2703				var dst_eb = (code < 4 ? 0 : (code-2)>>1);
2704				var dst = DST_LN[code];
2705				/* dist extra bits */
2706				if(dst_eb > 0) {
2707					dst += read_bits_n(data, boff, dst_eb);
2708					boff += dst_eb;
2709				}
2710
2711				/* in the common case, manual byte copy is faster than TA set / Buffer copy */
2712				if(!usz && OL < tgt) { outbuf = realloc(outbuf, tgt + 100); OL = outbuf.length; }
2713				while(woff < tgt) { outbuf[woff] = outbuf[woff - dst]; ++woff; }
2714			}
2715		}
2716	}
2717	if(usz) return [outbuf, (boff+7)>>>3];
2718	return [outbuf.slice(0, woff), (boff+7)>>>3];
2719}
2720
2721function _inflate(payload, usz) {
2722	var data = payload.slice(payload.l||0);
2723	var out = inflate(data, usz);
2724	payload.l += out[1];
2725	return out[0];
2726}
2727
2728function warn_or_throw(wrn, msg) {
2729	if(wrn) { if(typeof console !== 'undefined') console.error(msg); }
2730	else throw new Error(msg);
2731}
2732
2733function parse_zip(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ {
2734	var blob/*:CFBlob*/ = /*::(*/file/*:: :any)*/;
2735	prep_blob(blob, 0);
2736
2737	var FileIndex/*:CFBFileIndex*/ = [], FullPaths/*:Array<string>*/ = [];
2738	var o = {
2739		FileIndex: FileIndex,
2740		FullPaths: FullPaths
2741	};
2742	init_cfb(o, { root: options.root });
2743
2744	/* find end of central directory, start just after signature */
2745	var i = blob.length - 4;
2746	while((blob[i] != 0x50 || blob[i+1] != 0x4b || blob[i+2] != 0x05 || blob[i+3] != 0x06) && i >= 0) --i;
2747	blob.l = i + 4;
2748
2749	/* parse end of central directory */
2750	blob.l += 4;
2751	var fcnt = blob.read_shift(2);
2752	blob.l += 6;
2753	var start_cd = blob.read_shift(4);
2754
2755	/* parse central directory */
2756	blob.l = start_cd;
2757
2758	for(i = 0; i < fcnt; ++i) {
2759		/* trust local file header instead of CD entry */
2760		blob.l += 20;
2761		var csz = blob.read_shift(4);
2762		var usz = blob.read_shift(4);
2763		var namelen = blob.read_shift(2);
2764		var efsz = blob.read_shift(2);
2765		var fcsz = blob.read_shift(2);
2766		blob.l += 8;
2767		var offset = blob.read_shift(4);
2768		var EF = parse_extra_field(/*::(*/blob.slice(blob.l+namelen, blob.l+namelen+efsz)/*:: :any)*/);
2769		blob.l += namelen + efsz + fcsz;
2770
2771		var L = blob.l;
2772		blob.l = offset + 4;
2773		/* ZIP64 lengths */
2774		if(EF && EF[0x0001]) {
2775			if((EF[0x0001]||{}).usz) usz = EF[0x0001].usz;
2776			if((EF[0x0001]||{}).csz) csz = EF[0x0001].csz;
2777		}
2778		parse_local_file(blob, csz, usz, o, EF);
2779		blob.l = L;
2780	}
2781
2782	return o;
2783}
2784
2785
2786/* head starts just after local file header signature */
2787function parse_local_file(blob/*:CFBlob*/, csz/*:number*/, usz/*:number*/, o/*:CFBContainer*/, EF) {
2788	/* [local file header] */
2789	blob.l += 2;
2790	var flags = blob.read_shift(2);
2791	var meth = blob.read_shift(2);
2792	var date = parse_dos_date(blob);
2793
2794	if(flags & 0x2041) throw new Error("Unsupported ZIP encryption");
2795	var crc32 = blob.read_shift(4);
2796	var _csz = blob.read_shift(4);
2797	var _usz = blob.read_shift(4);
2798
2799	var namelen = blob.read_shift(2);
2800	var efsz = blob.read_shift(2);
2801
2802	// TODO: flags & (1<<11) // UTF8
2803	var name = ""; for(var i = 0; i < namelen; ++i) name += String.fromCharCode(blob[blob.l++]);
2804	if(efsz) {
2805		var ef = parse_extra_field(/*::(*/blob.slice(blob.l, blob.l + efsz)/*:: :any)*/);
2806		if((ef[0x5455]||{}).mt) date = ef[0x5455].mt;
2807		if((ef[0x0001]||{}).usz) _usz = ef[0x0001].usz;
2808		if((ef[0x0001]||{}).csz) _csz = ef[0x0001].csz;
2809		if(EF) {
2810			if((EF[0x5455]||{}).mt) date = EF[0x5455].mt;
2811			if((EF[0x0001]||{}).usz) _usz = ef[0x0001].usz;
2812			if((EF[0x0001]||{}).csz) _csz = ef[0x0001].csz;
2813		}
2814	}
2815	blob.l += efsz;
2816
2817	/* [encryption header] */
2818
2819	/* [file data] */
2820	var data = blob.slice(blob.l, blob.l + _csz);
2821	switch(meth) {
2822		case 8: data = _inflateRawSync(blob, _usz); break;
2823		case 0: break; // TODO: scan for magic number
2824		default: throw new Error("Unsupported ZIP Compression method " + meth);
2825	}
2826
2827	/* [data descriptor] */
2828	var wrn = false;
2829	if(flags & 8) {
2830		crc32 = blob.read_shift(4);
2831		if(crc32 == 0x08074b50) { crc32 = blob.read_shift(4); wrn = true; }
2832		_csz = blob.read_shift(4);
2833		_usz = blob.read_shift(4);
2834	}
2835
2836	if(_csz != csz) warn_or_throw(wrn, "Bad compressed size: " + csz + " != " + _csz);
2837	if(_usz != usz) warn_or_throw(wrn, "Bad uncompressed size: " + usz + " != " + _usz);
2838	//var _crc32 = CRC32.buf(data, 0);
2839	//if((crc32>>0) != (_crc32>>0)) warn_or_throw(wrn, "Bad CRC32 checksum: " + crc32 + " != " + _crc32);
2840	cfb_add(o, name, data, {unsafe: true, mt: date});
2841}
2842function write_zip(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes*/ {
2843	var _opts = options || {};
2844	var out = [], cdirs = [];
2845	var o/*:CFBlob*/ = new_buf(1);
2846	var method = (_opts.compression ? 8 : 0), flags = 0;
2847	var desc = false;
2848	if(desc) flags |= 8;
2849	var i = 0, j = 0;
2850
2851	var start_cd = 0, fcnt = 0;
2852	var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0];
2853	var crcs = [];
2854	var sz_cd = 0;
2855
2856	for(i = 1; i < cfb.FullPaths.length; ++i) {
2857		fp = cfb.FullPaths[i].slice(root.length); fi = cfb.FileIndex[i];
2858		if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue;
2859		var start = start_cd;
2860
2861		/* TODO: CP437 filename */
2862		var namebuf = new_buf(fp.length);
2863		for(j = 0; j < fp.length; ++j) namebuf.write_shift(1, fp.charCodeAt(j) & 0x7F);
2864		namebuf = namebuf.slice(0, namebuf.l);
2865		crcs[fcnt] = typeof fi.content == "string" ? CRC32.bstr(fi.content, 0) : CRC32.buf(/*::((*/fi.content/*::||[]):any)*/, 0);
2866
2867		var outbuf = typeof fi.content == "string" ? s2a(fi.content) : fi.content/*::||[]*/;
2868		if(method == 8) outbuf = _deflateRawSync(outbuf);
2869
2870		/* local file header */
2871		o = new_buf(30);
2872		o.write_shift(4, 0x04034b50);
2873		o.write_shift(2, 20);
2874		o.write_shift(2, flags);
2875		o.write_shift(2, method);
2876		/* TODO: last mod file time/date */
2877		if(fi.mt) write_dos_date(o, fi.mt);
2878		else o.write_shift(4, 0);
2879		o.write_shift(-4, (flags & 8) ? 0 : crcs[fcnt]);
2880		o.write_shift(4,  (flags & 8) ? 0 : outbuf.length);
2881		o.write_shift(4,  (flags & 8) ? 0 : /*::(*/fi.content/*::||[])*/.length);
2882		o.write_shift(2, namebuf.length);
2883		o.write_shift(2, 0);
2884
2885		start_cd += o.length;
2886		out.push(o);
2887		start_cd += namebuf.length;
2888		out.push(namebuf);
2889
2890		/* TODO: extra fields? */
2891
2892		/* TODO: encryption header ? */
2893
2894		start_cd += outbuf.length;
2895		out.push(outbuf);
2896
2897		/* data descriptor */
2898		if(flags & 8) {
2899			o = new_buf(12);
2900			o.write_shift(-4, crcs[fcnt]);
2901			o.write_shift(4, outbuf.length);
2902			o.write_shift(4, /*::(*/fi.content/*::||[])*/.length);
2903			start_cd += o.l;
2904			out.push(o);
2905		}
2906
2907		/* central directory */
2908		o = new_buf(46);
2909		o.write_shift(4, 0x02014b50);
2910		o.write_shift(2, 0);
2911		o.write_shift(2, 20);
2912		o.write_shift(2, flags);
2913		o.write_shift(2, method);
2914		o.write_shift(4, 0); /* TODO: last mod file time/date */
2915		o.write_shift(-4, crcs[fcnt]);
2916
2917		o.write_shift(4, outbuf.length);
2918		o.write_shift(4, /*::(*/fi.content/*::||[])*/.length);
2919		o.write_shift(2, namebuf.length);
2920		o.write_shift(2, 0);
2921		o.write_shift(2, 0);
2922		o.write_shift(2, 0);
2923		o.write_shift(2, 0);
2924		o.write_shift(4, 0);
2925		o.write_shift(4, start);
2926
2927		sz_cd += o.l;
2928		cdirs.push(o);
2929		sz_cd += namebuf.length;
2930		cdirs.push(namebuf);
2931		++fcnt;
2932	}
2933
2934	/* end of central directory */
2935	o = new_buf(22);
2936	o.write_shift(4, 0x06054b50);
2937	o.write_shift(2, 0);
2938	o.write_shift(2, 0);
2939	o.write_shift(2, fcnt);
2940	o.write_shift(2, fcnt);
2941	o.write_shift(4, sz_cd);
2942	o.write_shift(4, start_cd);
2943	o.write_shift(2, 0);
2944
2945	return bconcat(([bconcat((out/*:any*/)), bconcat(cdirs), o]/*:any*/));
2946}
2947var ContentTypeMap = ({
2948	"htm": "text/html",
2949	"xml": "text/xml",
2950
2951	"gif": "image/gif",
2952	"jpg": "image/jpeg",
2953	"png": "image/png",
2954
2955	"mso": "application/x-mso",
2956	"thmx": "application/vnd.ms-officetheme",
2957	"sh33tj5": "application/octet-stream"
2958}/*:any*/);
2959
2960function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ {
2961	if(fi.ctype) return fi.ctype;
2962
2963	var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/);
2964	if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]];
2965
2966	if(fp) {
2967		m = (ext = fp).match(/[\.\\]([^\.\\])+$/);
2968		if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]];
2969	}
2970
2971	return "application/octet-stream";
2972}
2973
2974/* 76 character chunks TODO: intertwine encoding */
2975function write_base64_76(bstr/*:string*/)/*:string*/ {
2976	var data = Base64_encode(bstr);
2977	var o = [];
2978	for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76));
2979	return o.join("\r\n") + "\r\n";
2980}
2981
2982/*
2983Rules for QP:
2984	- escape =## applies for all non-display characters and literal "="
2985	- space or tab at end of line must be encoded
2986	- \r\n newlines can be preserved, but bare \r and \n must be escaped
2987	- lines must not exceed 76 characters, use soft breaks =\r\n
2988
2989TODO: Some files from word appear to write line extensions with bare equals:
2990
2991```
2992<table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
2993="70%"
2994```
2995*/
2996function write_quoted_printable(text/*:string*/)/*:string*/ {
2997	var encoded = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF=]/g, function(c) {
2998		var w = c.charCodeAt(0).toString(16).toUpperCase();
2999		return "=" + (w.length == 1 ? "0" + w : w);
3000	});
3001
3002	encoded = encoded.replace(/ $/mg, "=20").replace(/\t$/mg, "=09");
3003
3004	if(encoded.charAt(0) == "\n") encoded = "=0D" + encoded.slice(1);
3005	encoded = encoded.replace(/\r(?!\n)/mg, "=0D").replace(/\n\n/mg, "\n=0A").replace(/([^\r\n])\n/mg, "$1=0A");
3006
3007	var o/*:Array<string>*/ = [], split = encoded.split("\r\n");
3008	for(var si = 0; si < split.length; ++si) {
3009		var str = split[si];
3010		if(str.length == 0) { o.push(""); continue; }
3011		for(var i = 0; i < str.length;) {
3012			var end = 76;
3013			var tmp = str.slice(i, i + end);
3014			if(tmp.charAt(end - 1) == "=") end --;
3015			else if(tmp.charAt(end - 2) == "=") end -= 2;
3016			else if(tmp.charAt(end - 3) == "=") end -= 3;
3017			tmp = str.slice(i, i + end);
3018			i += end;
3019			if(i < str.length) tmp += "=";
3020			o.push(tmp);
3021		}
3022	}
3023
3024	return o.join("\r\n");
3025}
3026function parse_quoted_printable(data/*:Array<string>*/)/*:RawBytes*/ {
3027	var o = [];
3028
3029	/* unify long lines */
3030	for(var di = 0; di < data.length; ++di) {
3031		var line = data[di];
3032		while(di <= data.length && line.charAt(line.length - 1) == "=") line = line.slice(0, line.length - 1) + data[++di];
3033		o.push(line);
3034	}
3035
3036	/* decode */
3037	for(var oi = 0; oi < o.length; ++oi) o[oi] = o[oi].replace(/[=][0-9A-Fa-f]{2}/g, function($$) { return String.fromCharCode(parseInt($$.slice(1), 16)); });
3038	return s2a(o.join("\r\n"));
3039}
3040
3041
3042function parse_mime(cfb/*:CFBContainer*/, data/*:Array<string>*/, root/*:string*/)/*:void*/ {
3043	var fname = "", cte = "", ctype = "", fdata;
3044	var di = 0;
3045	for(;di < 10; ++di) {
3046		var line = data[di];
3047		if(!line || line.match(/^\s*$/)) break;
3048		var m = line.match(/^(.*?):\s*([^\s].*)$/);
3049		if(m) switch(m[1].toLowerCase()) {
3050			case "content-location": fname = m[2].trim(); break;
3051			case "content-type": ctype = m[2].trim(); break;
3052			case "content-transfer-encoding": cte = m[2].trim(); break;
3053		}
3054	}
3055	++di;
3056	switch(cte.toLowerCase()) {
3057		case 'base64': fdata = s2a(Base64_decode(data.slice(di).join(""))); break;
3058		case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break;
3059		default: throw new Error("Unsupported Content-Transfer-Encoding " + cte);
3060	}
3061	var file = cfb_add(cfb, fname.slice(root.length), fdata, {unsafe: true});
3062	if(ctype) file.ctype = ctype;
3063}
3064
3065function parse_mad(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ {
3066	if(a2s(file.slice(0,13)).toLowerCase() != "mime-version:") throw new Error("Unsupported MAD header");
3067	var root = (options && options.root || "");
3068	// $FlowIgnore
3069	var data = (has_buf && Buffer.isBuffer(file) ? file.toString("binary") : a2s(file)).split("\r\n");
3070	var di = 0, row = "";
3071
3072	/* if root is not specified, scan for the common prefix */
3073	for(di = 0; di < data.length; ++di) {
3074		row = data[di];
3075		if(!/^Content-Location:/i.test(row)) continue;
3076		row = row.slice(row.indexOf("file"));
3077		if(!root) root = row.slice(0, row.lastIndexOf("/") + 1);
3078		if(row.slice(0, root.length) == root) continue;
3079		while(root.length > 0) {
3080			root = root.slice(0, root.length - 1);
3081			root = root.slice(0, root.lastIndexOf("/") + 1);
3082			if(row.slice(0,root.length) == root) break;
3083		}
3084	}
3085
3086	var mboundary = (data[1] || "").match(/boundary="(.*?)"/);
3087	if(!mboundary) throw new Error("MAD cannot find boundary");
3088	var boundary = "--" + (mboundary[1] || "");
3089
3090	var FileIndex/*:CFBFileIndex*/ = [], FullPaths/*:Array<string>*/ = [];
3091	var o = {
3092		FileIndex: FileIndex,
3093		FullPaths: FullPaths
3094	};
3095	init_cfb(o);
3096	var start_di, fcnt = 0;
3097	for(di = 0; di < data.length; ++di) {
3098		var line = data[di];
3099		if(line !== boundary && line !== boundary + "--") continue;
3100		if(fcnt++) parse_mime(o, data.slice(start_di, di), root);
3101		start_di = di;
3102	}
3103	return o;
3104}
3105
3106function write_mad(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:string*/ {
3107	var opts = options || {};
3108	var boundary = opts.boundary || "SheetJS";
3109	boundary = '------=' + boundary;
3110
3111	var out = [
3112		'MIME-Version: 1.0',
3113		'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"',
3114		'',
3115		'',
3116		''
3117	];
3118
3119	var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0];
3120	for(var i = 1; i < cfb.FullPaths.length; ++i) {
3121		fp = cfb.FullPaths[i].slice(root.length);
3122		fi = cfb.FileIndex[i];
3123		if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue;
3124
3125		/* Normalize filename */
3126		fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) {
3127			return "_x" + c.charCodeAt(0).toString(16) + "_";
3128		}).replace(/[\u0080-\uFFFF]/g, function(u) {
3129			return "_u" + u.charCodeAt(0).toString(16) + "_";
3130		});
3131
3132		/* Extract content as binary string */
3133		var ca = fi.content;
3134		// $FlowIgnore
3135		var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca);
3136
3137		/* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */
3138		var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0;
3139		for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt;
3140		var qp = dispcnt >= L * 4 / 5;
3141
3142		out.push(boundary);
3143		out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp);
3144		out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64'));
3145		out.push('Content-Type: ' + get_content_type(fi, fp));
3146		out.push('');
3147
3148		out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr));
3149	}
3150	out.push(boundary + '--\r\n');
3151	return out.join("\r\n");
3152}
3153function cfb_new(opts/*:?any*/)/*:CFBContainer*/ {
3154	var o/*:CFBContainer*/ = ({}/*:any*/);
3155	init_cfb(o, opts);
3156	return o;
3157}
3158
3159function cfb_add(cfb/*:CFBContainer*/, name/*:string*/, content/*:?RawBytes*/, opts/*:?any*/)/*:CFBEntry*/ {
3160	var unsafe = opts && opts.unsafe;
3161	if(!unsafe) init_cfb(cfb);
3162	var file = !unsafe && CFB.find(cfb, name);
3163	if(!file) {
3164		var fpath/*:string*/ = cfb.FullPaths[0];
3165		if(name.slice(0, fpath.length) == fpath) fpath = name;
3166		else {
3167			if(fpath.slice(-1) != "/") fpath += "/";
3168			fpath = (fpath + name).replace("//","/");
3169		}
3170		file = ({name: filename(name), type: 2}/*:any*/);
3171		cfb.FileIndex.push(file);
3172		cfb.FullPaths.push(fpath);
3173		if(!unsafe) CFB.utils.cfb_gc(cfb);
3174	}
3175	/*:: if(!file) throw new Error("unreachable"); */
3176	file.content = (content/*:any*/);
3177	file.size = content ? content.length : 0;
3178	if(opts) {
3179		if(opts.CLSID) file.clsid = opts.CLSID;
3180		if(opts.mt) file.mt = opts.mt;
3181		if(opts.ct) file.ct = opts.ct;
3182	}
3183	return file;
3184}
3185
3186function cfb_del(cfb/*:CFBContainer*/, name/*:string*/)/*:boolean*/ {
3187	init_cfb(cfb);
3188	var file = CFB.find(cfb, name);
3189	if(file) for(var j = 0; j < cfb.FileIndex.length; ++j) if(cfb.FileIndex[j] == file) {
3190		cfb.FileIndex.splice(j, 1);
3191		cfb.FullPaths.splice(j, 1);
3192		return true;
3193	}
3194	return false;
3195}
3196
3197function cfb_mov(cfb/*:CFBContainer*/, old_name/*:string*/, new_name/*:string*/)/*:boolean*/ {
3198	init_cfb(cfb);
3199	var file = CFB.find(cfb, old_name);
3200	if(file) for(var j = 0; j < cfb.FileIndex.length; ++j) if(cfb.FileIndex[j] == file) {
3201		cfb.FileIndex[j].name = filename(new_name);
3202		cfb.FullPaths[j] = new_name;
3203		return true;
3204	}
3205	return false;
3206}
3207
3208function cfb_gc(cfb/*:CFBContainer*/)/*:void*/ { rebuild_cfb(cfb, true); }
3209
3210exports.find = find;
3211exports.read = read;
3212exports.parse = parse;
3213exports.write = write;
3214exports.writeFile = write_file;
3215exports.utils = {
3216	cfb_new: cfb_new,
3217	cfb_add: cfb_add,
3218	cfb_del: cfb_del,
3219	cfb_mov: cfb_mov,
3220	cfb_gc: cfb_gc,
3221	ReadShift: ReadShift,
3222	CheckField: CheckField,
3223	prep_blob: prep_blob,
3224	bconcat: bconcat,
3225	use_zlib: use_zlib,
3226	_deflateRaw: _deflate,
3227	_inflateRaw: _inflate,
3228	consts: consts
3229};
3230
3231return exports;
3232})();
3233
3234var _fs;
3235function set_fs(fs) { _fs = fs; }
3236
3237/* normalize data for blob ctor */
3238function blobify(data) {
3239	if(typeof data === "string") return s2ab(data);
3240	if(Array.isArray(data)) return a2u(data);
3241	return data;
3242}
3243/* write or download file */
3244function write_dl(fname/*:string*/, payload/*:any*/, enc/*:?string*/) {
3245	/*global IE_SaveFile, Blob, navigator, saveAs, document, File, chrome */
3246	if(typeof _fs !== 'undefined' && _fs.writeFileSync) return enc ? _fs.writeFileSync(fname, payload, enc) : _fs.writeFileSync(fname, payload);
3247	if(typeof Deno !== 'undefined') {
3248		/* in this spot, it's safe to assume typed arrays and TextEncoder/TextDecoder exist */
3249		if(enc && typeof payload == "string") switch(enc) {
3250			case "utf8": payload = new TextEncoder(enc).encode(payload); break;
3251			case "binary": payload = s2ab(payload); break;
3252			/* TODO: binary equivalent */
3253			default: throw new Error("Unsupported encoding " + enc);
3254		}
3255		return Deno.writeFileSync(fname, payload);
3256	}
3257	var data = (enc == "utf8") ? utf8write(payload) : payload;
3258	/*:: declare var IE_SaveFile: any; */
3259	if(typeof IE_SaveFile !== 'undefined') return IE_SaveFile(data, fname);
3260	if(typeof Blob !== 'undefined') {
3261		var blob = new Blob([blobify(data)], {type:"application/octet-stream"});
3262		/*:: declare var navigator: any; */
3263		if(typeof navigator !== 'undefined' && navigator.msSaveBlob) return navigator.msSaveBlob(blob, fname);
3264		/*:: declare var saveAs: any; */
3265		if(typeof saveAs !== 'undefined') return saveAs(blob, fname);
3266		if(typeof URL !== 'undefined' && typeof document !== 'undefined' && document.createElement && URL.createObjectURL) {
3267			var url = URL.createObjectURL(blob);
3268			/*:: declare var chrome: any; */
3269			if(typeof chrome === 'object' && typeof (chrome.downloads||{}).download == "function") {
3270				if(URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000);
3271				return chrome.downloads.download({ url: url, filename: fname, saveAs: true});
3272			}
3273			var a = document.createElement("a");
3274			if(a.download != null) {
3275				/*:: if(document.body == null) throw new Error("unreachable"); */
3276				a.download = fname; a.href = url; document.body.appendChild(a); a.click();
3277				/*:: if(document.body == null) throw new Error("unreachable"); */ document.body.removeChild(a);
3278				if(URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000);
3279				return url;
3280			}
3281		}
3282	}
3283	// $FlowIgnore
3284	if(typeof $ !== 'undefined' && typeof File !== 'undefined' && typeof Folder !== 'undefined') try { // extendscript
3285		// $FlowIgnore
3286		var out = File(fname); out.open("w"); out.encoding = "binary";
3287		if(Array.isArray(payload)) payload = a2s(payload);
3288		out.write(payload); out.close(); return payload;
3289	} catch(e) { if(!e.message || !e.message.match(/onstruct/)) throw e; }
3290	throw new Error("cannot save file " + fname);
3291}
3292
3293/* read binary data from file */
3294function read_binary(path/*:string*/) {
3295	if(typeof _fs !== 'undefined') return _fs.readFileSync(path);
3296	if(typeof Deno !== 'undefined') return Deno.readFileSync(path);
3297	// $FlowIgnore
3298	if(typeof $ !== 'undefined' && typeof File !== 'undefined' && typeof Folder !== 'undefined') try { // extendscript
3299		// $FlowIgnore
3300		var infile = File(path); infile.open("r"); infile.encoding = "binary";
3301		var data = infile.read(); infile.close();
3302		return data;
3303	} catch(e) { if(!e.message || !e.message.match(/onstruct/)) throw e; }
3304	throw new Error("Cannot access file " + path);
3305}
3306function keys(o/*:any*/)/*:Array<any>*/ {
3307	var ks = Object.keys(o), o2 = [];
3308	for(var i = 0; i < ks.length; ++i) if(Object.prototype.hasOwnProperty.call(o, ks[i])) o2.push(ks[i]);
3309	return o2;
3310}
3311
3312function evert_key(obj/*:any*/, key/*:string*/)/*:EvertType*/ {
3313	var o = ([]/*:any*/), K = keys(obj);
3314	for(var i = 0; i !== K.length; ++i) if(o[obj[K[i]][key]] == null) o[obj[K[i]][key]] = K[i];
3315	return o;
3316}
3317
3318function evert(obj/*:any*/)/*:EvertType*/ {
3319	var o = ([]/*:any*/), K = keys(obj);
3320	for(var i = 0; i !== K.length; ++i) o[obj[K[i]]] = K[i];
3321	return o;
3322}
3323
3324function evert_num(obj/*:any*/)/*:EvertNumType*/ {
3325	var o = ([]/*:any*/), K = keys(obj);
3326	for(var i = 0; i !== K.length; ++i) o[obj[K[i]]] = parseInt(K[i],10);
3327	return o;
3328}
3329
3330function evert_arr(obj/*:any*/)/*:EvertArrType*/ {
3331	var o/*:EvertArrType*/ = ([]/*:any*/), K = keys(obj);
3332	for(var i = 0; i !== K.length; ++i) {
3333		if(o[obj[K[i]]] == null) o[obj[K[i]]] = [];
3334		o[obj[K[i]]].push(K[i]);
3335	}
3336	return o;
3337}
3338
3339var basedate = /*#__PURE__*/new Date(1899, 11, 30, 0, 0, 0); // 2209161600000
3340function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
3341	var epoch = /*#__PURE__*/v.getTime();
3342	if(date1904) epoch -= 1462*24*60*60*1000;
3343	var dnthresh = /*#__PURE__*/basedate.getTime() + (/*#__PURE__*/v.getTimezoneOffset() - /*#__PURE__*/basedate.getTimezoneOffset()) * 60000;
3344	return (epoch - dnthresh) / (24 * 60 * 60 * 1000);
3345}
3346var refdate = /*#__PURE__*/new Date();
3347var dnthresh = /*#__PURE__*/basedate.getTime() + (/*#__PURE__*/refdate.getTimezoneOffset() - /*#__PURE__*/basedate.getTimezoneOffset()) * 60000;
3348var refoffset = /*#__PURE__*/refdate.getTimezoneOffset();
3349function numdate(v/*:number*/)/*:Date*/ {
3350	var out = new Date();
3351	out.setTime(v * 24 * 60 * 60 * 1000 + dnthresh);
3352	if (out.getTimezoneOffset() !== refoffset) {
3353		out.setTime(out.getTime() + (out.getTimezoneOffset() - refoffset) * 60000);
3354	}
3355	return out;
3356}
3357
3358/* ISO 8601 Duration */
3359function parse_isodur(s) {
3360	var sec = 0, mt = 0, time = false;
3361	var m = s.match(/P([0-9\.]+Y)?([0-9\.]+M)?([0-9\.]+D)?T([0-9\.]+H)?([0-9\.]+M)?([0-9\.]+S)?/);
3362	if(!m) throw new Error("|" + s + "| is not an ISO8601 Duration");
3363	for(var i = 1; i != m.length; ++i) {
3364		if(!m[i]) continue;
3365		mt = 1;
3366		if(i > 3) time = true;
3367		switch(m[i].slice(m[i].length-1)) {
3368			case 'Y':
3369				throw new Error("Unsupported ISO Duration Field: " + m[i].slice(m[i].length-1));
3370			case 'D': mt *= 24;
3371				/* falls through */
3372			case 'H': mt *= 60;
3373				/* falls through */
3374			case 'M':
3375				if(!time) throw new Error("Unsupported ISO Duration Field: M");
3376				else mt *= 60;
3377				/* falls through */
3378			case 'S': break;
3379		}
3380		sec += mt * parseInt(m[i], 10);
3381	}
3382	return sec;
3383}
3384
3385var good_pd_date_1 = /*#__PURE__*/new Date('2017-02-19T19:06:09.000Z');
3386var good_pd_date = /*#__PURE__*/isNaN(/*#__PURE__*/good_pd_date_1.getFullYear()) ? /*#__PURE__*/new Date('2/19/17') : good_pd_date_1;
3387var good_pd = /*#__PURE__*/good_pd_date.getFullYear() == 2017;
3388/* parses a date as a local date */
3389function parseDate(str/*:string|Date*/, fixdate/*:?number*/)/*:Date*/ {
3390	var d = new Date(str);
3391	if(good_pd) {
3392		/*:: if(fixdate == null) fixdate = 0; */
3393		if(fixdate > 0) d.setTime(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
3394		else if(fixdate < 0) d.setTime(d.getTime() - d.getTimezoneOffset() * 60 * 1000);
3395		return d;
3396	}
3397	if(str instanceof Date) return str;
3398	if(good_pd_date.getFullYear() == 1917 && !isNaN(d.getFullYear())) {
3399		var s = d.getFullYear();
3400		if(str.indexOf("" + s) > -1) return d;
3401		d.setFullYear(d.getFullYear() + 100); return d;
3402	}
3403	var n = str.match(/\d+/g)||["2017","2","19","0","0","0"];
3404	var out = new Date(+n[0], +n[1] - 1, +n[2], (+n[3]||0), (+n[4]||0), (+n[5]||0));
3405	if(str.indexOf("Z") > -1) out = new Date(out.getTime() - out.getTimezoneOffset() * 60 * 1000);
3406	return out;
3407}
3408
3409function cc2str(arr/*:Array<number>*/, debomit)/*:string*/ {
3410	if(has_buf && Buffer.isBuffer(arr)) {
3411		if(debomit && buf_utf16le) {
3412			// TODO: temporary patch
3413			if(arr[0] == 0xFF && arr[1] == 0xFE) return utf8write(arr.slice(2).toString("utf16le"));
3414			if(arr[1] == 0xFE && arr[2] == 0xFF) return utf8write(utf16beread(arr.slice(2).toString("binary")));
3415		}
3416		return arr.toString("binary");
3417	}
3418
3419	if(typeof TextDecoder !== "undefined") try {
3420		if(debomit) {
3421			if(arr[0] == 0xFF && arr[1] == 0xFE) return utf8write(new TextDecoder("utf-16le").decode(arr.slice(2)));
3422			if(arr[0] == 0xFE && arr[1] == 0xFF) return utf8write(new TextDecoder("utf-16be").decode(arr.slice(2)));
3423		}
3424		var rev = {
3425			"\u20ac": "\x80", "\u201a": "\x82", "\u0192": "\x83", "\u201e": "\x84",
3426			"\u2026": "\x85", "\u2020": "\x86", "\u2021": "\x87", "\u02c6": "\x88",
3427			"\u2030": "\x89", "\u0160": "\x8a", "\u2039": "\x8b", "\u0152": "\x8c",
3428			"\u017d": "\x8e", "\u2018": "\x91", "\u2019": "\x92", "\u201c": "\x93",
3429			"\u201d": "\x94", "\u2022": "\x95", "\u2013": "\x96", "\u2014": "\x97",
3430			"\u02dc": "\x98", "\u2122": "\x99", "\u0161": "\x9a", "\u203a": "\x9b",
3431			"\u0153": "\x9c", "\u017e": "\x9e", "\u0178": "\x9f"
3432		};
3433		if(Array.isArray(arr)) arr = new Uint8Array(arr);
3434		return new TextDecoder("latin1").decode(arr).replace(/[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ]/g, function(c) { return rev[c] || c; });
3435	} catch(e) {}
3436
3437	var o = [];
3438	for(var i = 0; i != arr.length; ++i) o.push(String.fromCharCode(arr[i]));
3439	return o.join("");
3440}
3441
3442function dup(o/*:any*/)/*:any*/ {
3443	if(typeof JSON != 'undefined' && !Array.isArray(o)) return JSON.parse(JSON.stringify(o));
3444	if(typeof o != 'object' || o == null) return o;
3445	if(o instanceof Date) return new Date(o.getTime());
3446	var out = {};
3447	for(var k in o) if(Object.prototype.hasOwnProperty.call(o, k)) out[k] = dup(o[k]);
3448	return out;
3449}
3450
3451function fill(c/*:string*/,l/*:number*/)/*:string*/ { var o = ""; while(o.length < l) o+=c; return o; }
3452
3453/* TODO: stress test */
3454function fuzzynum(s/*:string*/)/*:number*/ {
3455	var v/*:number*/ = Number(s);
3456	if(!isNaN(v)) return isFinite(v) ? v : NaN;
3457	if(!/\d/.test(s)) return v;
3458	var wt = 1;
3459	var ss = s.replace(/([\d]),([\d])/g,"$1$2").replace(/[$]/g,"").replace(/[%]/g, function() { wt *= 100; return "";});
3460	if(!isNaN(v = Number(ss))) return v / wt;
3461	ss = ss.replace(/[(](.*)[)]/,function($$, $1) { wt = -wt; return $1;});
3462	if(!isNaN(v = Number(ss))) return v / wt;
3463	return v;
3464}
3465
3466/* NOTE: Chrome rejects bare times like 1:23 PM */
3467var FDRE1 = /^(0?\d|1[0-2])(?:|:([0-5]?\d)(?:|(\.\d+)(?:|:([0-5]?\d))|:([0-5]?\d)(|\.\d+)))\s+([ap])m?$/;
3468
3469function fuzzytime1(M) /*:Date*/ {
3470    /* TODO: 1904 adjustment, keep in sync with base date */
3471    if(!M[2]) return new Date(1899,11,30,(+M[1]%12) + (M[7] == "p" ? 12 : 0), 0, 0, 0);
3472    if(M[3]) {
3473        if(M[4]) return new Date(1899,11,30,(+M[1]%12) + (M[7] == "p" ? 12 : 0), +M[2], +M[4], parseFloat(M[3])*1000);
3474        else return new Date(1899,11,30,(M[7] == "p" ? 12 : 0), +M[1], +M[2], parseFloat(M[3])*1000);
3475    }
3476    else if(M[5]) return new Date(1899,11,30, (+M[1]%12) + (M[7] == "p" ? 12 : 0), +M[2], +M[5], M[6] ? parseFloat(M[6]) * 1000 : 0);
3477    else return new Date(1899,11,30,(+M[1]%12) + (M[7] == "p" ? 12 : 0), +M[2], 0, 0);
3478}
3479var lower_months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'];
3480function fuzzydate(s/*:string*/)/*:Date*/ {
3481	var lower = s.toLowerCase();
3482	var lnos = lower.replace(/\s+/g, " ").trim();
3483	var M = lnos.match(FDRE1);
3484	if(M) return fuzzytime1(M);
3485
3486	var o = new Date(s), n = new Date(NaN);
3487	var y = o.getYear(), m = o.getMonth(), d = o.getDate();
3488	if(isNaN(d)) return n;
3489	if(lower.match(/jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec/)) {
3490		lower = lower.replace(/[^a-z]/g,"").replace(/([^a-z]|^)[ap]m?([^a-z]|$)/,"");
3491		if(lower.length > 3 && lower_months.indexOf(lower) == -1) return n;
3492	} else if(lower.replace(/[ap]m?/, "").match(/[a-z]/)) return n;
3493	if(y < 0 || y > 8099 || s.match(/[^-0-9:,\/\\]/)) return n;
3494	return o;
3495}
3496
3497var split_regex = /*#__PURE__*/(function() {
3498	var safe_split_regex = "abacaba".split(/(:?b)/i).length == 5;
3499	return function split_regex(str/*:string*/, re, def/*:string*/)/*:Array<string>*/ {
3500		if(safe_split_regex || typeof re == "string") return str.split(re);
3501		var p = str.split(re), o = [p[0]];
3502		for(var i = 1; i < p.length; ++i) { o.push(def); o.push(p[i]); }
3503		return o;
3504	};
3505})();
3506function getdatastr(data)/*:?string*/ {
3507	if(!data) return null;
3508	if(data.content && data.type) return cc2str(data.content, true);
3509	if(data.data) return debom(data.data);
3510	if(data.asNodeBuffer && has_buf) return debom(data.asNodeBuffer().toString('binary'));
3511	if(data.asBinary) return debom(data.asBinary());
3512	if(data._data && data._data.getContent) return debom(cc2str(Array.prototype.slice.call(data._data.getContent(),0)));
3513	return null;
3514}
3515
3516function getdatabin(data) {
3517	if(!data) return null;
3518	if(data.data) return char_codes(data.data);
3519	if(data.asNodeBuffer && has_buf) return data.asNodeBuffer();
3520	if(data._data && data._data.getContent) {
3521		var o = data._data.getContent();
3522		if(typeof o == "string") return char_codes(o);
3523		return Array.prototype.slice.call(o);
3524	}
3525	if(data.content && data.type) return data.content;
3526	return null;
3527}
3528
3529function getdata(data) { return (data && data.name.slice(-4) === ".bin") ? getdatabin(data) : getdatastr(data); }
3530
3531/* Part 2 Section 10.1.2 "Mapping Content Types" Names are case-insensitive */
3532/* OASIS does not comment on filename case sensitivity */
3533function safegetzipfile(zip, file/*:string*/) {
3534	var k = zip.FullPaths || keys(zip.files);
3535	var f = file.toLowerCase().replace(/[\/]/g, '\\'), g = f.replace(/\\/g,'\/');
3536	for(var i=0; i<k.length; ++i) {
3537		var n = k[i].replace(/^Root Entry[\/]/,"").toLowerCase();
3538		if(f == n || g == n) return zip.files ? zip.files[k[i]] : zip.FileIndex[i];
3539	}
3540	return null;
3541}
3542
3543function getzipfile(zip, file/*:string*/) {
3544	var o = safegetzipfile(zip, file);
3545	if(o == null) throw new Error("Cannot find file " + file + " in zip");
3546	return o;
3547}
3548
3549function getzipdata(zip, file/*:string*/, safe/*:?boolean*/)/*:any*/ {
3550	if(!safe) return getdata(getzipfile(zip, file));
3551	if(!file) return null;
3552	try { return getzipdata(zip, file); } catch(e) { return null; }
3553}
3554
3555function getzipstr(zip, file/*:string*/, safe/*:?boolean*/)/*:?string*/ {
3556	if(!safe) return getdatastr(getzipfile(zip, file));
3557	if(!file) return null;
3558	try { return getzipstr(zip, file); } catch(e) { return null; }
3559}
3560
3561function getzipbin(zip, file/*:string*/, safe/*:?boolean*/)/*:any*/ {
3562	if(!safe) return getdatabin(getzipfile(zip, file));
3563	if(!file) return null;
3564	try { return getzipbin(zip, file); } catch(e) { return null; }
3565}
3566
3567function zipentries(zip) {
3568	var k = zip.FullPaths || keys(zip.files), o = [];
3569	for(var i = 0; i < k.length; ++i) if(k[i].slice(-1) != '/') o.push(k[i].replace(/^Root Entry[\/]/, ""));
3570	return o.sort();
3571}
3572
3573function zip_add_file(zip, path, content) {
3574	if(zip.FullPaths) {
3575		if(typeof content == "string") {
3576			var res;
3577			if(has_buf) res = Buffer_from(content);
3578			/* TODO: investigate performance in Edge 13 */
3579			//else if(typeof TextEncoder !== "undefined") res = new TextEncoder().encode(content);
3580			else res = utf8decode(content);
3581			return CFB.utils.cfb_add(zip, path, res);
3582		}
3583		CFB.utils.cfb_add(zip, path, content);
3584	}
3585	else zip.file(path, content);
3586}
3587
3588function zip_new() { return CFB.utils.cfb_new(); }
3589
3590function zip_read(d, o) {
3591	switch(o.type) {
3592		case "base64": return CFB.read(d, { type: "base64" });
3593		case "binary": return CFB.read(d, { type: "binary" });
3594		case "buffer": case "array": return CFB.read(d, { type: "buffer" });
3595	}
3596	throw new Error("Unrecognized type " + o.type);
3597}
3598
3599function resolve_path(path/*:string*/, base/*:string*/)/*:string*/ {
3600	if(path.charAt(0) == "/") return path.slice(1);
3601	var result = base.split('/');
3602	if(base.slice(-1) != "/") result.pop(); // folder path
3603	var target = path.split('/');
3604	while (target.length !== 0) {
3605		var step = target.shift();
3606		if (step === '..') result.pop();
3607		else if (step !== '.') result.push(step);
3608	}
3609	return result.join('/');
3610}
3611var XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n';
3612var attregexg=/([^"\s?>\/]+)\s*=\s*((?:")([^"]*)(?:")|(?:')([^']*)(?:')|([^'">\s]+))/g;
3613var tagregex1=/<[\/\?]?[a-zA-Z0-9:_-]+(?:\s+[^"\s?>\/]+\s*=\s*(?:"[^"]*"|'[^']*'|[^'">\s=]+))*\s*[\/\?]?>/mg, tagregex2 = /<[^>]*>/g;
3614var tagregex = /*#__PURE__*/XML_HEADER.match(tagregex1) ? tagregex1 : tagregex2;
3615var nsregex=/<\w*:/, nsregex2 = /<(\/?)\w+:/;
3616function parsexmltag(tag/*:string*/, skip_root/*:?boolean*/, skip_LC/*:?boolean*/)/*:any*/ {
3617	var z = ({}/*:any*/);
3618	var eq = 0, c = 0;
3619	for(; eq !== tag.length; ++eq) if((c = tag.charCodeAt(eq)) === 32 || c === 10 || c === 13) break;
3620	if(!skip_root) z[0] = tag.slice(0, eq);
3621	if(eq === tag.length) return z;
3622	var m = tag.match(attregexg), j=0, v="", i=0, q="", cc="", quot = 1;
3623	if(m) for(i = 0; i != m.length; ++i) {
3624		cc = m[i];
3625		for(c=0; c != cc.length; ++c) if(cc.charCodeAt(c) === 61) break;
3626		q = cc.slice(0,c).trim();
3627		while(cc.charCodeAt(c+1) == 32) ++c;
3628		quot = ((eq=cc.charCodeAt(c+1)) == 34 || eq == 39) ? 1 : 0;
3629		v = cc.slice(c+1+quot, cc.length-quot);
3630		for(j=0;j!=q.length;++j) if(q.charCodeAt(j) === 58) break;
3631		if(j===q.length) {
3632			if(q.indexOf("_") > 0) q = q.slice(0, q.indexOf("_")); // from ods
3633			z[q] = v;
3634			if(!skip_LC) z[q.toLowerCase()] = v;
3635		}
3636		else {
3637			var k = (j===5 && q.slice(0,5)==="xmlns"?"xmlns":"")+q.slice(j+1);
3638			if(z[k] && q.slice(j-3,j) == "ext") continue; // from ods
3639			z[k] = v;
3640			if(!skip_LC) z[k.toLowerCase()] = v;
3641		}
3642	}
3643	return z;
3644}
3645function strip_ns(x/*:string*/)/*:string*/ { return x.replace(nsregex2, "<$1"); }
3646
3647var encodings = {
3648	'&quot;': '"',
3649	'&apos;': "'",
3650	'&gt;': '>',
3651	'&lt;': '<',
3652	'&amp;': '&'
3653};
3654var rencoding = /*#__PURE__*/evert(encodings);
3655//var rencstr = "&<>'\"".split("");
3656
3657// TODO: CP remap (need to read file version to determine OS)
3658var unescapexml/*:StringConv*/ = /*#__PURE__*/(function() {
3659	/* 22.4.2.4 bstr (Basic String) */
3660	var encregex = /&(?:quot|apos|gt|lt|amp|#x?([\da-fA-F]+));/ig, coderegex = /_x([\da-fA-F]{4})_/ig;
3661	function raw_unescapexml(text/*:string*/)/*:string*/ {
3662		var s = text + '', i = s.indexOf("<![CDATA[");
3663		if(i == -1) return s.replace(encregex, function($$, $1) { return encodings[$$]||String.fromCharCode(parseInt($1,$$.indexOf("x")>-1?16:10))||$$; }).replace(coderegex,function(m,c) {return String.fromCharCode(parseInt(c,16));});
3664		var j = s.indexOf("]]>");
3665		return raw_unescapexml(s.slice(0, i)) + s.slice(i+9,j) + raw_unescapexml(s.slice(j+3));
3666	}
3667	return function unescapexml(text/*:string*/, xlsx/*:boolean*/) {
3668		var out = raw_unescapexml(text);
3669		return xlsx ? out.replace(/\r\n/g, "\n") : out;
3670	};
3671})();
3672
3673var decregex=/[&<>'"]/g, charegex = /[\u0000-\u0008\u000b-\u001f\uFFFE-\uFFFF]/g;
3674function escapexml(text/*:string*/)/*:string*/{
3675	var s = text + '';
3676	return s.replace(decregex, function(y) { return rencoding[y]; }).replace(charegex,function(s) { return "_x" + ("000"+s.charCodeAt(0).toString(16)).slice(-4) + "_";});
3677}
3678function escapexmltag(text/*:string*/)/*:string*/{ return escapexml(text).replace(/ /g,"_x0020_"); }
3679
3680var htmlcharegex = /[\u0000-\u001f]/g;
3681function escapehtml(text/*:string*/)/*:string*/{
3682	var s = text + '';
3683	return s.replace(decregex, function(y) { return rencoding[y]; }).replace(/\n/g, "<br/>").replace(htmlcharegex,function(s) { return "&#x" + ("000"+s.charCodeAt(0).toString(16)).slice(-4) + ";"; });
3684}
3685
3686function escapexlml(text/*:string*/)/*:string*/{
3687	var s = text + '';
3688	return s.replace(decregex, function(y) { return rencoding[y]; }).replace(htmlcharegex,function(s) { return "&#x" + (s.charCodeAt(0).toString(16)).toUpperCase() + ";"; });
3689}
3690
3691/* TODO: handle codepages */
3692var xlml_fixstr/*:StringConv*/ = /*#__PURE__*/(function() {
3693	var entregex = /&#(\d+);/g;
3694	function entrepl($$/*:string*/,$1/*:string*/)/*:string*/ { return String.fromCharCode(parseInt($1,10)); }
3695	return function xlml_fixstr(str/*:string*/)/*:string*/ { return str.replace(entregex,entrepl); };
3696})();
3697function xlml_unfixstr(str/*:string*/)/*:string*/ { return str.replace(/(\r\n|[\r\n])/g,"\&#10;"); }
3698
3699/* note: xsd:boolean valid values: true / 1 / false / 0 */
3700function parsexmlbool(value/*:any*/)/*:boolean*/ {
3701	switch(value) {
3702		case 1: case true:  case '1': case 'true':  return true;
3703		case 0: case false: case '0': case 'false': return false;
3704		//default: throw new Error("Invalid xsd:boolean " + value);
3705	}
3706	return false;
3707}
3708
3709function utf8reada(orig/*:string*/)/*:string*/ {
3710	var out = "", i = 0, c = 0, d = 0, e = 0, f = 0, w = 0;
3711	while (i < orig.length) {
3712		c = orig.charCodeAt(i++);
3713		if (c < 128) { out += String.fromCharCode(c); continue; }
3714		d = orig.charCodeAt(i++);
3715		if (c>191 && c<224) { f = ((c & 31) << 6); f |= (d & 63); out += String.fromCharCode(f); continue; }
3716		e = orig.charCodeAt(i++);
3717		if (c < 240) { out += String.fromCharCode(((c & 15) << 12) | ((d & 63) << 6) | (e & 63)); continue; }
3718		f = orig.charCodeAt(i++);
3719		w = (((c & 7) << 18) | ((d & 63) << 12) | ((e & 63) << 6) | (f & 63))-65536;
3720		out += String.fromCharCode(0xD800 + ((w>>>10)&1023));
3721		out += String.fromCharCode(0xDC00 + (w&1023));
3722	}
3723	return out;
3724}
3725
3726function utf8readb(data) {
3727	var out = new_raw_buf(2*data.length), w, i, j = 1, k = 0, ww=0, c;
3728	for(i = 0; i < data.length; i+=j) {
3729		j = 1;
3730		if((c=data.charCodeAt(i)) < 128) w = c;
3731		else if(c < 224) { w = (c&31)*64+(data.charCodeAt(i+1)&63); j=2; }
3732		else if(c < 240) { w=(c&15)*4096+(data.charCodeAt(i+1)&63)*64+(data.charCodeAt(i+2)&63); j=3; }
3733		else { j = 4;
3734			w = (c & 7)*262144+(data.charCodeAt(i+1)&63)*4096+(data.charCodeAt(i+2)&63)*64+(data.charCodeAt(i+3)&63);
3735			w -= 65536; ww = 0xD800 + ((w>>>10)&1023); w = 0xDC00 + (w&1023);
3736		}
3737		if(ww !== 0) { out[k++] = ww&255; out[k++] = ww>>>8; ww = 0; }
3738		out[k++] = w%256; out[k++] = w>>>8;
3739	}
3740	return out.slice(0,k).toString('ucs2');
3741}
3742
3743function utf8readc(data) { return Buffer_from(data, 'binary').toString('utf8'); }
3744
3745var utf8corpus = "foo bar baz\u00e2\u0098\u0083\u00f0\u009f\u008d\u00a3";
3746var utf8read = has_buf && (/*#__PURE__*/utf8readc(utf8corpus) == /*#__PURE__*/utf8reada(utf8corpus) && utf8readc || /*#__PURE__*/utf8readb(utf8corpus) == /*#__PURE__*/utf8reada(utf8corpus) && utf8readb) || utf8reada;
3747
3748var utf8write/*:StringConv*/ = has_buf ? function(data) { return Buffer_from(data, 'utf8').toString("binary"); } : function(orig/*:string*/)/*:string*/ {
3749	var out/*:Array<string>*/ = [], i = 0, c = 0, d = 0;
3750	while(i < orig.length) {
3751		c = orig.charCodeAt(i++);
3752		switch(true) {
3753			case c < 128: out.push(String.fromCharCode(c)); break;
3754			case c < 2048:
3755				out.push(String.fromCharCode(192 + (c >> 6)));
3756				out.push(String.fromCharCode(128 + (c & 63)));
3757				break;
3758			case c >= 55296 && c < 57344:
3759				c -= 55296; d = orig.charCodeAt(i++) - 56320 + (c<<10);
3760				out.push(String.fromCharCode(240 + ((d >>18) & 7)));
3761				out.push(String.fromCharCode(144 + ((d >>12) & 63)));
3762				out.push(String.fromCharCode(128 + ((d >> 6) & 63)));
3763				out.push(String.fromCharCode(128 + (d & 63)));
3764				break;
3765			default:
3766				out.push(String.fromCharCode(224 + (c >> 12)));
3767				out.push(String.fromCharCode(128 + ((c >> 6) & 63)));
3768				out.push(String.fromCharCode(128 + (c & 63)));
3769		}
3770	}
3771	return out.join("");
3772};
3773
3774// matches <foo>...</foo> extracts content
3775var matchtag = /*#__PURE__*/(function() {
3776	var mtcache/*:{[k:string]:RegExp}*/ = ({}/*:any*/);
3777	return function matchtag(f/*:string*/,g/*:?string*/)/*:RegExp*/ {
3778		var t = f+"|"+(g||"");
3779		if(mtcache[t]) return mtcache[t];
3780		return (mtcache[t] = new RegExp('<(?:\\w+:)?'+f+'(?: xml:space="preserve")?(?:[^>]*)>([\\s\\S]*?)</(?:\\w+:)?'+f+'>',((g||"")/*:any*/)));
3781	};
3782})();
3783
3784var htmldecode/*:{(s:string):string}*/ = /*#__PURE__*/(function() {
3785	var entities/*:Array<[RegExp, string]>*/ = [
3786		['nbsp', ' '], ['middot', '·'],
3787		['quot', '"'], ['apos', "'"], ['gt',   '>'], ['lt',   '<'], ['amp',  '&']
3788	].map(function(x/*:[string, string]*/) { return [new RegExp('&' + x[0] + ';', "ig"), x[1]]; });
3789	return function htmldecode(str/*:string*/)/*:string*/ {
3790		var o = str
3791				// Remove new lines and spaces from start of content
3792				.replace(/^[\t\n\r ]+/, "")
3793				// Remove new lines and spaces from end of content
3794				.replace(/[\t\n\r ]+$/,"")
3795				// Added line which removes any white space characters after and before html tags
3796				.replace(/>\s+/g,">").replace(/\s+</g,"<")
3797				// Replace remaining new lines and spaces with space
3798				.replace(/[\t\n\r ]+/g, " ")
3799				// Replace <br> tags with new lines
3800				.replace(/<\s*[bB][rR]\s*\/?>/g,"\n")
3801				// Strip HTML elements
3802				.replace(/<[^>]*>/g,"");
3803		for(var i = 0; i < entities.length; ++i) o = o.replace(entities[i][0], entities[i][1]);
3804		return o;
3805	};
3806})();
3807
3808var vtregex = /*#__PURE__*/(function(){ var vt_cache = {};
3809	return function vt_regex(bt) {
3810		if(vt_cache[bt] !== undefined) return vt_cache[bt];
3811		return (vt_cache[bt] = new RegExp("<(?:vt:)?" + bt + ">([\\s\\S]*?)</(?:vt:)?" + bt + ">", 'g') );
3812};})();
3813var vtvregex = /<\/?(?:vt:)?variant>/g, vtmregex = /<(?:vt:)([^>]*)>([\s\S]*)</;
3814function parseVector(data/*:string*/, opts)/*:Array<{v:string,t:string}>*/ {
3815	var h = parsexmltag(data);
3816
3817	var matches/*:Array<string>*/ = data.match(vtregex(h.baseType))||[];
3818	var res/*:Array<any>*/ = [];
3819	if(matches.length != h.size) {
3820		if(opts.WTF) throw new Error("unexpected vector length " + matches.length + " != " + h.size);
3821		return res;
3822	}
3823	matches.forEach(function(x/*:string*/) {
3824		var v = x.replace(vtvregex,"").match(vtmregex);
3825		if(v) res.push({v:utf8read(v[2]), t:v[1]});
3826	});
3827	return res;
3828}
3829
3830var wtregex = /(^\s|\s$|\n)/;
3831function writetag(f/*:string*/,g/*:string*/)/*:string*/ { return '<' + f + (g.match(wtregex)?' xml:space="preserve"' : "") + '>' + g + '</' + f + '>'; }
3832
3833function wxt_helper(h)/*:string*/ { return keys(h).map(function(k) { return " " + k + '="' + h[k] + '"';}).join(""); }
3834function writextag(f/*:string*/,g/*:?string*/,h) { return '<' + f + ((h != null) ? wxt_helper(h) : "") + ((g != null) ? (g.match(wtregex)?' xml:space="preserve"' : "") + '>' + g + '</' + f : "/") + '>';}
3835
3836function write_w3cdtf(d/*:Date*/, t/*:?boolean*/)/*:string*/ { try { return d.toISOString().replace(/\.\d*/,""); } catch(e) { if(t) throw e; } return ""; }
3837
3838function write_vt(s, xlsx/*:?boolean*/)/*:string*/ {
3839	switch(typeof s) {
3840		case 'string':
3841			var o = writextag('vt:lpwstr', escapexml(s));
3842			if(xlsx) o = o.replace(/&quot;/g, "_x0022_");
3843			return o;
3844		case 'number': return writextag((s|0)==s?'vt:i4':'vt:r8', escapexml(String(s)));
3845		case 'boolean': return writextag('vt:bool',s?'true':'false');
3846	}
3847	if(s instanceof Date) return writextag('vt:filetime', write_w3cdtf(s));
3848	throw new Error("Unable to serialize " + s);
3849}
3850
3851function xlml_normalize(d)/*:string*/ {
3852	if(has_buf &&/*::typeof Buffer !== "undefined" && d != null && d instanceof Buffer &&*/ Buffer.isBuffer(d)) return d.toString('utf8');
3853	if(typeof d === 'string') return d;
3854	/* duktape */
3855	if(typeof Uint8Array !== 'undefined' && d instanceof Uint8Array) return utf8read(a2s(ab2a(d)));
3856	throw new Error("Bad input format: expected Buffer or string");
3857}
3858/* UOS uses CJK in tags */
3859var xlmlregex = /<(\/?)([^\s?><!\/:]*:|)([^\s?<>:\/]+)(?:[\s?:\/](?:[^>=]|="[^"]*?")*)?>/mg;
3860//var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg;
3861
3862var XMLNS = ({
3863	CORE_PROPS: 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
3864	CUST_PROPS: "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties",
3865	EXT_PROPS: "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
3866	CT: 'http://schemas.openxmlformats.org/package/2006/content-types',
3867	RELS: 'http://schemas.openxmlformats.org/package/2006/relationships',
3868	TCMNT: 'http://schemas.microsoft.com/office/spreadsheetml/2018/threadedcomments',
3869	'dc': 'http://purl.org/dc/elements/1.1/',
3870	'dcterms': 'http://purl.org/dc/terms/',
3871	'dcmitype': 'http://purl.org/dc/dcmitype/',
3872	'mx': 'http://schemas.microsoft.com/office/mac/excel/2008/main',
3873	'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
3874	'sjs': 'http://schemas.openxmlformats.org/package/2006/sheetjs/core-properties',
3875	'vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes',
3876	'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
3877	'xsd': 'http://www.w3.org/2001/XMLSchema'
3878}/*:any*/);
3879
3880var XMLNS_main = [
3881	'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
3882	'http://purl.oclc.org/ooxml/spreadsheetml/main',
3883	'http://schemas.microsoft.com/office/excel/2006/main',
3884	'http://schemas.microsoft.com/office/excel/2006/2'
3885];
3886
3887var XLMLNS = ({
3888	'o':    'urn:schemas-microsoft-com:office:office',
3889	'x':    'urn:schemas-microsoft-com:office:excel',
3890	'ss':   'urn:schemas-microsoft-com:office:spreadsheet',
3891	'dt':   'uuid:C2F41010-65B3-11d1-A29F-00AA00C14882',
3892	'mv':   'http://macVmlSchemaUri',
3893	'v':    'urn:schemas-microsoft-com:vml',
3894	'html': 'http://www.w3.org/TR/REC-html40'
3895}/*:any*/);
3896function read_double_le(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ {
3897	var s = 1 - 2 * (b[idx + 7] >>> 7);
3898	var e = ((b[idx + 7] & 0x7f) << 4) + ((b[idx + 6] >>> 4) & 0x0f);
3899	var m = (b[idx+6]&0x0f);
3900	for(var i = 5; i >= 0; --i) m = m * 256 + b[idx + i];
3901	if(e == 0x7ff) return m == 0 ? (s * Infinity) : NaN;
3902	if(e == 0) e = -1022;
3903	else { e -= 1023; m += Math.pow(2,52); }
3904	return s * Math.pow(2, e - 52) * m;
3905}
3906
3907function write_double_le(b/*:RawBytes|CFBlob*/, v/*:number*/, idx/*:number*/) {
3908	var bs = ((((v < 0) || (1/v == -Infinity)) ? 1 : 0) << 7), e = 0, m = 0;
3909	var av = bs ? (-v) : v;
3910	if(!isFinite(av)) { e = 0x7ff; m = isNaN(v) ? 0x6969 : 0; }
3911	else if(av == 0) e = m = 0;
3912	else {
3913		e = Math.floor(Math.log(av) / Math.LN2);
3914		m = av * Math.pow(2, 52 - e);
3915		if((e <= -1023) && (!isFinite(m) || (m < Math.pow(2,52)))) { e = -1022; }
3916		else { m -= Math.pow(2,52); e+=1023; }
3917	}
3918	for(var i = 0; i <= 5; ++i, m/=256) b[idx + i] = m & 0xff;
3919	b[idx + 6] = ((e & 0x0f) << 4) | (m & 0xf);
3920	b[idx + 7] = (e >> 4) | bs;
3921}
3922
3923var ___toBuffer = function(bufs/*:Array<Array<RawBytes> >*/)/*:RawBytes*/ { var x=[],w=10240; for(var i=0;i<bufs[0].length;++i) if(bufs[0][i]) for(var j=0,L=bufs[0][i].length;j<L;j+=w) x.push.apply(x, bufs[0][i].slice(j,j+w)); return x; };
3924var __toBuffer = has_buf ? function(bufs) { return (bufs[0].length > 0 && Buffer.isBuffer(bufs[0][0])) ? Buffer.concat(bufs[0].map(function(x) { return Buffer.isBuffer(x) ? x : Buffer_from(x); })) : ___toBuffer(bufs);} : ___toBuffer;
3925
3926var ___utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/)/*:string*/ { var ss/*:Array<string>*/=[]; for(var i=s; i<e; i+=2) ss.push(String.fromCharCode(__readUInt16LE(b,i))); return ss.join("").replace(chr0,''); };
3927var __utf16le = has_buf ? function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/)/*:string*/ { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/ || !buf_utf16le) return ___utf16le(b,s,e); return b.toString('utf16le',s,e).replace(chr0,'')/*.replace(chr1,'!')*/; } : ___utf16le;
3928
3929var ___hexlify = function(b/*:RawBytes|CFBlob*/,s/*:number*/,l/*:number*/)/*:string*/ { var ss/*:Array<string>*/=[]; for(var i=s; i<s+l; ++i) ss.push(("0" + b[i].toString(16)).slice(-2)); return ss.join(""); };
3930var __hexlify = has_buf ? function(b/*:RawBytes|CFBlob*/,s/*:number*/,l/*:number*/)/*:string*/ { return Buffer.isBuffer(b)/*:: && b instanceof Buffer*/ ? b.toString('hex',s,s+l) : ___hexlify(b,s,l); } : ___hexlify;
3931
3932var ___utf8 = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { var ss=[]; for(var i=s; i<e; i++) ss.push(String.fromCharCode(__readUInt8(b,i))); return ss.join(""); };
3933var __utf8 = has_buf ? function utf8_b(b/*:RawBytes|CFBlob*/, s/*:number*/, e/*:number*/) { return (Buffer.isBuffer(b)/*:: && (b instanceof Buffer)*/) ? b.toString('utf8',s,e) : ___utf8(b,s,e); } : ___utf8;
3934
3935var ___lpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : "";};
3936var __lpstr = ___lpstr;
3937
3938var ___cpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : "";};
3939var __cpstr = ___cpstr;
3940
3941var ___lpwstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = 2*__readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : "";};
3942var __lpwstr = ___lpwstr;
3943
3944var ___lpp4 = function lpp4_(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? __utf16le(b, i+4,i+4+len) : "";};
3945var __lpp4 = ___lpp4;
3946
3947var ___8lpp4 = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len) : "";};
3948var __8lpp4 = ___8lpp4;
3949
3950var ___double = function(b/*:RawBytes|CFBlob*/, idx/*:number*/) { return read_double_le(b, idx);};
3951var __double = ___double;
3952
3953var is_buf = function is_buf_a(a) { return Array.isArray(a) || (typeof Uint8Array !== "undefined" && a instanceof Uint8Array); };
3954
3955if(has_buf/*:: && typeof Buffer !== 'undefined'*/) {
3956	__lpstr = function lpstr_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___lpstr(b, i); var len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8',i+4,i+4+len-1) : "";};
3957	__cpstr = function cpstr_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___cpstr(b, i); var len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8',i+4,i+4+len-1) : "";};
3958	__lpwstr = function lpwstr_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/ || !buf_utf16le) return ___lpwstr(b, i); var len = 2*b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len-1);};
3959	__lpp4 = function lpp4_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/ || !buf_utf16le) return ___lpp4(b, i); var len = b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len);};
3960	__8lpp4 = function lpp4_8b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___8lpp4(b, i); var len = b.readUInt32LE(i); return b.toString('utf8',i+4,i+4+len);};
3961	__double = function double_(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(Buffer.isBuffer(b)/*::&& b instanceof Buffer*/) return b.readDoubleLE(i); return ___double(b,i); };
3962	is_buf = function is_buf_b(a) { return Buffer.isBuffer(a) || Array.isArray(a) || (typeof Uint8Array !== "undefined" && a instanceof Uint8Array); };
3963}
3964
3965/* from js-xls */
3966function cpdoit() {
3967	__utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return $cptable.utils.decode(1200, b.slice(s,e)).replace(chr0, ''); };
3968	__utf8 = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return $cptable.utils.decode(65001, b.slice(s,e)); };
3969	__lpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(current_ansi, b.slice(i+4, i+4+len-1)) : "";};
3970	__cpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(current_codepage, b.slice(i+4, i+4+len-1)) : "";};
3971	__lpwstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = 2*__readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(1200, b.slice(i+4,i+4+len-1)) : "";};
3972	__lpp4 = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(1200, b.slice(i+4,i+4+len)) : "";};
3973	__8lpp4 = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(65001, b.slice(i+4,i+4+len)) : "";};
3974}
3975if(typeof $cptable !== 'undefined') cpdoit();
3976
3977var __readUInt8 = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return b[idx]; };
3978var __readUInt16LE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return (b[idx+1]*(1<<8))+b[idx]; };
3979var __readInt16LE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { var u = (b[idx+1]*(1<<8))+b[idx]; return (u < 0x8000) ? u : ((0xffff - u + 1) * -1); };
3980var __readUInt32LE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return b[idx+3]*(1<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; };
3981var __readInt32LE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return (b[idx+3]<<24)|(b[idx+2]<<16)|(b[idx+1]<<8)|b[idx]; };
3982var __readInt32BE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return (b[idx]<<24)|(b[idx+1]<<16)|(b[idx+2]<<8)|b[idx+3]; };
3983
3984function ReadShift(size/*:number*/, t/*:?string*/)/*:number|string*/ {
3985	var o="", oI/*:: :number = 0*/, oR, oo=[], w, vv, i, loc;
3986	switch(t) {
3987		case 'dbcs':
3988			loc = this.l;
3989			if(has_buf && Buffer.isBuffer(this)  && buf_utf16le) o = this.slice(this.l, this.l+2*size).toString("utf16le");
3990			else for(i = 0; i < size; ++i) { o+=String.fromCharCode(__readUInt16LE(this, loc)); loc+=2; }
3991			size *= 2;
3992			break;
3993
3994		case 'utf8': o = __utf8(this, this.l, this.l + size); break;
3995		case 'utf16le': size *= 2; o = __utf16le(this, this.l, this.l + size); break;
3996
3997		case 'wstr':
3998			if(typeof $cptable !== 'undefined') o = $cptable.utils.decode(current_codepage, this.slice(this.l, this.l+2*size));
3999			else return ReadShift.call(this, size, 'dbcs');
4000			size = 2 * size; break;
4001
4002		/* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */
4003		case 'lpstr-ansi': o = __lpstr(this, this.l); size = 4 + __readUInt32LE(this, this.l); break;
4004		case 'lpstr-cp': o = __cpstr(this, this.l); size = 4 + __readUInt32LE(this, this.l); break;
4005		/* [MS-OLEDS] 2.1.5 LengthPrefixedUnicodeString */
4006		case 'lpwstr': o = __lpwstr(this, this.l); size = 4 + 2 * __readUInt32LE(this, this.l); break;
4007		/* [MS-OFFCRYPTO] 2.1.2 Length-Prefixed Padded Unicode String (UNICODE-LP-P4) */
4008		case 'lpp4': size = 4 +  __readUInt32LE(this, this.l); o = __lpp4(this, this.l); if(size & 0x02) size += 2; break;
4009		/* [MS-OFFCRYPTO] 2.1.3 Length-Prefixed UTF-8 String (UTF-8-LP-P4) */
4010		case '8lpp4': size = 4 +  __readUInt32LE(this, this.l); o = __8lpp4(this, this.l); if(size & 0x03) size += 4 - (size & 0x03); break;
4011
4012		case 'cstr': size = 0; o = "";
4013			while((w=__readUInt8(this, this.l + size++))!==0) oo.push(_getchar(w));
4014			o = oo.join(""); break;
4015		case '_wstr': size = 0; o = "";
4016			while((w=__readUInt16LE(this,this.l +size))!==0){oo.push(_getchar(w));size+=2;}
4017			size+=2; o = oo.join(""); break;
4018
4019		/* sbcs and dbcs support continue records in the SST way TODO codepages */
4020		case 'dbcs-cont': o = ""; loc = this.l;
4021			for(i = 0; i < size; ++i) {
4022				if(this.lens && this.lens.indexOf(loc) !== -1) {
4023					w = __readUInt8(this, loc);
4024					this.l = loc + 1;
4025					vv = ReadShift.call(this, size-i, w ? 'dbcs-cont' : 'sbcs-cont');
4026					return oo.join("") + vv;
4027				}
4028				oo.push(_getchar(__readUInt16LE(this, loc)));
4029				loc+=2;
4030			} o = oo.join(""); size *= 2; break;
4031
4032		case 'cpstr':
4033			if(typeof $cptable !== 'undefined') {
4034				o = $cptable.utils.decode(current_codepage, this.slice(this.l, this.l + size));
4035				break;
4036			}
4037		/* falls through */
4038		case 'sbcs-cont': o = ""; loc = this.l;
4039			for(i = 0; i != size; ++i) {
4040				if(this.lens && this.lens.indexOf(loc) !== -1) {
4041					w = __readUInt8(this, loc);
4042					this.l = loc + 1;
4043					vv = ReadShift.call(this, size-i, w ? 'dbcs-cont' : 'sbcs-cont');
4044					return oo.join("") + vv;
4045				}
4046				oo.push(_getchar(__readUInt8(this, loc)));
4047				loc+=1;
4048			} o = oo.join(""); break;
4049
4050		default:
4051	switch(size) {
4052		case 1: oI = __readUInt8(this, this.l); this.l++; return oI;
4053		case 2: oI = (t === 'i' ? __readInt16LE : __readUInt16LE)(this, this.l); this.l += 2; return oI;
4054		case 4: case -4:
4055			if(t === 'i' || ((this[this.l+3] & 0x80)===0)) { oI = ((size > 0) ? __readInt32LE : __readInt32BE)(this, this.l); this.l += 4; return oI; }
4056			else { oR = __readUInt32LE(this, this.l); this.l += 4; } return oR;
4057		case 8: case -8:
4058			if(t === 'f') {
4059				if(size == 8) oR = __double(this, this.l);
4060				else oR = __double([this[this.l+7],this[this.l+6],this[this.l+5],this[this.l+4],this[this.l+3],this[this.l+2],this[this.l+1],this[this.l+0]], 0);
4061				this.l += 8; return oR;
4062			} else size = 8;
4063		/* falls through */
4064		case 16: o = __hexlify(this, this.l, size); break;
4065	}}
4066	this.l+=size; return o;
4067}
4068
4069var __writeUInt32LE = function(b/*:RawBytes|CFBlob*/, val/*:number*/, idx/*:number*/)/*:void*/ { b[idx] = (val & 0xFF); b[idx+1] = ((val >>> 8) & 0xFF); b[idx+2] = ((val >>> 16) & 0xFF); b[idx+3] = ((val >>> 24) & 0xFF); };
4070var __writeInt32LE  = function(b/*:RawBytes|CFBlob*/, val/*:number*/, idx/*:number*/)/*:void*/ { b[idx] = (val & 0xFF); b[idx+1] = ((val >> 8) & 0xFF); b[idx+2] = ((val >> 16) & 0xFF); b[idx+3] = ((val >> 24) & 0xFF); };
4071var __writeUInt16LE = function(b/*:RawBytes|CFBlob*/, val/*:number*/, idx/*:number*/)/*:void*/ { b[idx] = (val & 0xFF); b[idx+1] = ((val >>> 8) & 0xFF); };
4072
4073function WriteShift(t/*:number*/, val/*:string|number*/, f/*:?string*/)/*:any*/ {
4074	var size = 0, i = 0;
4075	if(f === 'dbcs') {
4076		/*:: if(typeof val !== 'string') throw new Error("unreachable"); */
4077		for(i = 0; i != val.length; ++i) __writeUInt16LE(this, val.charCodeAt(i), this.l + 2 * i);
4078		size = 2 * val.length;
4079	} else if(f === 'sbcs' || f == 'cpstr') {
4080		if(typeof $cptable !== 'undefined' && current_ansi == 874) {
4081			/* TODO: use tables directly, don't encode */
4082			/*:: if(typeof val !== "string") throw new Error("unreachable"); */
4083			for(i = 0; i != val.length; ++i) {
4084				var cpp = $cptable.utils.encode(current_ansi, val.charAt(i));
4085				this[this.l + i] = cpp[0];
4086			}
4087			size = val.length;
4088		} else if(typeof $cptable !== 'undefined' && f == 'cpstr') {
4089			cpp = $cptable.utils.encode(current_codepage, val);
4090			/* replace null bytes with _ when relevant */
4091      if(cpp.length == val.length) for(i = 0; i < val.length; ++i) if(cpp[i] == 0 && val.charCodeAt(i) != 0) cpp[i] = 0x5F;
4092      if(cpp.length == 2 * val.length) for(i = 0; i < val.length; ++i) if(cpp[2*i] == 0 && cpp[2*i+1] == 0 && val.charCodeAt(i) != 0) cpp[2*i] = 0x5F;
4093			for(i = 0; i < cpp.length; ++i) this[this.l + i] = cpp[i];
4094			size = cpp.length;
4095		} else {
4096			/*:: if(typeof val !== 'string') throw new Error("unreachable"); */
4097			val = val.replace(/[^\x00-\x7F]/g, "_");
4098			/*:: if(typeof val !== 'string') throw new Error("unreachable"); */
4099			for(i = 0; i != val.length; ++i) this[this.l + i] = (val.charCodeAt(i) & 0xFF);
4100			size = val.length;
4101		}
4102	} else if(f === 'hex') {
4103		for(; i < t; ++i) {
4104			/*:: if(typeof val !== "string") throw new Error("unreachable"); */
4105			this[this.l++] = (parseInt(val.slice(2*i, 2*i+2), 16)||0);
4106		} return this;
4107	} else if(f === 'utf16le') {
4108			/*:: if(typeof val !== "string") throw new Error("unreachable"); */
4109			var end/*:number*/ = Math.min(this.l + t, this.length);
4110			for(i = 0; i < Math.min(val.length, t); ++i) {
4111				var cc = val.charCodeAt(i);
4112				this[this.l++] = (cc & 0xff);
4113				this[this.l++] = (cc >> 8);
4114			}
4115			while(this.l < end) this[this.l++] = 0;
4116			return this;
4117	} else /*:: if(typeof val === 'number') */ switch(t) {
4118		case  1: size = 1; this[this.l] = val&0xFF; break;
4119		case  2: size = 2; this[this.l] = val&0xFF; val >>>= 8; this[this.l+1] = val&0xFF; break;
4120		case  3: size = 3; this[this.l] = val&0xFF; val >>>= 8; this[this.l+1] = val&0xFF; val >>>= 8; this[this.l+2] = val&0xFF; break;
4121		case  4: size = 4; __writeUInt32LE(this, val, this.l); break;
4122		case  8: size = 8; if(f === 'f') { write_double_le(this, val, this.l); break; }
4123		/* falls through */
4124		case 16: break;
4125		case -4: size = 4; __writeInt32LE(this, val, this.l); break;
4126	}
4127	this.l += size; return this;
4128}
4129
4130function CheckField(hexstr/*:string*/, fld/*:string*/)/*:void*/ {
4131	var m = __hexlify(this,this.l,hexstr.length>>1);
4132	if(m !== hexstr) throw new Error(fld + 'Expected ' + hexstr + ' saw ' + m);
4133	this.l += hexstr.length>>1;
4134}
4135
4136function prep_blob(blob, pos/*:number*/)/*:void*/ {
4137	blob.l = pos;
4138	blob.read_shift = /*::(*/ReadShift/*:: :any)*/;
4139	blob.chk = CheckField;
4140	blob.write_shift = WriteShift;
4141}
4142
4143function parsenoop(blob, length/*:: :number, opts?:any */) { blob.l += length; }
4144
4145function new_buf(sz/*:number*/)/*:Block*/ {
4146	var o = new_raw_buf(sz);
4147	prep_blob(o, 0);
4148	return o;
4149}
4150
4151/* [MS-XLSB] 2.1.4 Record */
4152function recordhopper(data, cb/*:RecordHopperCB*/, opts/*:?any*/) {
4153	if(!data) return;
4154	var tmpbyte, cntbyte, length;
4155	prep_blob(data, data.l || 0);
4156	var L = data.length, RT = 0, tgt = 0;
4157	while(data.l < L) {
4158		RT = data.read_shift(1);
4159		if(RT & 0x80) RT = (RT & 0x7F) + ((data.read_shift(1) & 0x7F)<<7);
4160		var R = XLSBRecordEnum[RT] || XLSBRecordEnum[0xFFFF];
4161		tmpbyte = data.read_shift(1);
4162		length = tmpbyte & 0x7F;
4163		for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte);
4164		tgt = data.l + length;
4165		var d = R.f && R.f(data, length, opts);
4166		data.l = tgt;
4167		if(cb(d, R, RT)) return;
4168	}
4169}
4170
4171/* control buffer usage for fixed-length buffers */
4172function buf_array()/*:BufArray*/ {
4173	var bufs/*:Array<Block>*/ = [], blksz = has_buf ? 256 : 2048;
4174	var newblk = function ba_newblk(sz/*:number*/)/*:Block*/ {
4175		var o/*:Block*/ = (new_buf(sz)/*:any*/);
4176		prep_blob(o, 0);
4177		return o;
4178	};
4179
4180	var curbuf/*:Block*/ = newblk(blksz);
4181
4182	var endbuf = function ba_endbuf() {
4183		if(!curbuf) return;
4184		// workaround for new Buffer(3).slice(0,0) bug in bun 0.1.3
4185		if(curbuf.l) {
4186			if(curbuf.length > curbuf.l) { curbuf = curbuf.slice(0, curbuf.l); curbuf.l = curbuf.length; }
4187			if(curbuf.length > 0) bufs.push(curbuf);
4188		}
4189		curbuf = null;
4190	};
4191
4192	var next = function ba_next(sz/*:number*/)/*:Block*/ {
4193		if(curbuf && (sz < (curbuf.length - curbuf.l))) return curbuf;
4194		endbuf();
4195		return (curbuf = newblk(Math.max(sz+1, blksz)));
4196	};
4197
4198	var end = function ba_end() {
4199		endbuf();
4200		return bconcat(bufs);
4201	};
4202
4203	var push = function ba_push(buf) { endbuf(); curbuf = buf; if(curbuf.l == null) curbuf.l = curbuf.length; next(blksz); };
4204
4205	return ({ next:next, push:push, end:end, _bufs:bufs }/*:any*/);
4206}
4207
4208function write_record(ba/*:BufArray*/, type/*:number*/, payload, length/*:?number*/) {
4209	var t/*:number*/ = +type, l;
4210	if(isNaN(t)) return; // TODO: throw something here?
4211	if(!length) length = XLSBRecordEnum[t].p || (payload||[]).length || 0;
4212	l = 1 + (t >= 0x80 ? 1 : 0) + 1/* + length*/;
4213	if(length >= 0x80) ++l; if(length >= 0x4000) ++l; if(length >= 0x200000) ++l;
4214	var o = ba.next(l);
4215	if(t <= 0x7F) o.write_shift(1, t);
4216	else {
4217		o.write_shift(1, (t & 0x7F) + 0x80);
4218		o.write_shift(1, (t >> 7));
4219	}
4220	for(var i = 0; i != 4; ++i) {
4221		if(length >= 0x80) { o.write_shift(1, (length & 0x7F)+0x80); length >>= 7; }
4222		else { o.write_shift(1, length); break; }
4223	}
4224	if(/*:: length != null &&*/length > 0 && is_buf(payload)) ba.push(payload);
4225}
4226/* XLS ranges enforced */
4227function shift_cell_xls(cell/*:CellAddress*/, tgt/*:any*/, opts/*:?any*/)/*:CellAddress*/ {
4228	var out = dup(cell);
4229	if(tgt.s) {
4230		if(out.cRel) out.c += tgt.s.c;
4231		if(out.rRel) out.r += tgt.s.r;
4232	} else {
4233		if(out.cRel) out.c += tgt.c;
4234		if(out.rRel) out.r += tgt.r;
4235	}
4236	if(!opts || opts.biff < 12) {
4237		while(out.c >= 0x100) out.c -= 0x100;
4238		while(out.r >= 0x10000) out.r -= 0x10000;
4239	}
4240	return out;
4241}
4242
4243function shift_range_xls(cell, range, opts) {
4244	var out = dup(cell);
4245	out.s = shift_cell_xls(out.s, range.s, opts);
4246	out.e = shift_cell_xls(out.e, range.s, opts);
4247	return out;
4248}
4249
4250function encode_cell_xls(c/*:CellAddress*/, biff/*:number*/)/*:string*/ {
4251	if(c.cRel && c.c < 0) { c = dup(c); while(c.c < 0) c.c += (biff > 8) ? 0x4000 : 0x100; }
4252	if(c.rRel && c.r < 0) { c = dup(c); while(c.r < 0) c.r += (biff > 8) ? 0x100000 : ((biff > 5) ? 0x10000 : 0x4000); }
4253	var s = encode_cell(c);
4254	if(!c.cRel && c.cRel != null) s = fix_col(s);
4255	if(!c.rRel && c.rRel != null) s = fix_row(s);
4256	return s;
4257}
4258
4259function encode_range_xls(r, opts)/*:string*/ {
4260	if(r.s.r == 0 && !r.s.rRel) {
4261		if(r.e.r == (opts.biff >= 12 ? 0xFFFFF : (opts.biff >= 8 ? 0x10000 : 0x4000)) && !r.e.rRel) {
4262			return (r.s.cRel ? "" : "$") + encode_col(r.s.c) + ":" + (r.e.cRel ? "" : "$") + encode_col(r.e.c);
4263		}
4264	}
4265	if(r.s.c == 0 && !r.s.cRel) {
4266		if(r.e.c == (opts.biff >= 12 ? 0x3FFF : 0xFF) && !r.e.cRel) {
4267			return (r.s.rRel ? "" : "$") + encode_row(r.s.r) + ":" + (r.e.rRel ? "" : "$") + encode_row(r.e.r);
4268		}
4269	}
4270	return encode_cell_xls(r.s, opts.biff) + ":" + encode_cell_xls(r.e, opts.biff);
4271}
4272function decode_row(rowstr/*:string*/)/*:number*/ { return parseInt(unfix_row(rowstr),10) - 1; }
4273function encode_row(row/*:number*/)/*:string*/ { return "" + (row + 1); }
4274function fix_row(cstr/*:string*/)/*:string*/ { return cstr.replace(/([A-Z]|^)(\d+)$/,"$1$$$2"); }
4275function unfix_row(cstr/*:string*/)/*:string*/ { return cstr.replace(/\$(\d+)$/,"$1"); }
4276
4277function decode_col(colstr/*:string*/)/*:number*/ { var c = unfix_col(colstr), d = 0, i = 0; for(; i !== c.length; ++i) d = 26*d + c.charCodeAt(i) - 64; return d - 1; }
4278function encode_col(col/*:number*/)/*:string*/ { if(col < 0) throw new Error("invalid column " + col); var s=""; for(++col; col; col=Math.floor((col-1)/26)) s = String.fromCharCode(((col-1)%26) + 65) + s; return s; }
4279function fix_col(cstr/*:string*/)/*:string*/ { return cstr.replace(/^([A-Z])/,"$$$1"); }
4280function unfix_col(cstr/*:string*/)/*:string*/ { return cstr.replace(/^\$([A-Z])/,"$1"); }
4281
4282function split_cell(cstr/*:string*/)/*:Array<string>*/ { return cstr.replace(/(\$?[A-Z]*)(\$?\d*)/,"$1,$2").split(","); }
4283//function decode_cell(cstr/*:string*/)/*:CellAddress*/ { var splt = split_cell(cstr); return { c:decode_col(splt[0]), r:decode_row(splt[1]) }; }
4284function decode_cell(cstr/*:string*/)/*:CellAddress*/ {
4285	var R = 0, C = 0;
4286	for(var i = 0; i < cstr.length; ++i) {
4287		var cc = cstr.charCodeAt(i);
4288		if(cc >= 48 && cc <= 57) R = 10 * R + (cc - 48);
4289		else if(cc >= 65 && cc <= 90) C = 26 * C + (cc - 64);
4290	}
4291	return { c: C - 1, r:R - 1 };
4292}
4293//function encode_cell(cell/*:CellAddress*/)/*:string*/ { return encode_col(cell.c) + encode_row(cell.r); }
4294function encode_cell(cell/*:CellAddress*/)/*:string*/ {
4295	var col = cell.c + 1;
4296	var s="";
4297	for(; col; col=((col-1)/26)|0) s = String.fromCharCode(((col-1)%26) + 65) + s;
4298	return s + (cell.r + 1);
4299}
4300function decode_range(range/*:string*/)/*:Range*/ {
4301	var idx = range.indexOf(":");
4302	if(idx == -1) return { s: decode_cell(range), e: decode_cell(range) };
4303	return { s: decode_cell(range.slice(0, idx)), e: decode_cell(range.slice(idx + 1)) };
4304}
4305/*# if only one arg, it is assumed to be a Range.  If 2 args, both are cell addresses */
4306function encode_range(cs/*:CellAddrSpec|Range*/,ce/*:?CellAddrSpec*/)/*:string*/ {
4307	if(typeof ce === 'undefined' || typeof ce === 'number') {
4308/*:: if(!(cs instanceof Range)) throw "unreachable"; */
4309		return encode_range(cs.s, cs.e);
4310	}
4311/*:: if((cs instanceof Range)) throw "unreachable"; */
4312	if(typeof cs !== 'string') cs = encode_cell((cs/*:any*/));
4313	if(typeof ce !== 'string') ce = encode_cell((ce/*:any*/));
4314/*:: if(typeof cs !== 'string') throw "unreachable"; */
4315/*:: if(typeof ce !== 'string') throw "unreachable"; */
4316	return cs == ce ? cs : cs + ":" + ce;
4317}
4318function fix_range(a1/*:string*/)/*:string*/ {
4319	var s = decode_range(a1);
4320	return "$" + encode_col(s.s.c) + "$" + encode_row(s.s.r) + ":$" + encode_col(s.e.c) + "$" + encode_row(s.e.r);
4321}
4322
4323// List of invalid characters needs to be tested further
4324function formula_quote_sheet_name(sname/*:string*/, opts)/*:string*/ {
4325	if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
4326	if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname.replace(/'/g, "''") + "'";
4327	return sname;
4328}
4329
4330function safe_decode_range(range/*:string*/)/*:Range*/ {
4331	var o = {s:{c:0,r:0},e:{c:0,r:0}};
4332	var idx = 0, i = 0, cc = 0;
4333	var len = range.length;
4334	for(idx = 0; i < len; ++i) {
4335		if((cc=range.charCodeAt(i)-64) < 1 || cc > 26) break;
4336		idx = 26*idx + cc;
4337	}
4338	o.s.c = --idx;
4339
4340	for(idx = 0; i < len; ++i) {
4341		if((cc=range.charCodeAt(i)-48) < 0 || cc > 9) break;
4342		idx = 10*idx + cc;
4343	}
4344	o.s.r = --idx;
4345
4346	if(i === len || cc != 10) { o.e.c=o.s.c; o.e.r=o.s.r; return o; }
4347	++i;
4348
4349	for(idx = 0; i != len; ++i) {
4350		if((cc=range.charCodeAt(i)-64) < 1 || cc > 26) break;
4351		idx = 26*idx + cc;
4352	}
4353	o.e.c = --idx;
4354
4355	for(idx = 0; i != len; ++i) {
4356		if((cc=range.charCodeAt(i)-48) < 0 || cc > 9) break;
4357		idx = 10*idx + cc;
4358	}
4359	o.e.r = --idx;
4360	return o;
4361}
4362
4363function safe_format_cell(cell/*:Cell*/, v/*:any*/) {
4364	var q = (cell.t == 'd' && v instanceof Date);
4365	if(cell.z != null) try { return (cell.w = SSF_format(cell.z, q ? datenum(v) : v)); } catch(e) { }
4366	try { return (cell.w = SSF_format((cell.XF||{}).numFmtId||(q ? 14 : 0),  q ? datenum(v) : v)); } catch(e) { return ''+v; }
4367}
4368
4369function format_cell(cell/*:Cell*/, v/*:any*/, o/*:any*/) {
4370	if(cell == null || cell.t == null || cell.t == 'z') return "";
4371	if(cell.w !== undefined) return cell.w;
4372	if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF;
4373	if(cell.t == "e") return BErr[cell.v] || cell.v;
4374	if(v == undefined) return safe_format_cell(cell, cell.v);
4375	return safe_format_cell(cell, v);
4376}
4377
4378function sheet_to_workbook(sheet/*:Worksheet*/, opts)/*:Workbook*/ {
4379	var n = opts && opts.sheet ? opts.sheet : "Sheet1";
4380	var sheets = {}; sheets[n] = sheet;
4381	return { SheetNames: [n], Sheets: sheets };
4382}
4383
4384function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ {
4385	var o = opts || {};
4386	var dense = _ws ? Array.isArray(_ws) : o.dense;
4387	if(DENSE != null && dense == null) dense = DENSE;
4388	var ws/*:Worksheet*/ = _ws || (dense ? ([]/*:any*/) : ({}/*:any*/));
4389	var _R = 0, _C = 0;
4390	if(ws && o.origin != null) {
4391		if(typeof o.origin == 'number') _R = o.origin;
4392		else {
4393			var _origin/*:CellAddress*/ = typeof o.origin == "string" ? decode_cell(o.origin) : o.origin;
4394			_R = _origin.r; _C = _origin.c;
4395		}
4396		if(!ws["!ref"]) ws["!ref"] = "A1:A1";
4397	}
4398	var range/*:Range*/ = ({s: {c:10000000, r:10000000}, e: {c:0, r:0}}/*:any*/);
4399	if(ws['!ref']) {
4400		var _range = safe_decode_range(ws['!ref']);
4401		range.s.c = _range.s.c;
4402		range.s.r = _range.s.r;
4403		range.e.c = Math.max(range.e.c, _range.e.c);
4404		range.e.r = Math.max(range.e.r, _range.e.r);
4405		if(_R == -1) range.e.r = _R = _range.e.r + 1;
4406	}
4407	for(var R = 0; R != data.length; ++R) {
4408		if(!data[R]) continue;
4409		if(!Array.isArray(data[R])) throw new Error("aoa_to_sheet expects an array of arrays");
4410		for(var C = 0; C != data[R].length; ++C) {
4411			if(typeof data[R][C] === 'undefined') continue;
4412			var cell/*:Cell*/ = ({v: data[R][C] }/*:any*/);
4413			var __R = _R + R, __C = _C + C;
4414			if(range.s.r > __R) range.s.r = __R;
4415			if(range.s.c > __C) range.s.c = __C;
4416			if(range.e.r < __R) range.e.r = __R;
4417			if(range.e.c < __C) range.e.c = __C;
4418			if(data[R][C] && typeof data[R][C] === 'object' && !Array.isArray(data[R][C]) && !(data[R][C] instanceof Date)) cell = data[R][C];
4419			else {
4420				if(Array.isArray(cell.v)) { cell.f = data[R][C][1]; cell.v = cell.v[0]; }
4421				if(cell.v === null) {
4422					if(cell.f) cell.t = 'n';
4423					else if(o.nullError) { cell.t = 'e'; cell.v = 0; }
4424					else if(!o.sheetStubs) continue;
4425					else cell.t = 'z';
4426				}
4427				else if(typeof cell.v === 'number') cell.t = 'n';
4428				else if(typeof cell.v === 'boolean') cell.t = 'b';
4429				else if(cell.v instanceof Date) {
4430					cell.z = o.dateNF || table_fmt[14];
4431					if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v, o.date1904)); }
4432					else { cell.t = 'n'; cell.v = datenum(cell.v, o.date1904); cell.w = SSF_format(cell.z, cell.v); }
4433				}
4434				else cell.t = 's';
4435			}
4436			if(dense) {
4437				if(!ws[__R]) ws[__R] = [];
4438				if(ws[__R][__C] && ws[__R][__C].z) cell.z = ws[__R][__C].z;
4439				ws[__R][__C] = cell;
4440			} else {
4441				var cell_ref = encode_cell(({c:__C,r:__R}/*:any*/));
4442				if(ws[cell_ref] && ws[cell_ref].z) cell.z = ws[cell_ref].z;
4443				ws[cell_ref] = cell;
4444			}
4445		}
4446	}
4447	if(range.s.c < 10000000) ws['!ref'] = encode_range(range);
4448	return ws;
4449}
4450function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ { return sheet_add_aoa(null, data, opts); }
4451
4452function parse_Int32LE(data) {
4453	return data.read_shift(4, 'i');
4454}
4455function write_UInt32LE(x/*:number*/, o) {
4456	if (!o) o = new_buf(4);
4457	o.write_shift(4, x);
4458	return o;
4459}
4460
4461/* [MS-XLSB] 2.5.168 */
4462function parse_XLWideString(data/*::, length*/)/*:string*/ {
4463	var cchCharacters = data.read_shift(4);
4464	return cchCharacters === 0 ? "" : data.read_shift(cchCharacters, 'dbcs');
4465}
4466function write_XLWideString(data/*:string*/, o) {
4467	var _null = false; if (o == null) { _null = true; o = new_buf(4 + 2 * data.length); }
4468	o.write_shift(4, data.length);
4469	if (data.length > 0) o.write_shift(0, data, 'dbcs');
4470	return _null ? o.slice(0, o.l) : o;
4471}
4472
4473/* [MS-XLSB] 2.5.91 */
4474//function parse_LPWideString(data/*::, length*/)/*:string*/ {
4475//	var cchCharacters = data.read_shift(2);
4476//	return cchCharacters === 0 ? "" : data.read_shift(cchCharacters, "utf16le");
4477//}
4478
4479/* [MS-XLSB] 2.5.143 */
4480function parse_StrRun(data) {
4481	return { ich: data.read_shift(2), ifnt: data.read_shift(2) };
4482}
4483function write_StrRun(run, o) {
4484	if (!o) o = new_buf(4);
4485	o.write_shift(2, run.ich || 0);
4486	o.write_shift(2, run.ifnt || 0);
4487	return o;
4488}
4489
4490/* [MS-XLSB] 2.5.121 */
4491function parse_RichStr(data, length/*:number*/)/*:XLString*/ {
4492	var start = data.l;
4493	var flags = data.read_shift(1);
4494	var str = parse_XLWideString(data);
4495	var rgsStrRun = [];
4496	var z = ({ t: str, h: str }/*:any*/);
4497	if ((flags & 1) !== 0) { /* fRichStr */
4498		/* TODO: formatted string */
4499		var dwSizeStrRun = data.read_shift(4);
4500		for (var i = 0; i != dwSizeStrRun; ++i) rgsStrRun.push(parse_StrRun(data));
4501		z.r = rgsStrRun;
4502	}
4503	else z.r = [{ ich: 0, ifnt: 0 }];
4504	//if((flags & 2) !== 0) { /* fExtStr */
4505	//	/* TODO: phonetic string */
4506	//}
4507	data.l = start + length;
4508	return z;
4509}
4510function write_RichStr(str/*:XLString*/, o/*:?Block*/)/*:Block*/ {
4511	/* TODO: formatted string */
4512	var _null = false; if (o == null) { _null = true; o = new_buf(15 + 4 * str.t.length); }
4513	o.write_shift(1, 0);
4514	write_XLWideString(str.t, o);
4515	return _null ? o.slice(0, o.l) : o;
4516}
4517/* [MS-XLSB] 2.4.328 BrtCommentText (RichStr w/1 run) */
4518var parse_BrtCommentText = parse_RichStr;
4519function write_BrtCommentText(str/*:XLString*/, o/*:?Block*/)/*:Block*/ {
4520	/* TODO: formatted string */
4521	var _null = false; if (o == null) { _null = true; o = new_buf(23 + 4 * str.t.length); }
4522	o.write_shift(1, 1);
4523	write_XLWideString(str.t, o);
4524	o.write_shift(4, 1);
4525	write_StrRun({ ich: 0, ifnt: 0 }, o);
4526	return _null ? o.slice(0, o.l) : o;
4527}
4528
4529/* [MS-XLSB] 2.5.9 */
4530function parse_XLSBCell(data)/*:any*/ {
4531	var col = data.read_shift(4);
4532	var iStyleRef = data.read_shift(2);
4533	iStyleRef += data.read_shift(1) << 16;
4534	data.l++; //var fPhShow = data.read_shift(1);
4535	return { c: col, iStyleRef: iStyleRef };
4536}
4537function write_XLSBCell(cell/*:any*/, o/*:?Block*/) {
4538	if (o == null) o = new_buf(8);
4539	o.write_shift(-4, cell.c);
4540	o.write_shift(3, cell.iStyleRef || cell.s);
4541	o.write_shift(1, 0); /* fPhShow */
4542	return o;
4543}
4544
4545/* Short XLSB Cell does not include column */
4546function parse_XLSBShortCell(data)/*:any*/ {
4547	var iStyleRef = data.read_shift(2);
4548	iStyleRef += data.read_shift(1) <<16;
4549	data.l++; //var fPhShow = data.read_shift(1);
4550	return { c:-1, iStyleRef: iStyleRef };
4551}
4552function write_XLSBShortCell(cell/*:any*/, o/*:?Block*/) {
4553	if(o == null) o = new_buf(4);
4554	o.write_shift(3, cell.iStyleRef || cell.s);
4555	o.write_shift(1, 0); /* fPhShow */
4556	return o;
4557}
4558
4559/* [MS-XLSB] 2.5.21 */
4560var parse_XLSBCodeName = parse_XLWideString;
4561var write_XLSBCodeName = write_XLWideString;
4562
4563/* [MS-XLSB] 2.5.166 */
4564function parse_XLNullableWideString(data/*::, length*/)/*:string*/ {
4565	var cchCharacters = data.read_shift(4);
4566	return cchCharacters === 0 || cchCharacters === 0xFFFFFFFF ? "" : data.read_shift(cchCharacters, 'dbcs');
4567}
4568function write_XLNullableWideString(data/*:string*/, o) {
4569	var _null = false; if (o == null) { _null = true; o = new_buf(127); }
4570	o.write_shift(4, data.length > 0 ? data.length : 0xFFFFFFFF);
4571	if (data.length > 0) o.write_shift(0, data, 'dbcs');
4572	return _null ? o.slice(0, o.l) : o;
4573}
4574
4575/* [MS-XLSB] 2.5.165 */
4576var parse_XLNameWideString = parse_XLWideString;
4577//var write_XLNameWideString = write_XLWideString;
4578
4579/* [MS-XLSB] 2.5.114 */
4580var parse_RelID = parse_XLNullableWideString;
4581var write_RelID = write_XLNullableWideString;
4582
4583
4584/* [MS-XLS] 2.5.217 ; [MS-XLSB] 2.5.122 */
4585function parse_RkNumber(data)/*:number*/ {
4586	var b = data.slice(data.l, data.l + 4);
4587	var fX100 = (b[0] & 1), fInt = (b[0] & 2);
4588	data.l += 4;
4589	var RK = fInt === 0 ? __double([0, 0, 0, 0, (b[0] & 0xFC), b[1], b[2], b[3]], 0) : __readInt32LE(b, 0) >> 2;
4590	return fX100 ? (RK / 100) : RK;
4591}
4592function write_RkNumber(data/*:number*/, o) {
4593	if (o == null) o = new_buf(4);
4594	var fX100 = 0, fInt = 0, d100 = data * 100;
4595	if ((data == (data | 0)) && (data >= -(1 << 29)) && (data < (1 << 29))) { fInt = 1; }
4596	else if ((d100 == (d100 | 0)) && (d100 >= -(1 << 29)) && (d100 < (1 << 29))) { fInt = 1; fX100 = 1; }
4597	if (fInt) o.write_shift(-4, ((fX100 ? d100 : data) << 2) + (fX100 + 2));
4598	else throw new Error("unsupported RkNumber " + data); // TODO
4599}
4600
4601
4602/* [MS-XLSB] 2.5.117 RfX */
4603function parse_RfX(data /*::, length*/)/*:Range*/ {
4604	var cell/*:Range*/ = ({ s: {}, e: {} }/*:any*/);
4605	cell.s.r = data.read_shift(4);
4606	cell.e.r = data.read_shift(4);
4607	cell.s.c = data.read_shift(4);
4608	cell.e.c = data.read_shift(4);
4609	return cell;
4610}
4611function write_RfX(r/*:Range*/, o) {
4612	if (!o) o = new_buf(16);
4613	o.write_shift(4, r.s.r);
4614	o.write_shift(4, r.e.r);
4615	o.write_shift(4, r.s.c);
4616	o.write_shift(4, r.e.c);
4617	return o;
4618}
4619
4620/* [MS-XLSB] 2.5.153 UncheckedRfX */
4621var parse_UncheckedRfX = parse_RfX;
4622var write_UncheckedRfX = write_RfX;
4623
4624/* [MS-XLSB] 2.5.155 UncheckedSqRfX */
4625//function parse_UncheckedSqRfX(data) {
4626//	var cnt = data.read_shift(4);
4627//	var out = [];
4628//	for(var i = 0; i < cnt; ++i) {
4629//		var rng = parse_UncheckedRfX(data);
4630//		out.push(encode_range(rng));
4631//	}
4632//	return out.join(",");
4633//}
4634//function write_UncheckedSqRfX(sqrfx/*:string*/) {
4635//	var parts = sqrfx.split(/\s*,\s*/);
4636//	var o = new_buf(4); o.write_shift(4, parts.length);
4637//	var out = [o];
4638//	parts.forEach(function(rng) {
4639//		out.push(write_UncheckedRfX(safe_decode_range(rng)));
4640//	});
4641//	return bconcat(out);
4642//}
4643
4644/* [MS-XLS] 2.5.342 ; [MS-XLSB] 2.5.171 */
4645/* TODO: error checking, NaN and Infinity values are not valid Xnum */
4646function parse_Xnum(data/*::, length*/) {
4647	if(data.length - data.l < 8) throw "XLS Xnum Buffer underflow";
4648	return data.read_shift(8, 'f');
4649}
4650function write_Xnum(data, o) { return (o || new_buf(8)).write_shift(8, data, 'f'); }
4651
4652/* [MS-XLSB] 2.4.324 BrtColor */
4653function parse_BrtColor(data/*::, length*/) {
4654	var out = {};
4655	var d = data.read_shift(1);
4656
4657	//var fValidRGB = d & 1;
4658	var xColorType = d >>> 1;
4659
4660	var index = data.read_shift(1);
4661	var nTS = data.read_shift(2, 'i');
4662	var bR = data.read_shift(1);
4663	var bG = data.read_shift(1);
4664	var bB = data.read_shift(1);
4665	data.l++; //var bAlpha = data.read_shift(1);
4666
4667	switch (xColorType) {
4668		case 0: out.auto = 1; break;
4669		case 1:
4670			out.index = index;
4671			var icv = XLSIcv[index];
4672			/* automatic pseudo index 81 */
4673			if (icv) out.rgb = rgb2Hex(icv);
4674			break;
4675		case 2:
4676			/* if(!fValidRGB) throw new Error("invalid"); */
4677			out.rgb = rgb2Hex([bR, bG, bB]);
4678			break;
4679		case 3: out.theme = index; break;
4680	}
4681	if (nTS != 0) out.tint = nTS > 0 ? nTS / 32767 : nTS / 32768;
4682
4683	return out;
4684}
4685function write_BrtColor(color, o) {
4686	if (!o) o = new_buf(8);
4687	if (!color || color.auto) { o.write_shift(4, 0); o.write_shift(4, 0); return o; }
4688	if (color.index != null) {
4689		o.write_shift(1, 0x02);
4690		o.write_shift(1, color.index);
4691	} else if (color.theme != null) {
4692		o.write_shift(1, 0x06);
4693		o.write_shift(1, color.theme);
4694	} else {
4695		o.write_shift(1, 0x05);
4696		o.write_shift(1, 0);
4697	}
4698	var nTS = color.tint || 0;
4699	if (nTS > 0) nTS *= 32767;
4700	else if (nTS < 0) nTS *= 32768;
4701	o.write_shift(2, nTS);
4702	if (!color.rgb || color.theme != null) {
4703		o.write_shift(2, 0);
4704		o.write_shift(1, 0);
4705		o.write_shift(1, 0);
4706	} else {
4707		var rgb = (color.rgb || 'FFFFFF');
4708		if (typeof rgb == 'number') rgb = ("000000" + rgb.toString(16)).slice(-6);
4709		o.write_shift(1, parseInt(rgb.slice(0, 2), 16));
4710		o.write_shift(1, parseInt(rgb.slice(2, 4), 16));
4711		o.write_shift(1, parseInt(rgb.slice(4, 6), 16));
4712		o.write_shift(1, 0xFF);
4713	}
4714	return o;
4715}
4716
4717/* [MS-XLSB] 2.5.52 */
4718function parse_FontFlags(data/*::, length, opts*/) {
4719	var d = data.read_shift(1);
4720	data.l++;
4721	var out = {
4722		fBold: d & 0x01,
4723		fItalic: d & 0x02,
4724		fUnderline: d & 0x04,
4725		fStrikeout: d & 0x08,
4726		fOutline: d & 0x10,
4727		fShadow: d & 0x20,
4728		fCondense: d & 0x40,
4729		fExtend: d & 0x80
4730	};
4731	return out;
4732}
4733function write_FontFlags(font, o) {
4734	if (!o) o = new_buf(2);
4735	var grbit =
4736		(font.italic ? 0x02 : 0) |
4737		(font.strike ? 0x08 : 0) |
4738		(font.outline ? 0x10 : 0) |
4739		(font.shadow ? 0x20 : 0) |
4740		(font.condense ? 0x40 : 0) |
4741		(font.extend ? 0x80 : 0);
4742	o.write_shift(1, grbit);
4743	o.write_shift(1, 0);
4744	return o;
4745}
4746
4747/* [MS-OLEDS] 2.3.1 and 2.3.2 */
4748function parse_ClipboardFormatOrString(o, w/*:number*/)/*:string*/ {
4749	// $FlowIgnore
4750	var ClipFmt = { 2: "BITMAP", 3: "METAFILEPICT", 8: "DIB", 14: "ENHMETAFILE" };
4751	var m/*:number*/ = o.read_shift(4);
4752	switch (m) {
4753		case 0x00000000: return "";
4754		case 0xffffffff: case 0xfffffffe: return ClipFmt[o.read_shift(4)] || "";
4755	}
4756	if (m > 0x190) throw new Error("Unsupported Clipboard: " + m.toString(16));
4757	o.l -= 4;
4758	return o.read_shift(0, w == 1 ? "lpstr" : "lpwstr");
4759}
4760function parse_ClipboardFormatOrAnsiString(o) { return parse_ClipboardFormatOrString(o, 1); }
4761function parse_ClipboardFormatOrUnicodeString(o) { return parse_ClipboardFormatOrString(o, 2); }
4762
4763/* [MS-OLEPS] 2.2 PropertyType */
4764// Note: some tree shakers cannot handle VT_VECTOR | $CONST, hence extra vars
4765//var VT_EMPTY    = 0x0000;
4766//var VT_NULL     = 0x0001;
4767var VT_I2       = 0x0002;
4768var VT_I4       = 0x0003;
4769//var VT_R4       = 0x0004;
4770//var VT_R8       = 0x0005;
4771//var VT_CY       = 0x0006;
4772//var VT_DATE     = 0x0007;
4773//var VT_BSTR     = 0x0008;
4774//var VT_ERROR    = 0x000A;
4775var VT_BOOL     = 0x000B;
4776var VT_VARIANT  = 0x000C;
4777//var VT_DECIMAL  = 0x000E;
4778//var VT_I1       = 0x0010;
4779//var VT_UI1      = 0x0011;
4780//var VT_UI2      = 0x0012;
4781var VT_UI4      = 0x0013;
4782//var VT_I8       = 0x0014;
4783//var VT_UI8      = 0x0015;
4784//var VT_INT      = 0x0016;
4785//var VT_UINT     = 0x0017;
4786//var VT_LPSTR    = 0x001E;
4787//var VT_LPWSTR   = 0x001F;
4788var VT_FILETIME = 0x0040;
4789var VT_BLOB     = 0x0041;
4790//var VT_STREAM   = 0x0042;
4791//var VT_STORAGE  = 0x0043;
4792//var VT_STREAMED_Object  = 0x0044;
4793//var VT_STORED_Object    = 0x0045;
4794//var VT_BLOB_Object      = 0x0046;
4795var VT_CF       = 0x0047;
4796//var VT_CLSID    = 0x0048;
4797//var VT_VERSIONED_STREAM = 0x0049;
4798//var VT_VECTOR   = 0x1000;
4799var VT_VECTOR_VARIANT = 0x100C;
4800var VT_VECTOR_LPSTR   = 0x101E;
4801//var VT_ARRAY    = 0x2000;
4802
4803var VT_STRING   = 0x0050; // 2.3.3.1.11 VtString
4804var VT_USTR     = 0x0051; // 2.3.3.1.12 VtUnalignedString
4805var VT_CUSTOM   = [VT_STRING, VT_USTR];
4806
4807/* [MS-OSHARED] 2.3.3.2.2.1 Document Summary Information PIDDSI */
4808var DocSummaryPIDDSI = {
4809	/*::[*/0x01/*::]*/: { n: 'CodePage', t: VT_I2 },
4810	/*::[*/0x02/*::]*/: { n: 'Category', t: VT_STRING },
4811	/*::[*/0x03/*::]*/: { n: 'PresentationFormat', t: VT_STRING },
4812	/*::[*/0x04/*::]*/: { n: 'ByteCount', t: VT_I4 },
4813	/*::[*/0x05/*::]*/: { n: 'LineCount', t: VT_I4 },
4814	/*::[*/0x06/*::]*/: { n: 'ParagraphCount', t: VT_I4 },
4815	/*::[*/0x07/*::]*/: { n: 'SlideCount', t: VT_I4 },
4816	/*::[*/0x08/*::]*/: { n: 'NoteCount', t: VT_I4 },
4817	/*::[*/0x09/*::]*/: { n: 'HiddenCount', t: VT_I4 },
4818	/*::[*/0x0a/*::]*/: { n: 'MultimediaClipCount', t: VT_I4 },
4819	/*::[*/0x0b/*::]*/: { n: 'ScaleCrop', t: VT_BOOL },
4820	/*::[*/0x0c/*::]*/: { n: 'HeadingPairs', t: VT_VECTOR_VARIANT /* VT_VECTOR | VT_VARIANT */ },
4821	/*::[*/0x0d/*::]*/: { n: 'TitlesOfParts', t: VT_VECTOR_LPSTR /* VT_VECTOR | VT_LPSTR */ },
4822	/*::[*/0x0e/*::]*/: { n: 'Manager', t: VT_STRING },
4823	/*::[*/0x0f/*::]*/: { n: 'Company', t: VT_STRING },
4824	/*::[*/0x10/*::]*/: { n: 'LinksUpToDate', t: VT_BOOL },
4825	/*::[*/0x11/*::]*/: { n: 'CharacterCount', t: VT_I4 },
4826	/*::[*/0x13/*::]*/: { n: 'SharedDoc', t: VT_BOOL },
4827	/*::[*/0x16/*::]*/: { n: 'HyperlinksChanged', t: VT_BOOL },
4828	/*::[*/0x17/*::]*/: { n: 'AppVersion', t: VT_I4, p: 'version' },
4829	/*::[*/0x18/*::]*/: { n: 'DigSig', t: VT_BLOB },
4830	/*::[*/0x1A/*::]*/: { n: 'ContentType', t: VT_STRING },
4831	/*::[*/0x1B/*::]*/: { n: 'ContentStatus', t: VT_STRING },
4832	/*::[*/0x1C/*::]*/: { n: 'Language', t: VT_STRING },
4833	/*::[*/0x1D/*::]*/: { n: 'Version', t: VT_STRING },
4834	/*::[*/0xFF/*::]*/: {},
4835	/* [MS-OLEPS] 2.18 */
4836	/*::[*/0x80000000/*::]*/: { n: 'Locale', t: VT_UI4 },
4837	/*::[*/0x80000003/*::]*/: { n: 'Behavior', t: VT_UI4 },
4838	/*::[*/0x72627262/*::]*/: {}
4839};
4840
4841/* [MS-OSHARED] 2.3.3.2.1.1 Summary Information Property Set PIDSI */
4842var SummaryPIDSI = {
4843	/*::[*/0x01/*::]*/: { n: 'CodePage', t: VT_I2 },
4844	/*::[*/0x02/*::]*/: { n: 'Title', t: VT_STRING },
4845	/*::[*/0x03/*::]*/: { n: 'Subject', t: VT_STRING },
4846	/*::[*/0x04/*::]*/: { n: 'Author', t: VT_STRING },
4847	/*::[*/0x05/*::]*/: { n: 'Keywords', t: VT_STRING },
4848	/*::[*/0x06/*::]*/: { n: 'Comments', t: VT_STRING },
4849	/*::[*/0x07/*::]*/: { n: 'Template', t: VT_STRING },
4850	/*::[*/0x08/*::]*/: { n: 'LastAuthor', t: VT_STRING },
4851	/*::[*/0x09/*::]*/: { n: 'RevNumber', t: VT_STRING },
4852	/*::[*/0x0A/*::]*/: { n: 'EditTime', t: VT_FILETIME },
4853	/*::[*/0x0B/*::]*/: { n: 'LastPrinted', t: VT_FILETIME },
4854	/*::[*/0x0C/*::]*/: { n: 'CreatedDate', t: VT_FILETIME },
4855	/*::[*/0x0D/*::]*/: { n: 'ModifiedDate', t: VT_FILETIME },
4856	/*::[*/0x0E/*::]*/: { n: 'PageCount', t: VT_I4 },
4857	/*::[*/0x0F/*::]*/: { n: 'WordCount', t: VT_I4 },
4858	/*::[*/0x10/*::]*/: { n: 'CharCount', t: VT_I4 },
4859	/*::[*/0x11/*::]*/: { n: 'Thumbnail', t: VT_CF },
4860	/*::[*/0x12/*::]*/: { n: 'Application', t: VT_STRING },
4861	/*::[*/0x13/*::]*/: { n: 'DocSecurity', t: VT_I4 },
4862	/*::[*/0xFF/*::]*/: {},
4863	/* [MS-OLEPS] 2.18 */
4864	/*::[*/0x80000000/*::]*/: { n: 'Locale', t: VT_UI4 },
4865	/*::[*/0x80000003/*::]*/: { n: 'Behavior', t: VT_UI4 },
4866	/*::[*/0x72627262/*::]*/: {}
4867};
4868
4869/* [MS-XLS] 2.4.63 Country/Region codes */
4870var CountryEnum = {
4871	/*::[*/0x0001/*::]*/: "US", // United States
4872	/*::[*/0x0002/*::]*/: "CA", // Canada
4873	/*::[*/0x0003/*::]*/: "", // Latin America (except Brazil)
4874	/*::[*/0x0007/*::]*/: "RU", // Russia
4875	/*::[*/0x0014/*::]*/: "EG", // Egypt
4876	/*::[*/0x001E/*::]*/: "GR", // Greece
4877	/*::[*/0x001F/*::]*/: "NL", // Netherlands
4878	/*::[*/0x0020/*::]*/: "BE", // Belgium
4879	/*::[*/0x0021/*::]*/: "FR", // France
4880	/*::[*/0x0022/*::]*/: "ES", // Spain
4881	/*::[*/0x0024/*::]*/: "HU", // Hungary
4882	/*::[*/0x0027/*::]*/: "IT", // Italy
4883	/*::[*/0x0029/*::]*/: "CH", // Switzerland
4884	/*::[*/0x002B/*::]*/: "AT", // Austria
4885	/*::[*/0x002C/*::]*/: "GB", // United Kingdom
4886	/*::[*/0x002D/*::]*/: "DK", // Denmark
4887	/*::[*/0x002E/*::]*/: "SE", // Sweden
4888	/*::[*/0x002F/*::]*/: "NO", // Norway
4889	/*::[*/0x0030/*::]*/: "PL", // Poland
4890	/*::[*/0x0031/*::]*/: "DE", // Germany
4891	/*::[*/0x0034/*::]*/: "MX", // Mexico
4892	/*::[*/0x0037/*::]*/: "BR", // Brazil
4893	/*::[*/0x003d/*::]*/: "AU", // Australia
4894	/*::[*/0x0040/*::]*/: "NZ", // New Zealand
4895	/*::[*/0x0042/*::]*/: "TH", // Thailand
4896	/*::[*/0x0051/*::]*/: "JP", // Japan
4897	/*::[*/0x0052/*::]*/: "KR", // Korea
4898	/*::[*/0x0054/*::]*/: "VN", // Viet Nam
4899	/*::[*/0x0056/*::]*/: "CN", // China
4900	/*::[*/0x005A/*::]*/: "TR", // Turkey
4901	/*::[*/0x0069/*::]*/: "JS", // Ramastan
4902	/*::[*/0x00D5/*::]*/: "DZ", // Algeria
4903	/*::[*/0x00D8/*::]*/: "MA", // Morocco
4904	/*::[*/0x00DA/*::]*/: "LY", // Libya
4905	/*::[*/0x015F/*::]*/: "PT", // Portugal
4906	/*::[*/0x0162/*::]*/: "IS", // Iceland
4907	/*::[*/0x0166/*::]*/: "FI", // Finland
4908	/*::[*/0x01A4/*::]*/: "CZ", // Czech Republic
4909	/*::[*/0x0376/*::]*/: "TW", // Taiwan
4910	/*::[*/0x03C1/*::]*/: "LB", // Lebanon
4911	/*::[*/0x03C2/*::]*/: "JO", // Jordan
4912	/*::[*/0x03C3/*::]*/: "SY", // Syria
4913	/*::[*/0x03C4/*::]*/: "IQ", // Iraq
4914	/*::[*/0x03C5/*::]*/: "KW", // Kuwait
4915	/*::[*/0x03C6/*::]*/: "SA", // Saudi Arabia
4916	/*::[*/0x03CB/*::]*/: "AE", // United Arab Emirates
4917	/*::[*/0x03CC/*::]*/: "IL", // Israel
4918	/*::[*/0x03CE/*::]*/: "QA", // Qatar
4919	/*::[*/0x03D5/*::]*/: "IR", // Iran
4920	/*::[*/0xFFFF/*::]*/: "US"  // United States
4921};
4922
4923/* [MS-XLS] 2.5.127 */
4924var XLSFillPattern = [
4925	null,
4926	'solid',
4927	'mediumGray',
4928	'darkGray',
4929	'lightGray',
4930	'darkHorizontal',
4931	'darkVertical',
4932	'darkDown',
4933	'darkUp',
4934	'darkGrid',
4935	'darkTrellis',
4936	'lightHorizontal',
4937	'lightVertical',
4938	'lightDown',
4939	'lightUp',
4940	'lightGrid',
4941	'lightTrellis',
4942	'gray125',
4943	'gray0625'
4944];
4945
4946function rgbify(arr/*:Array<number>*/)/*:Array<[number, number, number]>*/ { return arr.map(function(x) { return [(x>>16)&255,(x>>8)&255,x&255]; }); }
4947
4948/* [MS-XLS] 2.5.161 */
4949/* [MS-XLSB] 2.5.75 Icv */
4950var _XLSIcv = /*#__PURE__*/ rgbify([
4951	/* Color Constants */
4952	0x000000,
4953	0xFFFFFF,
4954	0xFF0000,
4955	0x00FF00,
4956	0x0000FF,
4957	0xFFFF00,
4958	0xFF00FF,
4959	0x00FFFF,
4960
4961	/* Overridable Defaults */
4962	0x000000,
4963	0xFFFFFF,
4964	0xFF0000,
4965	0x00FF00,
4966	0x0000FF,
4967	0xFFFF00,
4968	0xFF00FF,
4969	0x00FFFF,
4970
4971	0x800000,
4972	0x008000,
4973	0x000080,
4974	0x808000,
4975	0x800080,
4976	0x008080,
4977	0xC0C0C0,
4978	0x808080,
4979	0x9999FF,
4980	0x993366,
4981	0xFFFFCC,
4982	0xCCFFFF,
4983	0x660066,
4984	0xFF8080,
4985	0x0066CC,
4986	0xCCCCFF,
4987
4988	0x000080,
4989	0xFF00FF,
4990	0xFFFF00,
4991	0x00FFFF,
4992	0x800080,
4993	0x800000,
4994	0x008080,
4995	0x0000FF,
4996	0x00CCFF,
4997	0xCCFFFF,
4998	0xCCFFCC,
4999	0xFFFF99,
5000	0x99CCFF,
5001	0xFF99CC,
5002	0xCC99FF,
5003	0xFFCC99,
5004
5005	0x3366FF,
5006	0x33CCCC,
5007	0x99CC00,
5008	0xFFCC00,
5009	0xFF9900,
5010	0xFF6600,
5011	0x666699,
5012	0x969696,
5013	0x003366,
5014	0x339966,
5015	0x003300,
5016	0x333300,
5017	0x993300,
5018	0x993366,
5019	0x333399,
5020	0x333333,
5021
5022	/* Other entries to appease BIFF8/12 */
5023	0x000000, /* 0x40 icvForeground ?? */
5024	0xFFFFFF, /* 0x41 icvBackground ?? */
5025	0x000000, /* 0x42 icvFrame ?? */
5026	0x000000, /* 0x43 icv3D ?? */
5027	0x000000, /* 0x44 icv3DText ?? */
5028	0x000000, /* 0x45 icv3DHilite ?? */
5029	0x000000, /* 0x46 icv3DShadow ?? */
5030	0x000000, /* 0x47 icvHilite ?? */
5031	0x000000, /* 0x48 icvCtlText ?? */
5032	0x000000, /* 0x49 icvCtlScrl ?? */
5033	0x000000, /* 0x4A icvCtlInv ?? */
5034	0x000000, /* 0x4B icvCtlBody ?? */
5035	0x000000, /* 0x4C icvCtlFrame ?? */
5036	0x000000, /* 0x4D icvCtlFore ?? */
5037	0x000000, /* 0x4E icvCtlBack ?? */
5038	0x000000, /* 0x4F icvCtlNeutral */
5039	0x000000, /* 0x50 icvInfoBk ?? */
5040	0x000000 /* 0x51 icvInfoText ?? */
5041]);
5042var XLSIcv = /*#__PURE__*/dup(_XLSIcv);
5043
5044/* [MS-XLSB] 2.5.97.2 */
5045var BErr = {
5046	/*::[*/0x00/*::]*/: "#NULL!",
5047	/*::[*/0x07/*::]*/: "#DIV/0!",
5048	/*::[*/0x0F/*::]*/: "#VALUE!",
5049	/*::[*/0x17/*::]*/: "#REF!",
5050	/*::[*/0x1D/*::]*/: "#NAME?",
5051	/*::[*/0x24/*::]*/: "#NUM!",
5052	/*::[*/0x2A/*::]*/: "#N/A",
5053	/*::[*/0x2B/*::]*/: "#GETTING_DATA",
5054	/*::[*/0xFF/*::]*/: "#WTF?"
5055};
5056//var RBErr = evert_num(BErr);
5057var RBErr = {
5058	"#NULL!":        0x00,
5059	"#DIV/0!":       0x07,
5060	"#VALUE!":       0x0F,
5061	"#REF!":         0x17,
5062	"#NAME?":        0x1D,
5063	"#NUM!":         0x24,
5064	"#N/A":          0x2A,
5065	"#GETTING_DATA": 0x2B,
5066	"#WTF?":         0xFF
5067};
5068
5069var XLSLblBuiltIn = [
5070	"_xlnm.Consolidate_Area",
5071	"_xlnm.Auto_Open",
5072	"_xlnm.Auto_Close",
5073	"_xlnm.Extract",
5074	"_xlnm.Database",
5075	"_xlnm.Criteria",
5076	"_xlnm.Print_Area",
5077	"_xlnm.Print_Titles",
5078	"_xlnm.Recorder",
5079	"_xlnm.Data_Form",
5080	"_xlnm.Auto_Activate",
5081	"_xlnm.Auto_Deactivate",
5082	"_xlnm.Sheet_Title",
5083	"_xlnm._FilterDatabase"
5084];
5085
5086/* Parts enumerated in OPC spec, MS-XLSB and MS-XLSX */
5087/* 12.3 Part Summary <SpreadsheetML> */
5088/* 14.2 Part Summary <DrawingML> */
5089/* [MS-XLSX] 2.1 Part Enumerations ; [MS-XLSB] 2.1.7 Part Enumeration */
5090var ct2type/*{[string]:string}*/ = ({
5091	/* Workbook */
5092	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml": "workbooks",
5093	"application/vnd.ms-excel.sheet.macroEnabled.main+xml": "workbooks",
5094	"application/vnd.ms-excel.sheet.binary.macroEnabled.main": "workbooks",
5095	"application/vnd.ms-excel.addin.macroEnabled.main+xml": "workbooks",
5096	"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml": "workbooks",
5097
5098	/* Worksheet */
5099	"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml": "sheets",
5100	"application/vnd.ms-excel.worksheet": "sheets",
5101	"application/vnd.ms-excel.binIndexWs": "TODO", /* Binary Index */
5102
5103	/* Chartsheet */
5104	"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml": "charts",
5105	"application/vnd.ms-excel.chartsheet": "charts",
5106
5107	/* Macrosheet */
5108	"application/vnd.ms-excel.macrosheet+xml": "macros",
5109	"application/vnd.ms-excel.macrosheet": "macros",
5110	"application/vnd.ms-excel.intlmacrosheet": "TODO",
5111	"application/vnd.ms-excel.binIndexMs": "TODO", /* Binary Index */
5112
5113	/* Dialogsheet */
5114	"application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml": "dialogs",
5115	"application/vnd.ms-excel.dialogsheet": "dialogs",
5116
5117	/* Shared Strings */
5118	"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml": "strs",
5119	"application/vnd.ms-excel.sharedStrings": "strs",
5120
5121	/* Styles */
5122	"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml": "styles",
5123	"application/vnd.ms-excel.styles": "styles",
5124
5125	/* File Properties */
5126	"application/vnd.openxmlformats-package.core-properties+xml": "coreprops",
5127	"application/vnd.openxmlformats-officedocument.custom-properties+xml": "custprops",
5128	"application/vnd.openxmlformats-officedocument.extended-properties+xml": "extprops",
5129
5130	/* Custom Data Properties */
5131	"application/vnd.openxmlformats-officedocument.customXmlProperties+xml": "TODO",
5132	"application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty": "TODO",
5133
5134	/* Comments */
5135	"application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml": "comments",
5136	"application/vnd.ms-excel.comments": "comments",
5137	"application/vnd.ms-excel.threadedcomments+xml": "threadedcomments",
5138	"application/vnd.ms-excel.person+xml": "people",
5139
5140	/* Metadata (Stock/Geography and Dynamic Array) */
5141	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml": "metadata",
5142	"application/vnd.ms-excel.sheetMetadata": "metadata",
5143
5144	/* PivotTable */
5145	"application/vnd.ms-excel.pivotTable": "TODO",
5146	"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml": "TODO",
5147
5148	/* Chart Objects */
5149	"application/vnd.openxmlformats-officedocument.drawingml.chart+xml": "TODO",
5150
5151	/* Chart Colors */
5152	"application/vnd.ms-office.chartcolorstyle+xml": "TODO",
5153
5154	/* Chart Style */
5155	"application/vnd.ms-office.chartstyle+xml": "TODO",
5156
5157	/* Chart Advanced */
5158	"application/vnd.ms-office.chartex+xml": "TODO",
5159
5160	/* Calculation Chain */
5161	"application/vnd.ms-excel.calcChain": "calcchains",
5162	"application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml": "calcchains",
5163
5164	/* Printer Settings */
5165	"application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings": "TODO",
5166
5167	/* ActiveX */
5168	"application/vnd.ms-office.activeX": "TODO",
5169	"application/vnd.ms-office.activeX+xml": "TODO",
5170
5171	/* Custom Toolbars */
5172	"application/vnd.ms-excel.attachedToolbars": "TODO",
5173
5174	/* External Data Connections */
5175	"application/vnd.ms-excel.connections": "TODO",
5176	"application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml": "TODO",
5177
5178	/* External Links */
5179	"application/vnd.ms-excel.externalLink": "links",
5180	"application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml": "links",
5181
5182	/* PivotCache */
5183	"application/vnd.ms-excel.pivotCacheDefinition": "TODO",
5184	"application/vnd.ms-excel.pivotCacheRecords": "TODO",
5185	"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml": "TODO",
5186	"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml": "TODO",
5187
5188	/* Query Table */
5189	"application/vnd.ms-excel.queryTable": "TODO",
5190	"application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml": "TODO",
5191
5192	/* Shared Workbook */
5193	"application/vnd.ms-excel.userNames": "TODO",
5194	"application/vnd.ms-excel.revisionHeaders": "TODO",
5195	"application/vnd.ms-excel.revisionLog": "TODO",
5196	"application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml": "TODO",
5197	"application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml": "TODO",
5198	"application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml": "TODO",
5199
5200	/* Single Cell Table */
5201	"application/vnd.ms-excel.tableSingleCells": "TODO",
5202	"application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml": "TODO",
5203
5204	/* Slicer */
5205	"application/vnd.ms-excel.slicer": "TODO",
5206	"application/vnd.ms-excel.slicerCache": "TODO",
5207	"application/vnd.ms-excel.slicer+xml": "TODO",
5208	"application/vnd.ms-excel.slicerCache+xml": "TODO",
5209
5210	/* Sort Map */
5211	"application/vnd.ms-excel.wsSortMap": "TODO",
5212
5213	/* Table */
5214	"application/vnd.ms-excel.table": "TODO",
5215	"application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml": "TODO",
5216
5217	/* Themes */
5218	"application/vnd.openxmlformats-officedocument.theme+xml": "themes",
5219
5220	/* Theme Override */
5221	"application/vnd.openxmlformats-officedocument.themeOverride+xml": "TODO",
5222
5223	/* Timeline */
5224	"application/vnd.ms-excel.Timeline+xml": "TODO", /* verify */
5225	"application/vnd.ms-excel.TimelineCache+xml": "TODO", /* verify */
5226
5227	/* VBA */
5228	"application/vnd.ms-office.vbaProject": "vba",
5229	"application/vnd.ms-office.vbaProjectSignature": "TODO",
5230
5231	/* Volatile Dependencies */
5232	"application/vnd.ms-office.volatileDependencies": "TODO",
5233	"application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml": "TODO",
5234
5235	/* Control Properties */
5236	"application/vnd.ms-excel.controlproperties+xml": "TODO",
5237
5238	/* Data Model */
5239	"application/vnd.openxmlformats-officedocument.model+data": "TODO",
5240
5241	/* Survey */
5242	"application/vnd.ms-excel.Survey+xml": "TODO",
5243
5244	/* Drawing */
5245	"application/vnd.openxmlformats-officedocument.drawing+xml": "drawings",
5246	"application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml": "TODO",
5247	"application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml": "TODO",
5248	"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml": "TODO",
5249	"application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml": "TODO",
5250	"application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml": "TODO",
5251
5252	/* VML */
5253	"application/vnd.openxmlformats-officedocument.vmlDrawing": "TODO",
5254
5255	"application/vnd.openxmlformats-package.relationships+xml": "rels",
5256	"application/vnd.openxmlformats-officedocument.oleObject": "TODO",
5257
5258	/* Image */
5259	"image/png": "TODO",
5260
5261	"sheet": "js"
5262}/*:any*/);
5263
5264var CT_LIST = {
5265	workbooks: {
5266		xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
5267		xlsm: "application/vnd.ms-excel.sheet.macroEnabled.main+xml",
5268		xlsb: "application/vnd.ms-excel.sheet.binary.macroEnabled.main",
5269		xlam: "application/vnd.ms-excel.addin.macroEnabled.main+xml",
5270		xltx: "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"
5271	},
5272	strs: { /* Shared Strings */
5273		xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
5274		xlsb: "application/vnd.ms-excel.sharedStrings"
5275	},
5276	comments: { /* Comments */
5277		xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
5278		xlsb: "application/vnd.ms-excel.comments"
5279	},
5280	sheets: { /* Worksheet */
5281		xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
5282		xlsb: "application/vnd.ms-excel.worksheet"
5283	},
5284	charts: { /* Chartsheet */
5285		xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml",
5286		xlsb: "application/vnd.ms-excel.chartsheet"
5287	},
5288	dialogs: { /* Dialogsheet */
5289		xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml",
5290		xlsb: "application/vnd.ms-excel.dialogsheet"
5291	},
5292	macros: { /* Macrosheet (Excel 4.0 Macros) */
5293		xlsx: "application/vnd.ms-excel.macrosheet+xml",
5294		xlsb: "application/vnd.ms-excel.macrosheet"
5295	},
5296	metadata: { /* Metadata (Stock/Geography and Dynamic Array) */
5297		xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml",
5298		xlsb: "application/vnd.ms-excel.sheetMetadata"
5299	},
5300	styles: { /* Styles */
5301		xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
5302		xlsb: "application/vnd.ms-excel.styles"
5303	}
5304};
5305
5306function new_ct()/*:any*/ {
5307	return ({
5308		workbooks:[], sheets:[], charts:[], dialogs:[], macros:[],
5309		rels:[], strs:[], comments:[], threadedcomments:[], links:[],
5310		coreprops:[], extprops:[], custprops:[], themes:[], styles:[],
5311		calcchains:[], vba: [], drawings: [], metadata: [], people:[],
5312		TODO:[], xmlns: "" }/*:any*/);
5313}
5314
5315function parse_ct(data/*:?string*/) {
5316	var ct = new_ct();
5317	if(!data || !data.match) return ct;
5318	var ctext = {};
5319	(data.match(tagregex)||[]).forEach(function(x) {
5320		var y = parsexmltag(x);
5321		switch(y[0].replace(nsregex,"<")) {
5322			case '<?xml': break;
5323			case '<Types': ct.xmlns = y['xmlns' + (y[0].match(/<(\w+):/)||["",""])[1] ]; break;
5324			case '<Default': ctext[y.Extension.toLowerCase()] = y.ContentType; break;
5325			case '<Override':
5326				if(ct[ct2type[y.ContentType]] !== undefined) ct[ct2type[y.ContentType]].push(y.PartName);
5327				break;
5328		}
5329	});
5330	if(ct.xmlns !== XMLNS.CT) throw new Error("Unknown Namespace: " + ct.xmlns);
5331	ct.calcchain = ct.calcchains.length > 0 ? ct.calcchains[0] : "";
5332	ct.sst = ct.strs.length > 0 ? ct.strs[0] : "";
5333	ct.style = ct.styles.length > 0 ? ct.styles[0] : "";
5334	ct.defaults = ctext;
5335	delete ct.calcchains;
5336	return ct;
5337}
5338
5339function write_ct(ct, opts, raw)/*:string*/ {
5340	var type2ct/*{[string]:Array<string>}*/ = evert_arr(ct2type);
5341
5342	var o/*:Array<string>*/ = [], v;
5343
5344	if(!raw) {
5345		o[o.length] = (XML_HEADER);
5346		o[o.length] = writextag('Types', null, {
5347			'xmlns': XMLNS.CT,
5348			'xmlns:xsd': XMLNS.xsd,
5349			'xmlns:xsi': XMLNS.xsi
5350		});
5351		o = o.concat([
5352			['xml', 'application/xml'],
5353			['bin', 'application/vnd.ms-excel.sheet.binary.macroEnabled.main'],
5354			['vml', 'application/vnd.openxmlformats-officedocument.vmlDrawing'],
5355			['data', 'application/vnd.openxmlformats-officedocument.model+data'],
5356			/* from test files */
5357			['bmp', 'image/bmp'],
5358			['png', 'image/png'],
5359			['gif', 'image/gif'],
5360			['emf', 'image/x-emf'],
5361			['wmf', 'image/x-wmf'],
5362			['jpg', 'image/jpeg'], ['jpeg', 'image/jpeg'],
5363			['tif', 'image/tiff'], ['tiff', 'image/tiff'],
5364			['pdf', 'application/pdf'],
5365			['rels', 'application/vnd.openxmlformats-package.relationships+xml']
5366		].map(function(x) {
5367			return writextag('Default', null, {'Extension':x[0], 'ContentType': x[1]});
5368		}));
5369	}
5370
5371	/* only write first instance */
5372	var f1 = function(w) {
5373		if(ct[w] && ct[w].length > 0) {
5374			v = ct[w][0];
5375			o[o.length] = (writextag('Override', null, {
5376				'PartName': (v[0] == '/' ? "":"/") + v,
5377				'ContentType': CT_LIST[w][opts.bookType] || CT_LIST[w]['xlsx']
5378			}));
5379		}
5380	};
5381
5382	/* book type-specific */
5383	var f2 = function(w) {
5384		(ct[w]||[]).forEach(function(v) {
5385			o[o.length] = (writextag('Override', null, {
5386				'PartName': (v[0] == '/' ? "":"/") + v,
5387				'ContentType': CT_LIST[w][opts.bookType] || CT_LIST[w]['xlsx']
5388			}));
5389		});
5390	};
5391
5392	/* standard type */
5393	var f3 = function(t) {
5394		(ct[t]||[]).forEach(function(v) {
5395			o[o.length] = (writextag('Override', null, {
5396				'PartName': (v[0] == '/' ? "":"/") + v,
5397				'ContentType': type2ct[t][0]
5398			}));
5399		});
5400	};
5401
5402	f1('workbooks');
5403	f2('sheets');
5404	f2('charts');
5405	f3('themes');
5406	['strs', 'styles'].forEach(f1);
5407	['coreprops', 'extprops', 'custprops'].forEach(f3);
5408	f3('vba');
5409	f3('comments');
5410	f3('threadedcomments');
5411	f3('drawings');
5412	f2('metadata');
5413	f3('people');
5414	if(!raw && o.length>2){ o[o.length] = ('</Types>'); o[1]=o[1].replace("/>",">"); }
5415	return o.join("");
5416}
5417/* 9.3 Relationships */
5418var RELS = ({
5419	WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
5420	SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
5421	HLINK: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
5422	VML: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing",
5423	XPATH: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLinkPath",
5424	XMISS: "http://schemas.microsoft.com/office/2006/relationships/xlExternalLinkPath/xlPathMissing",
5425	XLINK: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink",
5426	CXML: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml",
5427	CXMLP: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps",
5428	CMNT: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
5429	CORE_PROPS: "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties",
5430	EXT_PROPS: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties',
5431	CUST_PROPS: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties',
5432	SST: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
5433	STY: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
5434	THEME: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",
5435	CHART: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
5436	CHARTEX: "http://schemas.microsoft.com/office/2014/relationships/chartEx",
5437	CS: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet",
5438	WS: [
5439		"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
5440		"http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet"
5441	],
5442	DS: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet",
5443	MS: "http://schemas.microsoft.com/office/2006/relationships/xlMacrosheet",
5444	IMG: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
5445	DRAW: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
5446	XLMETA: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
5447	TCMNT: "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment",
5448	PEOPLE: "http://schemas.microsoft.com/office/2017/10/relationships/person",
5449	CONN: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections",
5450	VBA: "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
5451}/*:any*/);
5452
5453/* 9.3.3 Representing Relationships */
5454function get_rels_path(file/*:string*/)/*:string*/ {
5455	var n = file.lastIndexOf("/");
5456	return file.slice(0,n+1) + '_rels/' + file.slice(n+1) + ".rels";
5457}
5458
5459function parse_rels(data/*:?string*/, currentFilePath/*:string*/) {
5460	var rels = {"!id":{}};
5461	if (!data) return rels;
5462	if (currentFilePath.charAt(0) !== '/') {
5463		currentFilePath = '/'+currentFilePath;
5464	}
5465	var hash = {};
5466
5467	(data.match(tagregex)||[]).forEach(function(x) {
5468		var y = parsexmltag(x);
5469		/* 9.3.2.2 OPC_Relationships */
5470		if (y[0] === '<Relationship') {
5471			var rel = {}; rel.Type = y.Type; rel.Target = y.Target; rel.Id = y.Id; if(y.TargetMode) rel.TargetMode = y.TargetMode;
5472			var canonictarget = y.TargetMode === 'External' ? y.Target : resolve_path(y.Target, currentFilePath);
5473			rels[canonictarget] = rel;
5474			hash[y.Id] = rel;
5475		}
5476	});
5477	rels["!id"] = hash;
5478	return rels;
5479}
5480
5481
5482/* TODO */
5483function write_rels(rels)/*:string*/ {
5484	var o = [XML_HEADER, writextag('Relationships', null, {
5485		//'xmlns:ns0': XMLNS.RELS,
5486		'xmlns': XMLNS.RELS
5487	})];
5488	keys(rels['!id']).forEach(function(rid) {
5489		o[o.length] = (writextag('Relationship', null, rels['!id'][rid]));
5490	});
5491	if(o.length>2){ o[o.length] = ('</Relationships>'); o[1]=o[1].replace("/>",">"); }
5492	return o.join("");
5493}
5494
5495function add_rels(rels, rId/*:number*/, f, type, relobj, targetmode/*:?string*/)/*:number*/ {
5496	if(!relobj) relobj = {};
5497	if(!rels['!id']) rels['!id'] = {};
5498	if(!rels['!idx']) rels['!idx'] = 1;
5499	if(rId < 0) for(rId = rels['!idx']; rels['!id']['rId' + rId]; ++rId){/* empty */}
5500	rels['!idx'] = rId + 1;
5501	relobj.Id = 'rId' + rId;
5502	relobj.Type = type;
5503	relobj.Target = f;
5504	if(targetmode) relobj.TargetMode = targetmode;
5505	else if([RELS.HLINK, RELS.XPATH, RELS.XMISS].indexOf(relobj.Type) > -1) relobj.TargetMode = "External";
5506	if(rels['!id'][relobj.Id]) throw new Error("Cannot rewrite rId " + rId);
5507	rels['!id'][relobj.Id] = relobj;
5508	rels[('/' + relobj.Target).replace("//","/")] = relobj;
5509	return rId;
5510}
5511/* Open Document Format for Office Applications (OpenDocument) Version 1.2 */
5512/* Part 3 Section 4 Manifest File */
5513var CT_ODS = "application/vnd.oasis.opendocument.spreadsheet";
5514function parse_manifest(d, opts) {
5515	var str = xlml_normalize(d);
5516	var Rn;
5517	var FEtag;
5518	while((Rn = xlmlregex.exec(str))) switch(Rn[3]) {
5519		case 'manifest': break; // 4.2 <manifest:manifest>
5520		case 'file-entry': // 4.3 <manifest:file-entry>
5521			FEtag = parsexmltag(Rn[0], false);
5522			if(FEtag.path == '/' && FEtag.type !== CT_ODS) throw new Error("This OpenDocument is not a spreadsheet");
5523			break;
5524		case 'encryption-data': // 4.4 <manifest:encryption-data>
5525		case 'algorithm': // 4.5 <manifest:algorithm>
5526		case 'start-key-generation': // 4.6 <manifest:start-key-generation>
5527		case 'key-derivation': // 4.7 <manifest:key-derivation>
5528			throw new Error("Unsupported ODS Encryption");
5529		default: if(opts && opts.WTF) throw Rn;
5530	}
5531}
5532
5533function write_manifest(manifest/*:Array<Array<string> >*/)/*:string*/ {
5534	var o = [XML_HEADER];
5535	o.push('<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2">\n');
5536	o.push('  <manifest:file-entry manifest:full-path="/" manifest:version="1.2" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>\n');
5537	for(var i = 0; i < manifest.length; ++i) o.push('  <manifest:file-entry manifest:full-path="' + manifest[i][0] + '" manifest:media-type="' + manifest[i][1] + '"/>\n');
5538	o.push('</manifest:manifest>');
5539	return o.join("");
5540}
5541
5542/* Part 3 Section 6 Metadata Manifest File */
5543function write_rdf_type(file/*:string*/, res/*:string*/, tag/*:?string*/) {
5544	return [
5545		'  <rdf:Description rdf:about="' + file + '">\n',
5546		'    <rdf:type rdf:resource="http://docs.oasis-open.org/ns/office/1.2/meta/' + (tag || "odf") + '#' + res + '"/>\n',
5547		'  </rdf:Description>\n'
5548	].join("");
5549}
5550function write_rdf_has(base/*:string*/, file/*:string*/) {
5551	return [
5552		'  <rdf:Description rdf:about="' + base + '">\n',
5553		'    <ns0:hasPart xmlns:ns0="http://docs.oasis-open.org/ns/office/1.2/meta/pkg#" rdf:resource="' + file + '"/>\n',
5554		'  </rdf:Description>\n'
5555	].join("");
5556}
5557function write_rdf(rdf) {
5558	var o = [XML_HEADER];
5559	o.push('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n');
5560	for(var i = 0; i != rdf.length; ++i) {
5561		o.push(write_rdf_type(rdf[i][0], rdf[i][1]));
5562		o.push(write_rdf_has("",rdf[i][0]));
5563	}
5564	o.push(write_rdf_type("","Document", "pkg"));
5565	o.push('</rdf:RDF>');
5566	return o.join("");
5567}
5568/* TODO: pull properties */
5569function write_meta_ods(/*:: wb: Workbook, opts: any*/)/*:string*/ {
5570	return '<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xlink="http://www.w3.org/1999/xlink" office:version="1.2"><office:meta><meta:generator>Sheet' + 'JS ' + XLSX.version + '</meta:generator></office:meta></office:document-meta>';
5571}
5572
5573/* ECMA-376 Part II 11.1 Core Properties Part */
5574/* [MS-OSHARED] 2.3.3.2.[1-2].1 (PIDSI/PIDDSI) */
5575var CORE_PROPS/*:Array<Array<string> >*/ = [
5576	["cp:category", "Category"],
5577	["cp:contentStatus", "ContentStatus"],
5578	["cp:keywords", "Keywords"],
5579	["cp:lastModifiedBy", "LastAuthor"],
5580	["cp:lastPrinted", "LastPrinted"],
5581	["cp:revision", "RevNumber"],
5582	["cp:version", "Version"],
5583	["dc:creator", "Author"],
5584	["dc:description", "Comments"],
5585	["dc:identifier", "Identifier"],
5586	["dc:language", "Language"],
5587	["dc:subject", "Subject"],
5588	["dc:title", "Title"],
5589	["dcterms:created", "CreatedDate", 'date'],
5590	["dcterms:modified", "ModifiedDate", 'date']
5591];
5592
5593var CORE_PROPS_REGEX/*:Array<RegExp>*/ = /*#__PURE__*/(function() {
5594	var r = new Array(CORE_PROPS.length);
5595	for(var i = 0; i < CORE_PROPS.length; ++i) {
5596		var f = CORE_PROPS[i];
5597		var g = "(?:"+ f[0].slice(0,f[0].indexOf(":")) +":)"+ f[0].slice(f[0].indexOf(":")+1);
5598		r[i] = new RegExp("<" + g + "[^>]*>([\\s\\S]*?)<\/" + g + ">");
5599	}
5600	return r;
5601})();
5602
5603function parse_core_props(data) {
5604	var p = {};
5605	data = utf8read(data);
5606
5607	for(var i = 0; i < CORE_PROPS.length; ++i) {
5608		var f = CORE_PROPS[i], cur = data.match(CORE_PROPS_REGEX[i]);
5609		if(cur != null && cur.length > 0) p[f[1]] = unescapexml(cur[1]);
5610		if(f[2] === 'date' && p[f[1]]) p[f[1]] = parseDate(p[f[1]]);
5611	}
5612
5613	return p;
5614}
5615
5616function cp_doit(f, g, h, o, p) {
5617	if(p[f] != null || g == null || g === "") return;
5618	p[f] = g;
5619	g = escapexml(g);
5620	o[o.length] = (h ? writextag(f,g,h) : writetag(f,g));
5621}
5622
5623function write_core_props(cp, _opts) {
5624	var opts = _opts || {};
5625	var o = [XML_HEADER, writextag('cp:coreProperties', null, {
5626		//'xmlns': XMLNS.CORE_PROPS,
5627		'xmlns:cp': XMLNS.CORE_PROPS,
5628		'xmlns:dc': XMLNS.dc,
5629		'xmlns:dcterms': XMLNS.dcterms,
5630		'xmlns:dcmitype': XMLNS.dcmitype,
5631		'xmlns:xsi': XMLNS.xsi
5632	})], p = {};
5633	if(!cp && !opts.Props) return o.join("");
5634
5635	if(cp) {
5636		if(cp.CreatedDate != null) cp_doit("dcterms:created", typeof cp.CreatedDate === "string" ? cp.CreatedDate : write_w3cdtf(cp.CreatedDate, opts.WTF), {"xsi:type":"dcterms:W3CDTF"}, o, p);
5637		if(cp.ModifiedDate != null) cp_doit("dcterms:modified", typeof cp.ModifiedDate === "string" ? cp.ModifiedDate : write_w3cdtf(cp.ModifiedDate, opts.WTF), {"xsi:type":"dcterms:W3CDTF"}, o, p);
5638	}
5639
5640	for(var i = 0; i != CORE_PROPS.length; ++i) {
5641		var f = CORE_PROPS[i];
5642		var v = opts.Props && opts.Props[f[1]] != null ? opts.Props[f[1]] : cp ? cp[f[1]] : null;
5643		if(v === true) v = "1";
5644		else if(v === false) v = "0";
5645		else if(typeof v == "number") v = String(v);
5646		if(v != null) cp_doit(f[0], v, null, o, p);
5647	}
5648	if(o.length>2){ o[o.length] = ('</cp:coreProperties>'); o[1]=o[1].replace("/>",">"); }
5649	return o.join("");
5650}
5651/* 15.2.12.3 Extended File Properties Part */
5652/* [MS-OSHARED] 2.3.3.2.[1-2].1 (PIDSI/PIDDSI) */
5653var EXT_PROPS/*:Array<Array<string> >*/ = [
5654	["Application", "Application", "string"],
5655	["AppVersion", "AppVersion", "string"],
5656	["Company", "Company", "string"],
5657	["DocSecurity", "DocSecurity", "string"],
5658	["Manager", "Manager", "string"],
5659	["HyperlinksChanged", "HyperlinksChanged", "bool"],
5660	["SharedDoc", "SharedDoc", "bool"],
5661	["LinksUpToDate", "LinksUpToDate", "bool"],
5662	["ScaleCrop", "ScaleCrop", "bool"],
5663	["HeadingPairs", "HeadingPairs", "raw"],
5664	["TitlesOfParts", "TitlesOfParts", "raw"]
5665];
5666
5667var PseudoPropsPairs = [
5668	"Worksheets",  "SheetNames",
5669	"NamedRanges", "DefinedNames",
5670	"Chartsheets", "ChartNames"
5671];
5672function load_props_pairs(HP/*:string|Array<Array<any>>*/, TOP, props, opts) {
5673	var v = [];
5674	if(typeof HP == "string") v = parseVector(HP, opts);
5675	else for(var j = 0; j < HP.length; ++j) v = v.concat(HP[j].map(function(hp) { return {v:hp}; }));
5676	var parts = (typeof TOP == "string") ? parseVector(TOP, opts).map(function (x) { return x.v; }) : TOP;
5677	var idx = 0, len = 0;
5678	if(parts.length > 0) for(var i = 0; i !== v.length; i += 2) {
5679		len = +(v[i+1].v);
5680		switch(v[i].v) {
5681			case "Worksheets":
5682			case "工作表":
5683			case "Листы":
5684			case "أوراق العمل":
5685			case "ワークシート":
5686			case "גליונות עבודה":
5687			case "Arbeitsblätter":
5688			case "Çalışma Sayfaları":
5689			case "Feuilles de calcul":
5690			case "Fogli di lavoro":
5691			case "Folhas de cálculo":
5692			case "Planilhas":
5693			case "Regneark":
5694			case "Hojas de cálculo":
5695			case "Werkbladen":
5696				props.Worksheets = len;
5697				props.SheetNames = parts.slice(idx, idx + len);
5698				break;
5699
5700			case "Named Ranges":
5701			case "Rangos con nombre":
5702			case "名前付き一覧":
5703			case "Benannte Bereiche":
5704			case "Navngivne områder":
5705				props.NamedRanges = len;
5706				props.DefinedNames = parts.slice(idx, idx + len);
5707				break;
5708
5709			case "Charts":
5710			case "Diagramme":
5711				props.Chartsheets = len;
5712				props.ChartNames = parts.slice(idx, idx + len);
5713				break;
5714		}
5715		idx += len;
5716	}
5717}
5718
5719function parse_ext_props(data, p, opts) {
5720	var q = {}; if(!p) p = {};
5721	data = utf8read(data);
5722
5723	EXT_PROPS.forEach(function(f) {
5724		var xml = (data.match(matchtag(f[0]))||[])[1];
5725		switch(f[2]) {
5726			case "string": if(xml) p[f[1]] = unescapexml(xml); break;
5727			case "bool": p[f[1]] = xml === "true"; break;
5728			case "raw":
5729				var cur = data.match(new RegExp("<" + f[0] + "[^>]*>([\\s\\S]*?)<\/" + f[0] + ">"));
5730				if(cur && cur.length > 0) q[f[1]] = cur[1];
5731				break;
5732		}
5733	});
5734
5735	if(q.HeadingPairs && q.TitlesOfParts) load_props_pairs(q.HeadingPairs, q.TitlesOfParts, p, opts);
5736
5737	return p;
5738}
5739
5740function write_ext_props(cp/*::, opts*/)/*:string*/ {
5741	var o/*:Array<string>*/ = [], W = writextag;
5742	if(!cp) cp = {};
5743	cp.Application = "SheetJS";
5744	o[o.length] = (XML_HEADER);
5745	o[o.length] = (writextag('Properties', null, {
5746		'xmlns': XMLNS.EXT_PROPS,
5747		'xmlns:vt': XMLNS.vt
5748	}));
5749
5750	EXT_PROPS.forEach(function(f) {
5751		if(cp[f[1]] === undefined) return;
5752		var v;
5753		switch(f[2]) {
5754			case 'string': v = escapexml(String(cp[f[1]])); break;
5755			case 'bool': v = cp[f[1]] ? 'true' : 'false'; break;
5756		}
5757		if(v !== undefined) o[o.length] = (W(f[0], v));
5758	});
5759
5760	/* TODO: HeadingPairs, TitlesOfParts */
5761	o[o.length] = (W('HeadingPairs', W('vt:vector', W('vt:variant', '<vt:lpstr>Worksheets</vt:lpstr>')+W('vt:variant', W('vt:i4', String(cp.Worksheets))), {size:2, baseType:"variant"})));
5762	o[o.length] = (W('TitlesOfParts', W('vt:vector', cp.SheetNames.map(function(s) { return "<vt:lpstr>" + escapexml(s) + "</vt:lpstr>"; }).join(""), {size: cp.Worksheets, baseType:"lpstr"})));
5763	if(o.length>2){ o[o.length] = ('</Properties>'); o[1]=o[1].replace("/>",">"); }
5764	return o.join("");
5765}
5766/* 15.2.12.2 Custom File Properties Part */
5767var custregex = /<[^>]+>[^<]*/g;
5768function parse_cust_props(data/*:string*/, opts) {
5769	var p = {}, name = "";
5770	var m = data.match(custregex);
5771	if(m) for(var i = 0; i != m.length; ++i) {
5772		var x = m[i], y = parsexmltag(x);
5773		switch(strip_ns(y[0])) {
5774			case '<?xml': break;
5775			case '<Properties': break;
5776			case '<property': name = unescapexml(y.name); break;
5777			case '</property>': name = null; break;
5778			default: if (x.indexOf('<vt:') === 0) {
5779				var toks = x.split('>');
5780				var type = toks[0].slice(4), text = toks[1];
5781				/* 22.4.2.32 (CT_Variant). Omit the binary types from 22.4 (Variant Types) */
5782				switch(type) {
5783					case 'lpstr': case 'bstr': case 'lpwstr':
5784						p[name] = unescapexml(text);
5785						break;
5786					case 'bool':
5787						p[name] = parsexmlbool(text);
5788						break;
5789					case 'i1': case 'i2': case 'i4': case 'i8': case 'int': case 'uint':
5790						p[name] = parseInt(text, 10);
5791						break;
5792					case 'r4': case 'r8': case 'decimal':
5793						p[name] = parseFloat(text);
5794						break;
5795					case 'filetime': case 'date':
5796						p[name] = parseDate(text);
5797						break;
5798					case 'cy': case 'error':
5799						p[name] = unescapexml(text);
5800						break;
5801					default:
5802						if(type.slice(-1) == '/') break;
5803						if(opts.WTF && typeof console !== 'undefined') console.warn('Unexpected', x, type, toks);
5804				}
5805			} else if(x.slice(0,2) === "</") {/* empty */
5806			} else if(opts.WTF) throw new Error(x);
5807		}
5808	}
5809	return p;
5810}
5811
5812function write_cust_props(cp/*::, opts*/)/*:string*/ {
5813	var o = [XML_HEADER, writextag('Properties', null, {
5814		'xmlns': XMLNS.CUST_PROPS,
5815		'xmlns:vt': XMLNS.vt
5816	})];
5817	if(!cp) return o.join("");
5818	var pid = 1;
5819	keys(cp).forEach(function custprop(k) { ++pid;
5820		o[o.length] = (writextag('property', write_vt(cp[k], true), {
5821			'fmtid': '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}',
5822			'pid': pid,
5823			'name': escapexml(k)
5824		}));
5825	});
5826	if(o.length>2){ o[o.length] = '</Properties>'; o[1]=o[1].replace("/>",">"); }
5827	return o.join("");
5828}
5829/* Common Name -> XLML Name */
5830var XLMLDocPropsMap = {
5831	Title: 'Title',
5832	Subject: 'Subject',
5833	Author: 'Author',
5834	Keywords: 'Keywords',
5835	Comments: 'Description',
5836	LastAuthor: 'LastAuthor',
5837	RevNumber: 'Revision',
5838	Application: 'AppName',
5839	/* TotalTime: 'TotalTime', */
5840	LastPrinted: 'LastPrinted',
5841	CreatedDate: 'Created',
5842	ModifiedDate: 'LastSaved',
5843	/* Pages */
5844	/* Words */
5845	/* Characters */
5846	Category: 'Category',
5847	/* PresentationFormat */
5848	Manager: 'Manager',
5849	Company: 'Company',
5850	/* Guid */
5851	/* HyperlinkBase */
5852	/* Bytes */
5853	/* Lines */
5854	/* Paragraphs */
5855	/* CharactersWithSpaces */
5856	AppVersion: 'Version',
5857
5858	ContentStatus: 'ContentStatus', /* NOTE: missing from schema */
5859	Identifier: 'Identifier', /* NOTE: missing from schema */
5860	Language: 'Language' /* NOTE: missing from schema */
5861};
5862var evert_XLMLDPM;
5863
5864function xlml_set_prop(Props, tag/*:string*/, val) {
5865	if(!evert_XLMLDPM) evert_XLMLDPM = evert(XLMLDocPropsMap);
5866	tag = evert_XLMLDPM[tag] || tag;
5867	Props[tag] = val;
5868}
5869
5870function xlml_write_docprops(Props, opts) {
5871	var o/*:Array<string>*/ = [];
5872	keys(XLMLDocPropsMap).map(function(m) {
5873		for(var i = 0; i < CORE_PROPS.length; ++i) if(CORE_PROPS[i][1] == m) return CORE_PROPS[i];
5874		for(i = 0; i < EXT_PROPS.length; ++i) if(EXT_PROPS[i][1] == m) return EXT_PROPS[i];
5875		throw m;
5876	}).forEach(function(p) {
5877		if(Props[p[1]] == null) return;
5878		var m = opts && opts.Props && opts.Props[p[1]] != null ? opts.Props[p[1]] : Props[p[1]];
5879		switch(p[2]) {
5880			case 'date': m = new Date(m).toISOString().replace(/\.\d*Z/,"Z"); break;
5881		}
5882		if(typeof m == 'number') m = String(m);
5883		else if(m === true || m === false) { m = m ? "1" : "0"; }
5884		else if(m instanceof Date) m = new Date(m).toISOString().replace(/\.\d*Z/,"");
5885		o.push(writetag(XLMLDocPropsMap[p[1]] || p[1], m));
5886	});
5887	return writextag('DocumentProperties', o.join(""), {xmlns:XLMLNS.o });
5888}
5889function xlml_write_custprops(Props, Custprops/*::, opts*/) {
5890	var BLACKLIST = ["Worksheets","SheetNames"];
5891	var T = 'CustomDocumentProperties';
5892	var o/*:Array<string>*/ = [];
5893	if(Props) keys(Props).forEach(function(k) {
5894		/*:: if(!Props) return; */
5895		if(!Object.prototype.hasOwnProperty.call(Props, k)) return;
5896		for(var i = 0; i < CORE_PROPS.length; ++i) if(k == CORE_PROPS[i][1]) return;
5897		for(i = 0; i < EXT_PROPS.length; ++i) if(k == EXT_PROPS[i][1]) return;
5898		for(i = 0; i < BLACKLIST.length; ++i) if(k == BLACKLIST[i]) return;
5899
5900		var m = Props[k];
5901		var t = "string";
5902		if(typeof m == 'number') { t = "float"; m = String(m); }
5903		else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; }
5904		else m = String(m);
5905		o.push(writextag(escapexmltag(k), m, {"dt:dt":t}));
5906	});
5907	if(Custprops) keys(Custprops).forEach(function(k) {
5908		/*:: if(!Custprops) return; */
5909		if(!Object.prototype.hasOwnProperty.call(Custprops, k)) return;
5910		if(Props && Object.prototype.hasOwnProperty.call(Props, k)) return;
5911		var m = Custprops[k];
5912		var t = "string";
5913		if(typeof m == 'number') { t = "float"; m = String(m); }
5914		else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; }
5915		else if(m instanceof Date) { t = "dateTime.tz"; m = m.toISOString(); }
5916		else m = String(m);
5917		o.push(writextag(escapexmltag(k), m, {"dt:dt":t}));
5918	});
5919	return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + '</' + T + '>';
5920}
5921/* [MS-DTYP] 2.3.3 FILETIME */
5922/* [MS-OLEDS] 2.1.3 FILETIME (Packet Version) */
5923/* [MS-OLEPS] 2.8 FILETIME (Packet Version) */
5924function parse_FILETIME(blob) {
5925	var dwLowDateTime = blob.read_shift(4), dwHighDateTime = blob.read_shift(4);
5926	return new Date(((dwHighDateTime/1e7*Math.pow(2,32) + dwLowDateTime/1e7) - 11644473600)*1000).toISOString().replace(/\.000/,"");
5927}
5928function write_FILETIME(time/*:string|Date*/) {
5929	var date = (typeof time == "string") ? new Date(Date.parse(time)) : time;
5930	var t = date.getTime() / 1000 + 11644473600;
5931	var l = t % Math.pow(2,32), h = (t - l) / Math.pow(2,32);
5932	l *= 1e7; h *= 1e7;
5933	var w = (l / Math.pow(2,32)) | 0;
5934	if(w > 0) { l = l % Math.pow(2,32); h += w; }
5935	var o = new_buf(8); o.write_shift(4, l); o.write_shift(4, h); return o;
5936}
5937
5938/* [MS-OSHARED] 2.3.3.1.4 Lpstr */
5939function parse_lpstr(blob, type, pad/*:?number*/) {
5940	var start = blob.l;
5941	var str = blob.read_shift(0, 'lpstr-cp');
5942	if(pad) while((blob.l - start) & 3) ++blob.l;
5943	return str;
5944}
5945
5946/* [MS-OSHARED] 2.3.3.1.6 Lpwstr */
5947function parse_lpwstr(blob, type, pad) {
5948	var str = blob.read_shift(0, 'lpwstr');
5949	if(pad) blob.l += (4 - ((str.length+1) & 3)) & 3;
5950	return str;
5951}
5952
5953
5954/* [MS-OSHARED] 2.3.3.1.11 VtString */
5955/* [MS-OSHARED] 2.3.3.1.12 VtUnalignedString */
5956function parse_VtStringBase(blob, stringType, pad) {
5957	if(stringType === 0x1F /*VT_LPWSTR*/) return parse_lpwstr(blob);
5958	return parse_lpstr(blob, stringType, pad);
5959}
5960
5961function parse_VtString(blob, t/*:number*/, pad/*:?boolean*/) { return parse_VtStringBase(blob, t, pad === false ? 0: 4); }
5962function parse_VtUnalignedString(blob, t/*:number*/) { if(!t) throw new Error("VtUnalignedString must have positive length"); return parse_VtStringBase(blob, t, 0); }
5963
5964/* [MS-OSHARED] 2.3.3.1.7 VtVecLpwstrValue */
5965function parse_VtVecLpwstrValue(blob)/*:Array<string>*/ {
5966	var length = blob.read_shift(4);
5967	var ret/*:Array<string>*/ = [];
5968	for(var i = 0; i != length; ++i) {
5969		var start = blob.l;
5970		ret[i] = blob.read_shift(0, 'lpwstr').replace(chr0,'');
5971		if((blob.l - start) & 0x02) blob.l += 2;
5972	}
5973	return ret;
5974}
5975
5976/* [MS-OSHARED] 2.3.3.1.9 VtVecUnalignedLpstrValue */
5977function parse_VtVecUnalignedLpstrValue(blob)/*:Array<string>*/ {
5978	var length = blob.read_shift(4);
5979	var ret/*:Array<string>*/ = [];
5980	for(var i = 0; i != length; ++i) ret[i] = blob.read_shift(0, 'lpstr-cp').replace(chr0,'');
5981	return ret;
5982}
5983
5984
5985/* [MS-OSHARED] 2.3.3.1.13 VtHeadingPair */
5986function parse_VtHeadingPair(blob) {
5987	var start = blob.l;
5988	var headingString = parse_TypedPropertyValue(blob, VT_USTR);
5989	if(blob[blob.l] == 0x00 && blob[blob.l+1] == 0x00 && ((blob.l - start) & 0x02)) blob.l += 2;
5990	var headerParts = parse_TypedPropertyValue(blob, VT_I4);
5991	return [headingString, headerParts];
5992}
5993
5994/* [MS-OSHARED] 2.3.3.1.14 VtVecHeadingPairValue */
5995function parse_VtVecHeadingPairValue(blob) {
5996	var cElements = blob.read_shift(4);
5997	var out = [];
5998	for(var i = 0; i < cElements / 2; ++i) out.push(parse_VtHeadingPair(blob));
5999	return out;
6000}
6001
6002/* [MS-OLEPS] 2.18.1 Dictionary (uses 2.17, 2.16) */
6003function parse_dictionary(blob,CodePage) {
6004	var cnt = blob.read_shift(4);
6005	var dict/*:{[number]:string}*/ = ({}/*:any*/);
6006	for(var j = 0; j != cnt; ++j) {
6007		var pid = blob.read_shift(4);
6008		var len = blob.read_shift(4);
6009		dict[pid] = blob.read_shift(len, (CodePage === 0x4B0 ?'utf16le':'utf8')).replace(chr0,'').replace(chr1,'!');
6010		if(CodePage === 0x4B0 && (len % 2)) blob.l += 2;
6011	}
6012	if(blob.l & 3) blob.l = (blob.l>>2+1)<<2;
6013	return dict;
6014}
6015
6016/* [MS-OLEPS] 2.9 BLOB */
6017function parse_BLOB(blob) {
6018	var size = blob.read_shift(4);
6019	var bytes = blob.slice(blob.l,blob.l+size);
6020	blob.l += size;
6021	if((size & 3) > 0) blob.l += (4 - (size & 3)) & 3;
6022	return bytes;
6023}
6024
6025/* [MS-OLEPS] 2.11 ClipboardData */
6026function parse_ClipboardData(blob) {
6027	// TODO
6028	var o = {};
6029	o.Size = blob.read_shift(4);
6030	//o.Format = blob.read_shift(4);
6031	blob.l += o.Size + 3 - (o.Size - 1) % 4;
6032	return o;
6033}
6034
6035/* [MS-OLEPS] 2.15 TypedPropertyValue */
6036function parse_TypedPropertyValue(blob, type/*:number*/, _opts)/*:any*/ {
6037	var t = blob.read_shift(2), ret, opts = _opts||{};
6038	blob.l += 2;
6039	if(type !== VT_VARIANT)
6040	if(t !== type && VT_CUSTOM.indexOf(type)===-1 && !((type & 0xFFFE) == 0x101E && (t & 0xFFFE) == 0x101E)) throw new Error('Expected type ' + type + ' saw ' + t);
6041	switch(type === VT_VARIANT ? t : type) {
6042		case 0x02 /*VT_I2*/: ret = blob.read_shift(2, 'i'); if(!opts.raw) blob.l += 2; return ret;
6043		case 0x03 /*VT_I4*/: ret = blob.read_shift(4, 'i'); return ret;
6044		case 0x0B /*VT_BOOL*/: return blob.read_shift(4) !== 0x0;
6045		case 0x13 /*VT_UI4*/: ret = blob.read_shift(4); return ret;
6046		case 0x1E /*VT_LPSTR*/: return parse_lpstr(blob, t, 4).replace(chr0,'');
6047		case 0x1F /*VT_LPWSTR*/: return parse_lpwstr(blob);
6048		case 0x40 /*VT_FILETIME*/: return parse_FILETIME(blob);
6049		case 0x41 /*VT_BLOB*/: return parse_BLOB(blob);
6050		case 0x47 /*VT_CF*/: return parse_ClipboardData(blob);
6051		case 0x50 /*VT_STRING*/: return parse_VtString(blob, t, !opts.raw).replace(chr0,'');
6052		case 0x51 /*VT_USTR*/: return parse_VtUnalignedString(blob, t/*, 4*/).replace(chr0,'');
6053		case 0x100C /*VT_VECTOR|VT_VARIANT*/: return parse_VtVecHeadingPairValue(blob);
6054		case 0x101E /*VT_VECTOR|VT_LPSTR*/:
6055		case 0x101F /*VT_VECTOR|VT_LPWSTR*/:
6056			return t == 0x101F ? parse_VtVecLpwstrValue(blob) : parse_VtVecUnalignedLpstrValue(blob);
6057		default: throw new Error("TypedPropertyValue unrecognized type " + type + " " + t);
6058	}
6059}
6060function write_TypedPropertyValue(type/*:number*/, value) {
6061	var o = new_buf(4), p = new_buf(4);
6062	o.write_shift(4, type == 0x50 ? 0x1F : type);
6063	switch(type) {
6064		case 0x03 /*VT_I4*/: p.write_shift(-4, value); break;
6065		case 0x05 /*VT_I4*/: p = new_buf(8); p.write_shift(8, value, 'f'); break;
6066		case 0x0B /*VT_BOOL*/: p.write_shift(4, value ? 0x01 : 0x00); break;
6067		case 0x40 /*VT_FILETIME*/: /*:: if(typeof value !== "string" && !(value instanceof Date)) throw "unreachable"; */ p = write_FILETIME(value); break;
6068		case 0x1F /*VT_LPWSTR*/:
6069		case 0x50 /*VT_STRING*/:
6070			/*:: if(typeof value !== "string") throw "unreachable"; */
6071			p = new_buf(4 + 2 * (value.length + 1) + (value.length % 2 ? 0 : 2));
6072			p.write_shift(4, value.length + 1);
6073			p.write_shift(0, value, "dbcs");
6074			while(p.l != p.length) p.write_shift(1, 0);
6075			break;
6076		default: throw new Error("TypedPropertyValue unrecognized type " + type + " " + value);
6077	}
6078	return bconcat([o, p]);
6079}
6080
6081/* [MS-OLEPS] 2.20 PropertySet */
6082function parse_PropertySet(blob, PIDSI) {
6083	var start_addr = blob.l;
6084	var size = blob.read_shift(4);
6085	var NumProps = blob.read_shift(4);
6086	var Props = [], i = 0;
6087	var CodePage = 0;
6088	var Dictionary = -1, DictObj/*:{[number]:string}*/ = ({}/*:any*/);
6089	for(i = 0; i != NumProps; ++i) {
6090		var PropID = blob.read_shift(4);
6091		var Offset = blob.read_shift(4);
6092		Props[i] = [PropID, Offset + start_addr];
6093	}
6094	Props.sort(function(x,y) { return x[1] - y[1]; });
6095	var PropH = {};
6096	for(i = 0; i != NumProps; ++i) {
6097		if(blob.l !== Props[i][1]) {
6098			var fail = true;
6099			if(i>0 && PIDSI) switch(PIDSI[Props[i-1][0]].t) {
6100				case 0x02 /*VT_I2*/: if(blob.l+2 === Props[i][1]) { blob.l+=2; fail = false; } break;
6101				case 0x50 /*VT_STRING*/: if(blob.l <= Props[i][1]) { blob.l=Props[i][1]; fail = false; } break;
6102				case 0x100C /*VT_VECTOR|VT_VARIANT*/: if(blob.l <= Props[i][1]) { blob.l=Props[i][1]; fail = false; } break;
6103			}
6104			if((!PIDSI||i==0) && blob.l <= Props[i][1]) { fail=false; blob.l = Props[i][1]; }
6105			if(fail) throw new Error("Read Error: Expected address " + Props[i][1] + ' at ' + blob.l + ' :' + i);
6106		}
6107		if(PIDSI) {
6108			if(Props[i][0] == 0 && Props.length > i+1 && Props[i][1] == Props[i+1][1]) continue; // R9
6109			var piddsi = PIDSI[Props[i][0]];
6110			PropH[piddsi.n] = parse_TypedPropertyValue(blob, piddsi.t, {raw:true});
6111			if(piddsi.p === 'version') PropH[piddsi.n] = String(PropH[piddsi.n] >> 16) + "." + ("0000" + String(PropH[piddsi.n] & 0xFFFF)).slice(-4);
6112			if(piddsi.n == "CodePage") switch(PropH[piddsi.n]) {
6113				case 0: PropH[piddsi.n] = 1252;
6114					/* falls through */
6115				case 874:
6116				case 932:
6117				case 936:
6118				case 949:
6119				case 950:
6120				case 1250:
6121				case 1251:
6122				case 1253:
6123				case 1254:
6124				case 1255:
6125				case 1256:
6126				case 1257:
6127				case 1258:
6128				case 10000:
6129				case 1200:
6130				case 1201:
6131				case 1252:
6132				case 65000: case -536:
6133				case 65001: case -535:
6134					set_cp(CodePage = (PropH[piddsi.n]>>>0) & 0xFFFF); break;
6135				default: throw new Error("Unsupported CodePage: " + PropH[piddsi.n]);
6136			}
6137		} else {
6138			if(Props[i][0] === 0x1) {
6139				CodePage = PropH.CodePage = (parse_TypedPropertyValue(blob, VT_I2)/*:number*/);
6140				set_cp(CodePage);
6141				if(Dictionary !== -1) {
6142					var oldpos = blob.l;
6143					blob.l = Props[Dictionary][1];
6144					DictObj = parse_dictionary(blob,CodePage);
6145					blob.l = oldpos;
6146				}
6147			} else if(Props[i][0] === 0) {
6148				if(CodePage === 0) { Dictionary = i; blob.l = Props[i+1][1]; continue; }
6149				DictObj = parse_dictionary(blob,CodePage);
6150			} else {
6151				var name = DictObj[Props[i][0]];
6152				var val;
6153				/* [MS-OSHARED] 2.3.3.2.3.1.2 + PROPVARIANT */
6154				switch(blob[blob.l]) {
6155					case 0x41 /*VT_BLOB*/: blob.l += 4; val = parse_BLOB(blob); break;
6156					case 0x1E /*VT_LPSTR*/: blob.l += 4; val = parse_VtString(blob, blob[blob.l-4]).replace(/\u0000+$/,""); break;
6157					case 0x1F /*VT_LPWSTR*/: blob.l += 4; val = parse_VtString(blob, blob[blob.l-4]).replace(/\u0000+$/,""); break;
6158					case 0x03 /*VT_I4*/: blob.l += 4; val = blob.read_shift(4, 'i'); break;
6159					case 0x13 /*VT_UI4*/: blob.l += 4; val = blob.read_shift(4); break;
6160					case 0x05 /*VT_R8*/: blob.l += 4; val = blob.read_shift(8, 'f'); break;
6161					case 0x0B /*VT_BOOL*/: blob.l += 4; val = parsebool(blob, 4); break;
6162					case 0x40 /*VT_FILETIME*/: blob.l += 4; val = parseDate(parse_FILETIME(blob)); break;
6163					default: throw new Error("unparsed value: " + blob[blob.l]);
6164				}
6165				PropH[name] = val;
6166			}
6167		}
6168	}
6169	blob.l = start_addr + size; /* step ahead to skip padding */
6170	return PropH;
6171}
6172var XLSPSSkip = [ "CodePage", "Thumbnail", "_PID_LINKBASE", "_PID_HLINKS", "SystemIdentifier", "FMTID" ];
6173function guess_property_type(val/*:any*/)/*:number*/ {
6174	switch(typeof val) {
6175		case "boolean": return 0x0B;
6176		case "number": return ((val|0)==val) ? 0x03 : 0x05;
6177		case "string": return 0x1F;
6178		case "object": if(val instanceof Date) return 0x40; break;
6179	}
6180	return -1;
6181}
6182function write_PropertySet(entries, RE, PIDSI) {
6183	var hdr = new_buf(8), piao = [], prop = [];
6184	var sz = 8, i = 0;
6185
6186	var pr = new_buf(8), pio = new_buf(8);
6187	pr.write_shift(4, 0x0002);
6188	pr.write_shift(4, 0x04B0);
6189	pio.write_shift(4, 0x0001);
6190	prop.push(pr); piao.push(pio);
6191	sz += 8 + pr.length;
6192
6193	if(!RE) {
6194		pio = new_buf(8);
6195		pio.write_shift(4, 0);
6196		piao.unshift(pio);
6197
6198		var bufs = [new_buf(4)];
6199		bufs[0].write_shift(4, entries.length);
6200		for(i = 0; i < entries.length; ++i) {
6201			var value = entries[i][0];
6202			pr = new_buf(4 + 4 + 2 * (value.length + 1) + (value.length % 2 ? 0 : 2));
6203			pr.write_shift(4, i+2);
6204			pr.write_shift(4, value.length + 1);
6205			pr.write_shift(0, value, "dbcs");
6206			while(pr.l != pr.length) pr.write_shift(1, 0);
6207			bufs.push(pr);
6208		}
6209		pr = bconcat(bufs);
6210		prop.unshift(pr);
6211		sz += 8 + pr.length;
6212	}
6213
6214	for(i = 0; i < entries.length; ++i) {
6215		if(RE && !RE[entries[i][0]]) continue;
6216		if(XLSPSSkip.indexOf(entries[i][0]) > -1 || PseudoPropsPairs.indexOf(entries[i][0]) > -1) continue;
6217		if(entries[i][1] == null) continue;
6218
6219		var val = entries[i][1], idx = 0;
6220		if(RE) {
6221			idx = +RE[entries[i][0]];
6222			var pinfo = (PIDSI/*:: || {}*/)[idx]/*:: || {} */;
6223			if(pinfo.p == "version" && typeof val == "string") {
6224				/*:: if(typeof val !== "string") throw "unreachable"; */
6225				var arr = val.split(".");
6226				val = ((+arr[0])<<16) + ((+arr[1])||0);
6227			}
6228			pr = write_TypedPropertyValue(pinfo.t, val);
6229		} else {
6230			var T = guess_property_type(val);
6231			if(T == -1) { T = 0x1F; val = String(val); }
6232			pr = write_TypedPropertyValue(T, val);
6233		}
6234		prop.push(pr);
6235
6236		pio = new_buf(8);
6237		pio.write_shift(4, !RE ? 2+i : idx);
6238		piao.push(pio);
6239
6240		sz += 8 + pr.length;
6241	}
6242
6243	var w = 8 * (prop.length + 1);
6244	for(i = 0; i < prop.length; ++i) { piao[i].write_shift(4, w); w += prop[i].length; }
6245	hdr.write_shift(4, sz);
6246	hdr.write_shift(4, prop.length);
6247	return bconcat([hdr].concat(piao).concat(prop));
6248}
6249
6250/* [MS-OLEPS] 2.21 PropertySetStream */
6251function parse_PropertySetStream(file, PIDSI, clsid) {
6252	var blob = file.content;
6253	if(!blob) return ({}/*:any*/);
6254	prep_blob(blob, 0);
6255
6256	var NumSets, FMTID0, FMTID1, Offset0, Offset1 = 0;
6257	blob.chk('feff', 'Byte Order: ');
6258
6259	/*var vers = */blob.read_shift(2); // TODO: check version
6260	var SystemIdentifier = blob.read_shift(4);
6261	var CLSID = blob.read_shift(16);
6262	if(CLSID !== CFB.utils.consts.HEADER_CLSID && CLSID !== clsid) throw new Error("Bad PropertySet CLSID " + CLSID);
6263	NumSets = blob.read_shift(4);
6264	if(NumSets !== 1 && NumSets !== 2) throw new Error("Unrecognized #Sets: " + NumSets);
6265	FMTID0 = blob.read_shift(16); Offset0 = blob.read_shift(4);
6266
6267	if(NumSets === 1 && Offset0 !== blob.l) throw new Error("Length mismatch: " + Offset0 + " !== " + blob.l);
6268	else if(NumSets === 2) { FMTID1 = blob.read_shift(16); Offset1 = blob.read_shift(4); }
6269	var PSet0 = parse_PropertySet(blob, PIDSI);
6270
6271	var rval = ({ SystemIdentifier: SystemIdentifier }/*:any*/);
6272	for(var y in PSet0) rval[y] = PSet0[y];
6273	//rval.blob = blob;
6274	rval.FMTID = FMTID0;
6275	//rval.PSet0 = PSet0;
6276	if(NumSets === 1) return rval;
6277	if(Offset1 - blob.l == 2) blob.l += 2;
6278	if(blob.l !== Offset1) throw new Error("Length mismatch 2: " + blob.l + " !== " + Offset1);
6279	var PSet1;
6280	try { PSet1 = parse_PropertySet(blob, null); } catch(e) {/* empty */}
6281	for(y in PSet1) rval[y] = PSet1[y];
6282	rval.FMTID = [FMTID0, FMTID1]; // TODO: verify FMTID0/1
6283	return rval;
6284}
6285function write_PropertySetStream(entries, clsid, RE, PIDSI/*:{[key:string|number]:any}*/, entries2/*:?any*/, clsid2/*:?any*/) {
6286	var hdr = new_buf(entries2 ? 68 : 48);
6287	var bufs = [hdr];
6288	hdr.write_shift(2, 0xFFFE);
6289	hdr.write_shift(2, 0x0000); /* TODO: type 1 props */
6290	hdr.write_shift(4, 0x32363237);
6291	hdr.write_shift(16, CFB.utils.consts.HEADER_CLSID, "hex");
6292	hdr.write_shift(4, (entries2 ? 2 : 1));
6293	hdr.write_shift(16, clsid, "hex");
6294	hdr.write_shift(4, (entries2 ? 68 : 48));
6295	var ps0 = write_PropertySet(entries, RE, PIDSI);
6296	bufs.push(ps0);
6297
6298	if(entries2) {
6299		var ps1 = write_PropertySet(entries2, null, null);
6300		hdr.write_shift(16, clsid2, "hex");
6301		hdr.write_shift(4, 68 + ps0.length);
6302		bufs.push(ps1);
6303	}
6304	return bconcat(bufs);
6305}
6306
6307function parsenoop2(blob, length) { blob.read_shift(length); return null; }
6308function writezeroes(n, o) { if(!o) o=new_buf(n); for(var j=0; j<n; ++j) o.write_shift(1, 0); return o; }
6309
6310function parslurp(blob, length, cb) {
6311	var arr = [], target = blob.l + length;
6312	while(blob.l < target) arr.push(cb(blob, target - blob.l));
6313	if(target !== blob.l) throw new Error("Slurp error");
6314	return arr;
6315}
6316
6317function parsebool(blob, length/*:number*/) { return blob.read_shift(length) === 0x1; }
6318function writebool(v/*:any*/, o) { if(!o) o=new_buf(2); o.write_shift(2, +!!v); return o; }
6319
6320function parseuint16(blob/*::, length:?number, opts:?any*/) { return blob.read_shift(2, 'u'); }
6321function writeuint16(v/*:number*/, o) { if(!o) o=new_buf(2); o.write_shift(2, v); return o; }
6322function parseuint16a(blob, length/*:: :?number, opts:?any*/) { return parslurp(blob,length,parseuint16);}
6323
6324/* --- 2.5 Structures --- */
6325
6326/* [MS-XLS] 2.5.10 Bes (boolean or error) */
6327function parse_Bes(blob/*::, length*/) {
6328	var v = blob.read_shift(1), t = blob.read_shift(1);
6329	return t === 0x01 ? v : v === 0x01;
6330}
6331function write_Bes(v, t/*:string*/, o) {
6332	if(!o) o = new_buf(2);
6333	o.write_shift(1, ((t == 'e') ? +v : +!!v));
6334	o.write_shift(1, ((t == 'e') ? 1 : 0));
6335	return o;
6336}
6337
6338/* [MS-XLS] 2.5.240 ShortXLUnicodeString */
6339function parse_ShortXLUnicodeString(blob, length, opts) {
6340	var cch = blob.read_shift(opts && opts.biff >= 12 ? 2 : 1);
6341	var encoding = 'sbcs-cont';
6342	var cp = current_codepage;
6343	if(opts && opts.biff >= 8) current_codepage = 1200;
6344	if(!opts || opts.biff == 8 ) {
6345		var fHighByte = blob.read_shift(1);
6346		if(fHighByte) { encoding = 'dbcs-cont'; }
6347	} else if(opts.biff == 12) {
6348		encoding = 'wstr';
6349	}
6350	if(opts.biff >= 2 && opts.biff <= 5) encoding = 'cpstr';
6351	var o = cch ? blob.read_shift(cch, encoding) : "";
6352	current_codepage = cp;
6353	return o;
6354}
6355
6356/* 2.5.293 XLUnicodeRichExtendedString */
6357function parse_XLUnicodeRichExtendedString(blob) {
6358	var cp = current_codepage;
6359	current_codepage = 1200;
6360	var cch = blob.read_shift(2), flags = blob.read_shift(1);
6361	var /*fHighByte = flags & 0x1,*/ fExtSt = flags & 0x4, fRichSt = flags & 0x8;
6362	var width = 1 + (flags & 0x1); // 0x0 -> utf8, 0x1 -> dbcs
6363	var cRun = 0, cbExtRst;
6364	var z = {};
6365	if(fRichSt) cRun = blob.read_shift(2);
6366	if(fExtSt) cbExtRst = blob.read_shift(4);
6367	var encoding = width == 2 ? 'dbcs-cont' : 'sbcs-cont';
6368	var msg = cch === 0 ? "" : blob.read_shift(cch, encoding);
6369	if(fRichSt) blob.l += 4 * cRun; //TODO: parse this
6370	if(fExtSt) blob.l += cbExtRst; //TODO: parse this
6371	z.t = msg;
6372	if(!fRichSt) { z.raw = "<t>" + z.t + "</t>"; z.r = z.t; }
6373	current_codepage = cp;
6374	return z;
6375}
6376function write_XLUnicodeRichExtendedString(xlstr/*:: :XLString, opts*/) {
6377	var str = (xlstr.t||""), nfmts = 1;
6378
6379	var hdr = new_buf(3 + (nfmts > 1 ? 2 : 0));
6380	hdr.write_shift(2, str.length);
6381	hdr.write_shift(1, (nfmts > 1 ? 0x08 : 0x00) | 0x01);
6382	if(nfmts > 1) hdr.write_shift(2, nfmts);
6383
6384	var otext = new_buf(2 * str.length);
6385	otext.write_shift(2 * str.length, str, 'utf16le');
6386
6387	var out = [hdr, otext];
6388
6389	return bconcat(out);
6390}
6391
6392/* 2.5.296 XLUnicodeStringNoCch */
6393function parse_XLUnicodeStringNoCch(blob, cch, opts) {
6394	var retval;
6395	if(opts) {
6396		if(opts.biff >= 2 && opts.biff <= 5) return blob.read_shift(cch, 'cpstr');
6397		if(opts.biff >= 12) return blob.read_shift(cch, 'dbcs-cont');
6398	}
6399	var fHighByte = blob.read_shift(1);
6400	if(fHighByte===0) { retval = blob.read_shift(cch, 'sbcs-cont'); }
6401	else { retval = blob.read_shift(cch, 'dbcs-cont'); }
6402	return retval;
6403}
6404
6405/* 2.5.294 XLUnicodeString */
6406function parse_XLUnicodeString(blob, length, opts) {
6407	var cch = blob.read_shift(opts && opts.biff == 2 ? 1 : 2);
6408	if(cch === 0) { blob.l++; return ""; }
6409	return parse_XLUnicodeStringNoCch(blob, cch, opts);
6410}
6411/* BIFF5 override */
6412function parse_XLUnicodeString2(blob, length, opts) {
6413	if(opts.biff > 5) return parse_XLUnicodeString(blob, length, opts);
6414	var cch = blob.read_shift(1);
6415	if(cch === 0) { blob.l++; return ""; }
6416	return blob.read_shift(cch, (opts.biff <= 4 || !blob.lens ) ? 'cpstr' : 'sbcs-cont');
6417}
6418/* TODO: BIFF5 and lower, codepage awareness */
6419function write_XLUnicodeString(str, opts, o) {
6420	if(!o) o = new_buf(3 + 2 * str.length);
6421	o.write_shift(2, str.length);
6422	o.write_shift(1, 1);
6423	o.write_shift(31, str, 'utf16le');
6424	return o;
6425}
6426
6427/* [MS-XLS] 2.5.61 ControlInfo */
6428function parse_ControlInfo(blob/*::, length, opts*/) {
6429	var flags = blob.read_shift(1);
6430	blob.l++;
6431	var accel = blob.read_shift(2);
6432	blob.l += 2;
6433	return [flags, accel];
6434}
6435
6436/* [MS-OSHARED] 2.3.7.6 URLMoniker TODO: flags */
6437function parse_URLMoniker(blob/*::, length, opts*/) {
6438	var len = blob.read_shift(4), start = blob.l;
6439	var extra = false;
6440	if(len > 24) {
6441		/* look ahead */
6442		blob.l += len - 24;
6443		if(blob.read_shift(16) === "795881f43b1d7f48af2c825dc4852763") extra = true;
6444		blob.l = start;
6445	}
6446	var url = blob.read_shift((extra?len-24:len)>>1, 'utf16le').replace(chr0,"");
6447	if(extra) blob.l += 24;
6448	return url;
6449}
6450
6451/* [MS-OSHARED] 2.3.7.8 FileMoniker TODO: all fields */
6452function parse_FileMoniker(blob/*::, length*/) {
6453	var cAnti = blob.read_shift(2);
6454	var preamble = ""; while(cAnti-- > 0) preamble += "../";
6455	var ansiPath = blob.read_shift(0, 'lpstr-ansi');
6456	blob.l += 2; //var endServer = blob.read_shift(2);
6457	if(blob.read_shift(2) != 0xDEAD) throw new Error("Bad FileMoniker");
6458	var sz = blob.read_shift(4);
6459	if(sz === 0) return preamble + ansiPath.replace(/\\/g,"/");
6460	var bytes = blob.read_shift(4);
6461	if(blob.read_shift(2) != 3) throw new Error("Bad FileMoniker");
6462	var unicodePath = blob.read_shift(bytes>>1, 'utf16le').replace(chr0,"");
6463	return preamble + unicodePath;
6464}
6465
6466/* [MS-OSHARED] 2.3.7.2 HyperlinkMoniker TODO: all the monikers */
6467function parse_HyperlinkMoniker(blob, length) {
6468	var clsid = blob.read_shift(16); length -= 16;
6469	switch(clsid) {
6470		case "e0c9ea79f9bace118c8200aa004ba90b": return parse_URLMoniker(blob, length);
6471		case "0303000000000000c000000000000046": return parse_FileMoniker(blob, length);
6472		default: throw new Error("Unsupported Moniker " + clsid);
6473	}
6474}
6475
6476/* [MS-OSHARED] 2.3.7.9 HyperlinkString */
6477function parse_HyperlinkString(blob/*::, length*/) {
6478	var len = blob.read_shift(4);
6479	var o = len > 0 ? blob.read_shift(len, 'utf16le').replace(chr0, "") : "";
6480	return o;
6481}
6482function write_HyperlinkString(str/*:string*/, o) {
6483	if(!o) o = new_buf(6 + str.length * 2);
6484	o.write_shift(4, 1 + str.length);
6485	for(var i = 0; i < str.length; ++i) o.write_shift(2, str.charCodeAt(i));
6486	o.write_shift(2, 0);
6487	return o;
6488}
6489
6490/* [MS-OSHARED] 2.3.7.1 Hyperlink Object */
6491function parse_Hyperlink(blob, length)/*:Hyperlink*/ {
6492	var end = blob.l + length;
6493	var sVer = blob.read_shift(4);
6494	if(sVer !== 2) throw new Error("Unrecognized streamVersion: " + sVer);
6495	var flags = blob.read_shift(2);
6496	blob.l += 2;
6497	var displayName, targetFrameName, moniker, oleMoniker, Loc="", guid, fileTime;
6498	if(flags & 0x0010) displayName = parse_HyperlinkString(blob, end - blob.l);
6499	if(flags & 0x0080) targetFrameName = parse_HyperlinkString(blob, end - blob.l);
6500	if((flags & 0x0101) === 0x0101) moniker = parse_HyperlinkString(blob, end - blob.l);
6501	if((flags & 0x0101) === 0x0001) oleMoniker = parse_HyperlinkMoniker(blob, end - blob.l);
6502	if(flags & 0x0008) Loc = parse_HyperlinkString(blob, end - blob.l);
6503	if(flags & 0x0020) guid = blob.read_shift(16);
6504	if(flags & 0x0040) fileTime = parse_FILETIME(blob/*, 8*/);
6505	blob.l = end;
6506	var target = targetFrameName||moniker||oleMoniker||"";
6507	if(target && Loc) target+="#"+Loc;
6508	if(!target) target = "#" + Loc;
6509	if((flags & 0x0002) && target.charAt(0) == "/" && target.charAt(1) != "/") target = "file://" + target;
6510	var out = ({Target:target}/*:any*/);
6511	if(guid) out.guid = guid;
6512	if(fileTime) out.time = fileTime;
6513	if(displayName) out.Tooltip = displayName;
6514	return out;
6515}
6516function write_Hyperlink(hl) {
6517	var out = new_buf(512), i = 0;
6518	var Target = hl.Target;
6519	if(Target.slice(0,7) == "file://") Target = Target.slice(7);
6520	var hashidx = Target.indexOf("#");
6521	var F = hashidx > -1 ? 0x1f : 0x17;
6522	switch(Target.charAt(0)) { case "#": F=0x1c; break; case ".": F&=~2; break; }
6523	out.write_shift(4,2); out.write_shift(4, F);
6524	var data = [8,6815827,6619237,4849780,83]; for(i = 0; i < data.length; ++i) out.write_shift(4, data[i]);
6525	if(F == 0x1C) {
6526		Target = Target.slice(1);
6527		write_HyperlinkString(Target, out);
6528	} else if(F & 0x02) {
6529		data = "e0 c9 ea 79 f9 ba ce 11 8c 82 00 aa 00 4b a9 0b".split(" ");
6530		for(i = 0; i < data.length; ++i) out.write_shift(1, parseInt(data[i], 16));
6531		var Pretarget = hashidx > -1 ? Target.slice(0, hashidx) : Target;
6532		out.write_shift(4, 2*(Pretarget.length + 1));
6533		for(i = 0; i < Pretarget.length; ++i) out.write_shift(2, Pretarget.charCodeAt(i));
6534		out.write_shift(2, 0);
6535		if(F & 0x08) write_HyperlinkString(hashidx > -1 ? Target.slice(hashidx+1): "", out);
6536	} else {
6537		data = "03 03 00 00 00 00 00 00 c0 00 00 00 00 00 00 46".split(" ");
6538		for(i = 0; i < data.length; ++i) out.write_shift(1, parseInt(data[i], 16));
6539		var P = 0;
6540		while(Target.slice(P*3,P*3+3)=="../"||Target.slice(P*3,P*3+3)=="..\\") ++P;
6541		out.write_shift(2, P);
6542		out.write_shift(4, Target.length - 3 * P + 1);
6543		for(i = 0; i < Target.length - 3 * P; ++i) out.write_shift(1, Target.charCodeAt(i + 3 * P) & 0xFF);
6544		out.write_shift(1, 0);
6545		out.write_shift(2, 0xFFFF);
6546		out.write_shift(2, 0xDEAD);
6547		for(i = 0; i < 6; ++i) out.write_shift(4, 0);
6548	}
6549	return out.slice(0, out.l);
6550}
6551
6552/* 2.5.178 LongRGBA */
6553function parse_LongRGBA(blob/*::, length*/) { var r = blob.read_shift(1), g = blob.read_shift(1), b = blob.read_shift(1), a = blob.read_shift(1); return [r,g,b,a]; }
6554
6555/* 2.5.177 LongRGB */
6556function parse_LongRGB(blob, length) { var x = parse_LongRGBA(blob, length); x[3] = 0; return x; }
6557
6558
6559/* [MS-XLS] 2.5.19 */
6560function parse_XLSCell(blob/*::, length*/)/*:Cell*/ {
6561	var rw = blob.read_shift(2); // 0-indexed
6562	var col = blob.read_shift(2);
6563	var ixfe = blob.read_shift(2);
6564	return ({r:rw, c:col, ixfe:ixfe}/*:any*/);
6565}
6566function write_XLSCell(R/*:number*/, C/*:number*/, ixfe/*:?number*/, o) {
6567	if(!o) o = new_buf(6);
6568	o.write_shift(2, R);
6569	o.write_shift(2, C);
6570	o.write_shift(2, ixfe||0);
6571	return o;
6572}
6573
6574/* [MS-XLS] 2.5.134 */
6575function parse_frtHeader(blob) {
6576	var rt = blob.read_shift(2);
6577	var flags = blob.read_shift(2); // TODO: parse these flags
6578	blob.l += 8;
6579	return {type: rt, flags: flags};
6580}
6581
6582
6583
6584function parse_OptXLUnicodeString(blob, length, opts) { return length === 0 ? "" : parse_XLUnicodeString2(blob, length, opts); }
6585
6586/* [MS-XLS] 2.5.344 */
6587function parse_XTI(blob, length, opts) {
6588	var w = opts.biff > 8 ? 4 : 2;
6589	var iSupBook = blob.read_shift(w), itabFirst = blob.read_shift(w,'i'), itabLast = blob.read_shift(w,'i');
6590	return [iSupBook, itabFirst, itabLast];
6591}
6592
6593/* [MS-XLS] 2.5.218 */
6594function parse_RkRec(blob) {
6595	var ixfe = blob.read_shift(2);
6596	var RK = parse_RkNumber(blob);
6597	return [ixfe, RK];
6598}
6599
6600/* [MS-XLS] 2.5.1 */
6601function parse_AddinUdf(blob, length, opts) {
6602	blob.l += 4; length -= 4;
6603	var l = blob.l + length;
6604	var udfName = parse_ShortXLUnicodeString(blob, length, opts);
6605	var cb = blob.read_shift(2);
6606	l -= blob.l;
6607	if(cb !== l) throw new Error("Malformed AddinUdf: padding = " + l + " != " + cb);
6608	blob.l += cb;
6609	return udfName;
6610}
6611
6612/* [MS-XLS] 2.5.209 TODO: Check sizes */
6613function parse_Ref8U(blob/*::, length*/) {
6614	var rwFirst = blob.read_shift(2);
6615	var rwLast = blob.read_shift(2);
6616	var colFirst = blob.read_shift(2);
6617	var colLast = blob.read_shift(2);
6618	return {s:{c:colFirst, r:rwFirst}, e:{c:colLast,r:rwLast}};
6619}
6620function write_Ref8U(r/*:Range*/, o) {
6621	if(!o) o = new_buf(8);
6622	o.write_shift(2, r.s.r);
6623	o.write_shift(2, r.e.r);
6624	o.write_shift(2, r.s.c);
6625	o.write_shift(2, r.e.c);
6626	return o;
6627}
6628
6629/* [MS-XLS] 2.5.211 */
6630function parse_RefU(blob/*::, length*/) {
6631	var rwFirst = blob.read_shift(2);
6632	var rwLast = blob.read_shift(2);
6633	var colFirst = blob.read_shift(1);
6634	var colLast = blob.read_shift(1);
6635	return {s:{c:colFirst, r:rwFirst}, e:{c:colLast,r:rwLast}};
6636}
6637
6638/* [MS-XLS] 2.5.207 */
6639var parse_Ref = parse_RefU;
6640
6641/* [MS-XLS] 2.5.143 */
6642function parse_FtCmo(blob/*::, length*/) {
6643	blob.l += 4;
6644	var ot = blob.read_shift(2);
6645	var id = blob.read_shift(2);
6646	var flags = blob.read_shift(2);
6647	blob.l+=12;
6648	return [id, ot, flags];
6649}
6650
6651/* [MS-XLS] 2.5.149 */
6652function parse_FtNts(blob) {
6653	var out = {};
6654	blob.l += 4;
6655	blob.l += 16; // GUID TODO
6656	out.fSharedNote = blob.read_shift(2);
6657	blob.l += 4;
6658	return out;
6659}
6660
6661/* [MS-XLS] 2.5.142 */
6662function parse_FtCf(blob) {
6663	var out = {};
6664	blob.l += 4;
6665	blob.cf = blob.read_shift(2);
6666	return out;
6667}
6668
6669/* [MS-XLS] 2.5.140 - 2.5.154 and friends */
6670function parse_FtSkip(blob) { blob.l += 2; blob.l += blob.read_shift(2); }
6671var FtTab = {
6672	/*::[*/0x00/*::]*/: parse_FtSkip,      /* FtEnd */
6673	/*::[*/0x04/*::]*/: parse_FtSkip,      /* FtMacro */
6674	/*::[*/0x05/*::]*/: parse_FtSkip,      /* FtButton */
6675	/*::[*/0x06/*::]*/: parse_FtSkip,      /* FtGmo */
6676	/*::[*/0x07/*::]*/: parse_FtCf,        /* FtCf */
6677	/*::[*/0x08/*::]*/: parse_FtSkip,      /* FtPioGrbit */
6678	/*::[*/0x09/*::]*/: parse_FtSkip,      /* FtPictFmla */
6679	/*::[*/0x0A/*::]*/: parse_FtSkip,      /* FtCbls */
6680	/*::[*/0x0B/*::]*/: parse_FtSkip,      /* FtRbo */
6681	/*::[*/0x0C/*::]*/: parse_FtSkip,      /* FtSbs */
6682	/*::[*/0x0D/*::]*/: parse_FtNts,       /* FtNts */
6683	/*::[*/0x0E/*::]*/: parse_FtSkip,      /* FtSbsFmla */
6684	/*::[*/0x0F/*::]*/: parse_FtSkip,      /* FtGboData */
6685	/*::[*/0x10/*::]*/: parse_FtSkip,      /* FtEdoData */
6686	/*::[*/0x11/*::]*/: parse_FtSkip,      /* FtRboData */
6687	/*::[*/0x12/*::]*/: parse_FtSkip,      /* FtCblsData */
6688	/*::[*/0x13/*::]*/: parse_FtSkip,      /* FtLbsData */
6689	/*::[*/0x14/*::]*/: parse_FtSkip,      /* FtCblsFmla */
6690	/*::[*/0x15/*::]*/: parse_FtCmo
6691};
6692function parse_FtArray(blob, length/*::, ot*/) {
6693	var tgt = blob.l + length;
6694	var fts = [];
6695	while(blob.l < tgt) {
6696		var ft = blob.read_shift(2);
6697		blob.l-=2;
6698		try {
6699			fts.push(FtTab[ft](blob, tgt - blob.l));
6700		} catch(e) { blob.l = tgt; return fts; }
6701	}
6702	if(blob.l != tgt) blob.l = tgt; //throw new Error("bad Object Ft-sequence");
6703	return fts;
6704}
6705
6706/* --- 2.4 Records --- */
6707
6708/* [MS-XLS] 2.4.21 */
6709function parse_BOF(blob, length) {
6710	var o = {BIFFVer:0, dt:0};
6711	o.BIFFVer = blob.read_shift(2); length -= 2;
6712	if(length >= 2) { o.dt = blob.read_shift(2); blob.l -= 2; }
6713	switch(o.BIFFVer) {
6714		case 0x0600: /* BIFF8 */
6715		case 0x0500: /* BIFF5 */
6716		case 0x0400: /* BIFF4 */
6717		case 0x0300: /* BIFF3 */
6718		case 0x0200: /* BIFF2 */
6719		case 0x0002: case 0x0007: /* BIFF2 */
6720			break;
6721		default: if(length > 6) throw new Error("Unexpected BIFF Ver " + o.BIFFVer);
6722	}
6723
6724	blob.read_shift(length);
6725	return o;
6726}
6727function write_BOF(wb/*:Workbook*/, t/*:number*/, o) {
6728	var h = 0x0600, w = 16;
6729	switch(o.bookType) {
6730		case 'biff8': break;
6731		case 'biff5': h = 0x0500; w = 8; break;
6732		case 'biff4': h = 0x0004; w = 6; break;
6733		case 'biff3': h = 0x0003; w = 6; break;
6734		case 'biff2': h = 0x0002; w = 4; break;
6735		case 'xla': break;
6736		default: throw new Error("unsupported BIFF version");
6737	}
6738	var out = new_buf(w);
6739	out.write_shift(2, h);
6740	out.write_shift(2, t);
6741	if(w > 4) out.write_shift(2, 0x7262);
6742	if(w > 6) out.write_shift(2, 0x07CD);
6743	if(w > 8) {
6744		out.write_shift(2, 0xC009);
6745		out.write_shift(2, 0x0001);
6746		out.write_shift(2, 0x0706);
6747		out.write_shift(2, 0x0000);
6748	}
6749	return out;
6750}
6751
6752
6753/* [MS-XLS] 2.4.146 */
6754function parse_InterfaceHdr(blob, length) {
6755	if(length === 0) return 0x04b0;
6756	if((blob.read_shift(2))!==0x04b0){/* empty */}
6757	return 0x04b0;
6758}
6759
6760
6761/* [MS-XLS] 2.4.349 */
6762function parse_WriteAccess(blob, length, opts) {
6763	if(opts.enc) { blob.l += length; return ""; }
6764	var l = blob.l;
6765	// TODO: make sure XLUnicodeString doesnt overrun
6766	var UserName = parse_XLUnicodeString2(blob, 0, opts);
6767	blob.read_shift(length + l - blob.l);
6768	return UserName;
6769}
6770function write_WriteAccess(s/*:string*/, opts) {
6771	var b8 = !opts || opts.biff == 8;
6772	var o = new_buf(b8 ? 112 : 54);
6773	o.write_shift(opts.biff == 8 ? 2 : 1, 7);
6774	if(b8) o.write_shift(1, 0);
6775	o.write_shift(4, 0x33336853);
6776	o.write_shift(4, (0x00534A74 | (b8 ? 0 : 0x20000000)));
6777	while(o.l < o.length) o.write_shift(1, (b8 ? 0 : 32));
6778	return o;
6779}
6780
6781/* [MS-XLS] 2.4.351 */
6782function parse_WsBool(blob, length, opts) {
6783	var flags = opts && opts.biff == 8 || length == 2 ? blob.read_shift(2) : (blob.l += length, 0);
6784	return { fDialog: flags & 0x10, fBelow: flags & 0x40, fRight: flags & 0x80 };
6785}
6786
6787/* [MS-XLS] 2.4.28 */
6788function parse_BoundSheet8(blob, length, opts) {
6789	var pos = blob.read_shift(4);
6790	var hidden = blob.read_shift(1) & 0x03;
6791	var dt = blob.read_shift(1);
6792	switch(dt) {
6793		case 0: dt = 'Worksheet'; break;
6794		case 1: dt = 'Macrosheet'; break;
6795		case 2: dt = 'Chartsheet'; break;
6796		case 6: dt = 'VBAModule'; break;
6797	}
6798	var name = parse_ShortXLUnicodeString(blob, 0, opts);
6799	if(name.length === 0) name = "Sheet1";
6800	return { pos:pos, hs:hidden, dt:dt, name:name };
6801}
6802function write_BoundSheet8(data, opts) {
6803	var w = (!opts || opts.biff >= 8 ? 2 : 1);
6804	var o = new_buf(8 + w * data.name.length);
6805	o.write_shift(4, data.pos);
6806	o.write_shift(1, data.hs || 0);
6807	o.write_shift(1, data.dt);
6808	o.write_shift(1, data.name.length);
6809	if(opts.biff >= 8) o.write_shift(1, 1);
6810	o.write_shift(w * data.name.length, data.name, opts.biff < 8 ? 'sbcs' : 'utf16le');
6811	var out = o.slice(0, o.l);
6812	out.l = o.l; return out;
6813}
6814
6815/* [MS-XLS] 2.4.265 TODO */
6816function parse_SST(blob, length)/*:SST*/ {
6817	var end = blob.l + length;
6818	var cnt = blob.read_shift(4);
6819	var ucnt = blob.read_shift(4);
6820	var strs/*:SST*/ = ([]/*:any*/);
6821	for(var i = 0; i != ucnt && blob.l < end; ++i) {
6822		strs.push(parse_XLUnicodeRichExtendedString(blob));
6823	}
6824	strs.Count = cnt; strs.Unique = ucnt;
6825	return strs;
6826}
6827function write_SST(sst, opts) {
6828	var header = new_buf(8);
6829	header.write_shift(4, sst.Count);
6830	header.write_shift(4, sst.Unique);
6831	var strs = [];
6832	for(var j = 0; j < sst.length; ++j) strs[j] = write_XLUnicodeRichExtendedString(sst[j], opts);
6833	var o = bconcat([header].concat(strs));
6834	/*::(*/o/*:: :any)*/.parts = [header.length].concat(strs.map(function(str) { return str.length; }));
6835	return o;
6836}
6837
6838/* [MS-XLS] 2.4.107 */
6839function parse_ExtSST(blob, length) {
6840	var extsst = {};
6841	extsst.dsst = blob.read_shift(2);
6842	blob.l += length-2;
6843	return extsst;
6844}
6845
6846
6847/* [MS-XLS] 2.4.221 TODO: check BIFF2-4 */
6848function parse_Row(blob) {
6849	var z = ({}/*:any*/);
6850	z.r = blob.read_shift(2);
6851	z.c = blob.read_shift(2);
6852	z.cnt = blob.read_shift(2) - z.c;
6853	var miyRw = blob.read_shift(2);
6854	blob.l += 4; // reserved(2), unused(2)
6855	var flags = blob.read_shift(1); // various flags
6856	blob.l += 3; // reserved(8), ixfe(12), flags(4)
6857	if(flags & 0x07) z.level = flags & 0x07;
6858	// collapsed: flags & 0x10
6859	if(flags & 0x20) z.hidden = true;
6860	if(flags & 0x40) z.hpt = miyRw / 20;
6861	return z;
6862}
6863
6864
6865/* [MS-XLS] 2.4.125 */
6866function parse_ForceFullCalculation(blob) {
6867	var header = parse_frtHeader(blob);
6868	if(header.type != 0x08A3) throw new Error("Invalid Future Record " + header.type);
6869	var fullcalc = blob.read_shift(4);
6870	return fullcalc !== 0x0;
6871}
6872
6873
6874
6875
6876
6877/* [MS-XLS] 2.4.215 rt */
6878function parse_RecalcId(blob) {
6879	blob.read_shift(2);
6880	return blob.read_shift(4);
6881}
6882
6883/* [MS-XLS] 2.4.87 */
6884function parse_DefaultRowHeight(blob, length, opts) {
6885	var f = 0;
6886	if(!(opts && opts.biff == 2)) {
6887		f = blob.read_shift(2);
6888	}
6889	var miyRw = blob.read_shift(2);
6890	if((opts && opts.biff == 2)) {
6891		f = 1 - (miyRw >> 15); miyRw &= 0x7fff;
6892	}
6893	var fl = {Unsynced:f&1,DyZero:(f&2)>>1,ExAsc:(f&4)>>2,ExDsc:(f&8)>>3};
6894	return [fl, miyRw];
6895}
6896
6897/* [MS-XLS] 2.4.345 TODO */
6898function parse_Window1(blob) {
6899	var xWn = blob.read_shift(2), yWn = blob.read_shift(2), dxWn = blob.read_shift(2), dyWn = blob.read_shift(2);
6900	var flags = blob.read_shift(2), iTabCur = blob.read_shift(2), iTabFirst = blob.read_shift(2);
6901	var ctabSel = blob.read_shift(2), wTabRatio = blob.read_shift(2);
6902	return { Pos: [xWn, yWn], Dim: [dxWn, dyWn], Flags: flags, CurTab: iTabCur,
6903		FirstTab: iTabFirst, Selected: ctabSel, TabRatio: wTabRatio };
6904}
6905function write_Window1(/*::opts*/) {
6906	var o = new_buf(18);
6907	o.write_shift(2, 0);
6908	o.write_shift(2, 0);
6909	o.write_shift(2, 0x7260);
6910	o.write_shift(2, 0x44c0);
6911	o.write_shift(2, 0x38);
6912	o.write_shift(2, 0);
6913	o.write_shift(2, 0);
6914	o.write_shift(2, 1);
6915	o.write_shift(2, 0x01f4);
6916	return o;
6917}
6918/* [MS-XLS] 2.4.346 TODO */
6919function parse_Window2(blob, length, opts) {
6920	if(opts && opts.biff >= 2 && opts.biff < 5) return {};
6921	var f = blob.read_shift(2);
6922	return { RTL: f & 0x40 };
6923}
6924function write_Window2(view) {
6925	var o = new_buf(18), f = 0x6b6;
6926	if(view && view.RTL) f |= 0x40;
6927	o.write_shift(2, f);
6928	o.write_shift(4, 0);
6929	o.write_shift(4, 64);
6930	o.write_shift(4, 0);
6931	o.write_shift(4, 0);
6932	return o;
6933}
6934
6935/* [MS-XLS] 2.4.189 TODO */
6936function parse_Pane(/*blob, length, opts*/) {
6937}
6938
6939/* [MS-XLS] 2.4.122 TODO */
6940function parse_Font(blob, length, opts) {
6941	var o/*:any*/ = {
6942		dyHeight: blob.read_shift(2),
6943		fl: blob.read_shift(2)
6944	};
6945	switch((opts && opts.biff) || 8) {
6946		case 2: break;
6947		case 3: case 4: blob.l += 2; break;
6948		default: blob.l += 10; break;
6949	}
6950	o.name = parse_ShortXLUnicodeString(blob, 0, opts);
6951	return o;
6952}
6953function write_Font(data, opts) {
6954	var name = data.name || "Arial";
6955	var b5 = (opts && (opts.biff == 5)), w = (b5 ? (15 + name.length) : (16 + 2 * name.length));
6956	var o = new_buf(w);
6957	o.write_shift(2, (data.sz || 12) * 20);
6958	o.write_shift(4, 0);
6959	o.write_shift(2, 400);
6960	o.write_shift(4, 0);
6961	o.write_shift(2, 0);
6962	o.write_shift(1, name.length);
6963	if(!b5) o.write_shift(1, 1);
6964	o.write_shift((b5 ? 1 : 2) * name.length, name, (b5 ? "sbcs" : "utf16le"));
6965	return o;
6966}
6967
6968/* [MS-XLS] 2.4.149 */
6969function parse_LabelSst(blob) {
6970	var cell = parse_XLSCell(blob);
6971	cell.isst = blob.read_shift(4);
6972	return cell;
6973}
6974function write_LabelSst(R/*:number*/, C/*:number*/, v/*:number*/, os/*:number*/ /*::, opts*/) {
6975	var o = new_buf(10);
6976	write_XLSCell(R, C, os, o);
6977	o.write_shift(4, v);
6978	return o;
6979}
6980
6981/* [MS-XLS] 2.4.148 */
6982function parse_Label(blob, length, opts) {
6983	if(opts.biffguess && opts.biff == 2) opts.biff = 5;
6984	var target = blob.l + length;
6985	var cell = parse_XLSCell(blob, 6);
6986	if(opts.biff == 2) blob.l++;
6987	var str = parse_XLUnicodeString(blob, target - blob.l, opts);
6988	cell.val = str;
6989	return cell;
6990}
6991function write_Label(R/*:number*/, C/*:number*/, v/*:string*/, os/*:number*/, opts) {
6992	var b8 = !opts || opts.biff == 8;
6993	var o = new_buf(6 + 2 + (+b8) + (1 + b8) * v.length);
6994	write_XLSCell(R, C, os, o);
6995	o.write_shift(2, v.length);
6996	if(b8) o.write_shift(1, 1);
6997	o.write_shift((1 + b8) * v.length, v, b8 ? 'utf16le' : 'sbcs');
6998	return o;
6999}
7000
7001
7002/* [MS-XLS] 2.4.126 Number Formats */
7003function parse_Format(blob, length, opts) {
7004	var numFmtId = blob.read_shift(2);
7005	var fmtstr = parse_XLUnicodeString2(blob, 0, opts);
7006	return [numFmtId, fmtstr];
7007}
7008function write_Format(i/*:number*/, f/*:string*/, opts, o) {
7009	var b5 = (opts && (opts.biff == 5));
7010	if(!o) o = new_buf(b5 ? (3 + f.length) : (5 + 2 * f.length));
7011	o.write_shift(2, i);
7012	o.write_shift((b5 ? 1 : 2), f.length);
7013	if(!b5) o.write_shift(1, 1);
7014	o.write_shift((b5 ? 1 : 2) * f.length, f, (b5 ? 'sbcs' : 'utf16le'));
7015	var out = (o.length > o.l) ? o.slice(0, o.l) : o;
7016	if(out.l == null) out.l = out.length;
7017	return out;
7018}
7019var parse_BIFF2Format = parse_XLUnicodeString2;
7020
7021/* [MS-XLS] 2.4.90 */
7022function parse_Dimensions(blob, length, opts) {
7023	var end = blob.l + length;
7024	var w = opts.biff == 8 || !opts.biff ? 4 : 2;
7025	var r = blob.read_shift(w), R = blob.read_shift(w);
7026	var c = blob.read_shift(2), C = blob.read_shift(2);
7027	blob.l = end;
7028	return {s: {r:r, c:c}, e: {r:R, c:C}};
7029}
7030function write_Dimensions(range, opts) {
7031	var w = opts.biff == 8 || !opts.biff ? 4 : 2;
7032	var o = new_buf(2*w + 6);
7033	o.write_shift(w, range.s.r);
7034	o.write_shift(w, range.e.r + 1);
7035	o.write_shift(2, range.s.c);
7036	o.write_shift(2, range.e.c + 1);
7037	o.write_shift(2, 0);
7038	return o;
7039}
7040
7041/* [MS-XLS] 2.4.220 */
7042function parse_RK(blob) {
7043	var rw = blob.read_shift(2), col = blob.read_shift(2);
7044	var rkrec = parse_RkRec(blob);
7045	return {r:rw, c:col, ixfe:rkrec[0], rknum:rkrec[1]};
7046}
7047
7048/* [MS-XLS] 2.4.175 */
7049function parse_MulRk(blob, length) {
7050	var target = blob.l + length - 2;
7051	var rw = blob.read_shift(2), col = blob.read_shift(2);
7052	var rkrecs = [];
7053	while(blob.l < target) rkrecs.push(parse_RkRec(blob));
7054	if(blob.l !== target) throw new Error("MulRK read error");
7055	var lastcol = blob.read_shift(2);
7056	if(rkrecs.length != lastcol - col + 1) throw new Error("MulRK length mismatch");
7057	return {r:rw, c:col, C:lastcol, rkrec:rkrecs};
7058}
7059/* [MS-XLS] 2.4.174 */
7060function parse_MulBlank(blob, length) {
7061	var target = blob.l + length - 2;
7062	var rw = blob.read_shift(2), col = blob.read_shift(2);
7063	var ixfes = [];
7064	while(blob.l < target) ixfes.push(blob.read_shift(2));
7065	if(blob.l !== target) throw new Error("MulBlank read error");
7066	var lastcol = blob.read_shift(2);
7067	if(ixfes.length != lastcol - col + 1) throw new Error("MulBlank length mismatch");
7068	return {r:rw, c:col, C:lastcol, ixfe:ixfes};
7069}
7070
7071/* [MS-XLS] 2.5.20 2.5.249 TODO: interpret values here */
7072function parse_CellStyleXF(blob, length, style, opts) {
7073	var o = {};
7074	var a = blob.read_shift(4), b = blob.read_shift(4);
7075	var c = blob.read_shift(4), d = blob.read_shift(2);
7076	o.patternType = XLSFillPattern[c >> 26];
7077
7078	if(!opts.cellStyles) return o;
7079	o.alc = a & 0x07;
7080	o.fWrap = (a >> 3) & 0x01;
7081	o.alcV = (a >> 4) & 0x07;
7082	o.fJustLast = (a >> 7) & 0x01;
7083	o.trot = (a >> 8) & 0xFF;
7084	o.cIndent = (a >> 16) & 0x0F;
7085	o.fShrinkToFit = (a >> 20) & 0x01;
7086	o.iReadOrder = (a >> 22) & 0x02;
7087	o.fAtrNum = (a >> 26) & 0x01;
7088	o.fAtrFnt = (a >> 27) & 0x01;
7089	o.fAtrAlc = (a >> 28) & 0x01;
7090	o.fAtrBdr = (a >> 29) & 0x01;
7091	o.fAtrPat = (a >> 30) & 0x01;
7092	o.fAtrProt = (a >> 31) & 0x01;
7093
7094	o.dgLeft = b & 0x0F;
7095	o.dgRight = (b >> 4) & 0x0F;
7096	o.dgTop = (b >> 8) & 0x0F;
7097	o.dgBottom = (b >> 12) & 0x0F;
7098	o.icvLeft = (b >> 16) & 0x7F;
7099	o.icvRight = (b >> 23) & 0x7F;
7100	o.grbitDiag = (b >> 30) & 0x03;
7101
7102	o.icvTop = c & 0x7F;
7103	o.icvBottom = (c >> 7) & 0x7F;
7104	o.icvDiag = (c >> 14) & 0x7F;
7105	o.dgDiag = (c >> 21) & 0x0F;
7106
7107	o.icvFore = d & 0x7F;
7108	o.icvBack = (d >> 7) & 0x7F;
7109	o.fsxButton = (d >> 14) & 0x01;
7110	return o;
7111}
7112//function parse_CellXF(blob, length, opts) {return parse_CellStyleXF(blob,length,0, opts);}
7113//function parse_StyleXF(blob, length, opts) {return parse_CellStyleXF(blob,length,1, opts);}
7114
7115/* [MS-XLS] 2.4.353 TODO: actually do this right */
7116function parse_XF(blob, length, opts) {
7117	var o = {};
7118	o.ifnt = blob.read_shift(2); o.numFmtId = blob.read_shift(2); o.flags = blob.read_shift(2);
7119	o.fStyle = (o.flags >> 2) & 0x01;
7120	length -= 6;
7121	o.data = parse_CellStyleXF(blob, length, o.fStyle, opts);
7122	return o;
7123}
7124function write_XF(data, ixfeP, opts, o) {
7125	var b5 = (opts && (opts.biff == 5));
7126	if(!o) o = new_buf(b5 ? 16 : 20);
7127	o.write_shift(2, 0);
7128	if(data.style) {
7129		o.write_shift(2, (data.numFmtId||0));
7130		o.write_shift(2, 0xFFF4);
7131	} else {
7132		o.write_shift(2, (data.numFmtId||0));
7133		o.write_shift(2, (ixfeP<<4));
7134	}
7135	var f = 0;
7136	if(data.numFmtId > 0 && b5) f |= 0x0400;
7137	o.write_shift(4, f);
7138	o.write_shift(4, 0);
7139	if(!b5) o.write_shift(4, 0);
7140	o.write_shift(2, 0);
7141	return o;
7142}
7143
7144/* [MS-XLS] 2.4.134 */
7145function parse_Guts(blob) {
7146	blob.l += 4;
7147	var out = [blob.read_shift(2), blob.read_shift(2)];
7148	if(out[0] !== 0) out[0]--;
7149	if(out[1] !== 0) out[1]--;
7150	if(out[0] > 7 || out[1] > 7) throw new Error("Bad Gutters: " + out.join("|"));
7151	return out;
7152}
7153function write_Guts(guts/*:Array<number>*/) {
7154	var o = new_buf(8);
7155	o.write_shift(4, 0);
7156	o.write_shift(2, guts[0] ? guts[0] + 1 : 0);
7157	o.write_shift(2, guts[1] ? guts[1] + 1 : 0);
7158	return o;
7159}
7160
7161/* [MS-XLS] 2.4.24 */
7162function parse_BoolErr(blob, length, opts) {
7163	var cell = parse_XLSCell(blob, 6);
7164	if(opts.biff == 2 || length == 9) ++blob.l;
7165	var val = parse_Bes(blob, 2);
7166	cell.val = val;
7167	cell.t = (val === true || val === false) ? 'b' : 'e';
7168	return cell;
7169}
7170function write_BoolErr(R/*:number*/, C/*:number*/, v, os/*:number*/, opts, t/*:string*/) {
7171	var o = new_buf(8);
7172	write_XLSCell(R, C, os, o);
7173	write_Bes(v, t, o);
7174	return o;
7175}
7176
7177/* [MS-XLS] 2.4.180 Number */
7178function parse_Number(blob, length, opts) {
7179	if(opts.biffguess && opts.biff == 2) opts.biff = 5;
7180	var cell = parse_XLSCell(blob, 6);
7181	var xnum = parse_Xnum(blob, 8);
7182	cell.val = xnum;
7183	return cell;
7184}
7185function write_Number(R/*:number*/, C/*:number*/, v, os/*:: :number, opts*/) {
7186	var o = new_buf(14);
7187	write_XLSCell(R, C, os, o);
7188	write_Xnum(v, o);
7189	return o;
7190}
7191
7192var parse_XLHeaderFooter = parse_OptXLUnicodeString; // TODO: parse 2.4.136
7193
7194/* [MS-XLS] 2.4.271 */
7195function parse_SupBook(blob, length, opts) {
7196	var end = blob.l + length;
7197	var ctab = blob.read_shift(2);
7198	var cch = blob.read_shift(2);
7199	opts.sbcch = cch;
7200	if(cch == 0x0401 || cch == 0x3A01) return [cch, ctab];
7201	if(cch < 0x01 || cch >0xff) throw new Error("Unexpected SupBook type: "+cch);
7202	var virtPath = parse_XLUnicodeStringNoCch(blob, cch);
7203	/* TODO: 2.5.277 Virtual Path */
7204	var rgst = [];
7205	while(end > blob.l) rgst.push(parse_XLUnicodeString(blob));
7206	return [cch, ctab, virtPath, rgst];
7207}
7208
7209/* [MS-XLS] 2.4.105 TODO */
7210function parse_ExternName(blob, length, opts) {
7211	var flags = blob.read_shift(2);
7212	var body;
7213	var o = ({
7214		fBuiltIn: flags & 0x01,
7215		fWantAdvise: (flags >>> 1) & 0x01,
7216		fWantPict: (flags >>> 2) & 0x01,
7217		fOle: (flags >>> 3) & 0x01,
7218		fOleLink: (flags >>> 4) & 0x01,
7219		cf: (flags >>> 5) & 0x3FF,
7220		fIcon: flags >>> 15 & 0x01
7221	}/*:any*/);
7222	if(opts.sbcch === 0x3A01) body = parse_AddinUdf(blob, length-2, opts);
7223	//else throw new Error("unsupported SupBook cch: " + opts.sbcch);
7224	o.body = body || blob.read_shift(length-2);
7225	if(typeof body === "string") o.Name = body;
7226	return o;
7227}
7228
7229/* [MS-XLS] 2.4.150 TODO */
7230function parse_Lbl(blob, length, opts) {
7231	var target = blob.l + length;
7232	var flags = blob.read_shift(2);
7233	var chKey = blob.read_shift(1);
7234	var cch = blob.read_shift(1);
7235	var cce = blob.read_shift(opts && opts.biff == 2 ? 1 : 2);
7236	var itab = 0;
7237	if(!opts || opts.biff >= 5) {
7238		if(opts.biff != 5) blob.l += 2;
7239		itab = blob.read_shift(2);
7240		if(opts.biff == 5) blob.l += 2;
7241		blob.l += 4;
7242	}
7243	var name = parse_XLUnicodeStringNoCch(blob, cch, opts);
7244	if(flags & 0x20) name = XLSLblBuiltIn[name.charCodeAt(0)];
7245	var npflen = target - blob.l; if(opts && opts.biff == 2) --npflen;
7246	/*jshint -W018 */
7247	var rgce = (target == blob.l || cce === 0 || !(npflen > 0)) ? [] : parse_NameParsedFormula(blob, npflen, opts, cce);
7248	/*jshint +W018 */
7249	return {
7250		chKey: chKey,
7251		Name: name,
7252		itab: itab,
7253		rgce: rgce
7254	};
7255}
7256
7257/* [MS-XLS] 2.4.106 TODO: verify filename encoding */
7258function parse_ExternSheet(blob, length, opts) {
7259	if(opts.biff < 8) return parse_BIFF5ExternSheet(blob, length, opts);
7260	var o = [], target = blob.l + length, len = blob.read_shift(opts.biff > 8 ? 4 : 2);
7261	while(len-- !== 0) o.push(parse_XTI(blob, opts.biff > 8 ? 12 : 6, opts));
7262		// [iSupBook, itabFirst, itabLast];
7263	if(blob.l != target) throw new Error("Bad ExternSheet: " + blob.l + " != " + target);
7264	return o;
7265}
7266function parse_BIFF5ExternSheet(blob, length, opts) {
7267	if(blob[blob.l + 1] == 0x03) blob[blob.l]++;
7268	var o = parse_ShortXLUnicodeString(blob, length, opts);
7269	return o.charCodeAt(0) == 0x03 ? o.slice(1) : o;
7270}
7271
7272/* [MS-XLS] 2.4.176 TODO: check older biff */
7273function parse_NameCmt(blob, length, opts) {
7274	if(opts.biff < 8) { blob.l += length; return; }
7275	var cchName = blob.read_shift(2);
7276	var cchComment = blob.read_shift(2);
7277	var name = parse_XLUnicodeStringNoCch(blob, cchName, opts);
7278	var comment = parse_XLUnicodeStringNoCch(blob, cchComment, opts);
7279	return [name, comment];
7280}
7281
7282/* [MS-XLS] 2.4.260 */
7283function parse_ShrFmla(blob, length, opts) {
7284	var ref = parse_RefU(blob, 6);
7285	blob.l++;
7286	var cUse = blob.read_shift(1);
7287	length -= 8;
7288	return [parse_SharedParsedFormula(blob, length, opts), cUse, ref];
7289}
7290
7291/* [MS-XLS] 2.4.4 TODO */
7292function parse_Array(blob, length, opts) {
7293	var ref = parse_Ref(blob, 6);
7294	/* TODO: fAlwaysCalc */
7295	switch(opts.biff) {
7296		case 2: blob.l ++; length -= 7; break;
7297		case 3: case 4: blob.l += 2; length -= 8; break;
7298		default: blob.l += 6; length -= 12;
7299	}
7300	return [ref, parse_ArrayParsedFormula(blob, length, opts, ref)];
7301}
7302
7303/* [MS-XLS] 2.4.173 */
7304function parse_MTRSettings(blob) {
7305	var fMTREnabled = blob.read_shift(4) !== 0x00;
7306	var fUserSetThreadCount = blob.read_shift(4) !== 0x00;
7307	var cUserThreadCount = blob.read_shift(4);
7308	return [fMTREnabled, fUserSetThreadCount, cUserThreadCount];
7309}
7310
7311/* [MS-XLS] 2.5.186 TODO: BIFF5 */
7312function parse_NoteSh(blob, length, opts) {
7313	if(opts.biff < 8) return;
7314	var row = blob.read_shift(2), col = blob.read_shift(2);
7315	var flags = blob.read_shift(2), idObj = blob.read_shift(2);
7316	var stAuthor = parse_XLUnicodeString2(blob, 0, opts);
7317	if(opts.biff < 8) blob.read_shift(1);
7318	return [{r:row,c:col}, stAuthor, idObj, flags];
7319}
7320
7321/* [MS-XLS] 2.4.179 */
7322function parse_Note(blob, length, opts) {
7323	/* TODO: Support revisions */
7324	return parse_NoteSh(blob, length, opts);
7325}
7326
7327/* [MS-XLS] 2.4.168 */
7328function parse_MergeCells(blob, length)/*:Array<Range>*/ {
7329	var merges/*:Array<Range>*/ = [];
7330	var cmcs = blob.read_shift(2);
7331	while (cmcs--) merges.push(parse_Ref8U(blob,length));
7332	return merges;
7333}
7334function write_MergeCells(merges/*:Array<Range>*/) {
7335	var o = new_buf(2 + merges.length * 8);
7336	o.write_shift(2, merges.length);
7337	for(var i = 0; i < merges.length; ++i) write_Ref8U(merges[i], o);
7338	return o;
7339}
7340
7341/* [MS-XLS] 2.4.181 TODO: parse all the things! */
7342function parse_Obj(blob, length, opts) {
7343	if(opts && opts.biff < 8) return parse_BIFF5Obj(blob, length, opts);
7344	var cmo = parse_FtCmo(blob, 22); // id, ot, flags
7345	var fts = parse_FtArray(blob, length-22, cmo[1]);
7346	return { cmo: cmo, ft:fts };
7347}
7348/* from older spec */
7349var parse_BIFF5OT = {
73500x08: function(blob, length) {
7351	var tgt = blob.l + length;
7352	blob.l += 10; // todo
7353	var cf = blob.read_shift(2);
7354	blob.l += 4;
7355	blob.l += 2; //var cbPictFmla = blob.read_shift(2);
7356	blob.l += 2;
7357	blob.l += 2; //var grbit = blob.read_shift(2);
7358	blob.l += 4;
7359	var cchName = blob.read_shift(1);
7360	blob.l += cchName; // TODO: stName
7361	blob.l = tgt; // TODO: fmla
7362	return { fmt:cf };
7363}
7364};
7365
7366function parse_BIFF5Obj(blob, length, opts) {
7367	blob.l += 4; //var cnt = blob.read_shift(4);
7368	var ot = blob.read_shift(2);
7369	var id = blob.read_shift(2);
7370	var grbit = blob.read_shift(2);
7371	blob.l += 2; //var colL = blob.read_shift(2);
7372	blob.l += 2; //var dxL = blob.read_shift(2);
7373	blob.l += 2; //var rwT = blob.read_shift(2);
7374	blob.l += 2; //var dyT = blob.read_shift(2);
7375	blob.l += 2; //var colR = blob.read_shift(2);
7376	blob.l += 2; //var dxR = blob.read_shift(2);
7377	blob.l += 2; //var rwB = blob.read_shift(2);
7378	blob.l += 2; //var dyB = blob.read_shift(2);
7379	blob.l += 2; //var cbMacro = blob.read_shift(2);
7380	blob.l += 6;
7381	length -= 36;
7382	var fts = [];
7383	fts.push((parse_BIFF5OT[ot]||parsenoop)(blob, length, opts));
7384	return { cmo: [id, ot, grbit], ft:fts };
7385}
7386
7387/* [MS-XLS] 2.4.329 TODO: parse properly */
7388function parse_TxO(blob, length, opts) {
7389	var s = blob.l;
7390	var texts = "";
7391try {
7392	blob.l += 4;
7393	var ot = (opts.lastobj||{cmo:[0,0]}).cmo[1];
7394	var controlInfo; // eslint-disable-line no-unused-vars
7395	if([0,5,7,11,12,14].indexOf(ot) == -1) blob.l += 6;
7396	else controlInfo = parse_ControlInfo(blob, 6, opts); // eslint-disable-line no-unused-vars
7397	var cchText = blob.read_shift(2);
7398	/*var cbRuns = */blob.read_shift(2);
7399	/*var ifntEmpty = */parseuint16(blob, 2);
7400	var len = blob.read_shift(2);
7401	blob.l += len;
7402	//var fmla = parse_ObjFmla(blob, s + length - blob.l);
7403
7404	for(var i = 1; i < blob.lens.length-1; ++i) {
7405		if(blob.l-s != blob.lens[i]) throw new Error("TxO: bad continue record");
7406		var hdr = blob[blob.l];
7407		var t = parse_XLUnicodeStringNoCch(blob, blob.lens[i+1]-blob.lens[i]-1);
7408		texts += t;
7409		if(texts.length >= (hdr ? cchText : 2*cchText)) break;
7410	}
7411	if(texts.length !== cchText && texts.length !== cchText*2) {
7412		throw new Error("cchText: " + cchText + " != " + texts.length);
7413	}
7414
7415	blob.l = s + length;
7416	/* [MS-XLS] 2.5.272 TxORuns */
7417//	var rgTxoRuns = [];
7418//	for(var j = 0; j != cbRuns/8-1; ++j) blob.l += 8;
7419//	var cchText2 = blob.read_shift(2);
7420//	if(cchText2 !== cchText) throw new Error("TxOLastRun mismatch: " + cchText2 + " " + cchText);
7421//	blob.l += 6;
7422//	if(s + length != blob.l) throw new Error("TxO " + (s + length) + ", at " + blob.l);
7423	return { t: texts };
7424} catch(e) { blob.l = s + length; return { t: texts }; }
7425}
7426
7427/* [MS-XLS] 2.4.140 */
7428function parse_HLink(blob, length) {
7429	var ref = parse_Ref8U(blob, 8);
7430	blob.l += 16; /* CLSID */
7431	var hlink = parse_Hyperlink(blob, length-24);
7432	return [ref, hlink];
7433}
7434function write_HLink(hl) {
7435	var O = new_buf(24);
7436	var ref = decode_cell(hl[0]);
7437	O.write_shift(2, ref.r); O.write_shift(2, ref.r);
7438	O.write_shift(2, ref.c); O.write_shift(2, ref.c);
7439	var clsid = "d0 c9 ea 79 f9 ba ce 11 8c 82 00 aa 00 4b a9 0b".split(" ");
7440	for(var i = 0; i < 16; ++i) O.write_shift(1, parseInt(clsid[i], 16));
7441	return bconcat([O, write_Hyperlink(hl[1])]);
7442}
7443
7444
7445/* [MS-XLS] 2.4.141 */
7446function parse_HLinkTooltip(blob, length) {
7447	blob.read_shift(2);
7448	var ref = parse_Ref8U(blob, 8);
7449	var wzTooltip = blob.read_shift((length-10)/2, 'dbcs-cont');
7450	wzTooltip = wzTooltip.replace(chr0,"");
7451	return [ref, wzTooltip];
7452}
7453function write_HLinkTooltip(hl) {
7454	var TT = hl[1].Tooltip;
7455	var O = new_buf(10 + 2 * (TT.length + 1));
7456	O.write_shift(2, 0x0800);
7457	var ref = decode_cell(hl[0]);
7458	O.write_shift(2, ref.r); O.write_shift(2, ref.r);
7459	O.write_shift(2, ref.c); O.write_shift(2, ref.c);
7460	for(var i = 0; i < TT.length; ++i) O.write_shift(2, TT.charCodeAt(i));
7461	O.write_shift(2, 0);
7462	return O;
7463}
7464
7465/* [MS-XLS] 2.4.63 */
7466function parse_Country(blob)/*:[string|number, string|number]*/ {
7467	var o = [0,0], d;
7468	d = blob.read_shift(2); o[0] = CountryEnum[d] || d;
7469	d = blob.read_shift(2); o[1] = CountryEnum[d] || d;
7470	return o;
7471}
7472function write_Country(o) {
7473	if(!o) o = new_buf(4);
7474	o.write_shift(2, 0x01);
7475	o.write_shift(2, 0x01);
7476	return o;
7477}
7478
7479/* [MS-XLS] 2.4.50 ClrtClient */
7480function parse_ClrtClient(blob) {
7481	var ccv = blob.read_shift(2);
7482	var o = [];
7483	while(ccv-->0) o.push(parse_LongRGB(blob, 8));
7484	return o;
7485}
7486
7487/* [MS-XLS] 2.4.188 */
7488function parse_Palette(blob) {
7489	var ccv = blob.read_shift(2);
7490	var o = [];
7491	while(ccv-->0) o.push(parse_LongRGB(blob, 8));
7492	return o;
7493}
7494
7495/* [MS-XLS] 2.4.354 */
7496function parse_XFCRC(blob) {
7497	blob.l += 2;
7498	var o = {cxfs:0, crc:0};
7499	o.cxfs = blob.read_shift(2);
7500	o.crc = blob.read_shift(4);
7501	return o;
7502}
7503
7504/* [MS-XLS] 2.4.53 TODO: parse flags */
7505/* [MS-XLSB] 2.4.323 TODO: parse flags */
7506function parse_ColInfo(blob, length, opts) {
7507	if(!opts.cellStyles) return parsenoop(blob, length);
7508	var w = opts && opts.biff >= 12 ? 4 : 2;
7509	var colFirst = blob.read_shift(w);
7510	var colLast = blob.read_shift(w);
7511	var coldx = blob.read_shift(w);
7512	var ixfe = blob.read_shift(w);
7513	var flags = blob.read_shift(2);
7514	if(w == 2) blob.l += 2;
7515	var o = ({s:colFirst, e:colLast, w:coldx, ixfe:ixfe, flags:flags}/*:any*/);
7516	if(opts.biff >= 5 || !opts.biff) o.level = (flags >> 8) & 0x7;
7517	return o;
7518}
7519function write_ColInfo(col, idx) {
7520	var o = new_buf(12);
7521	o.write_shift(2, idx);
7522	o.write_shift(2, idx);
7523	o.write_shift(2, col.width * 256);
7524	o.write_shift(2, 0);
7525	var f = 0;
7526	if(col.hidden) f |= 1;
7527	o.write_shift(1, f);
7528	f = col.level || 0;
7529	o.write_shift(1, f);
7530	o.write_shift(2, 0);
7531	return o;
7532}
7533
7534/* [MS-XLS] 2.4.257 */
7535function parse_Setup(blob, length) {
7536	var o = {};
7537	if(length < 32) return o;
7538	blob.l += 16;
7539	o.header = parse_Xnum(blob, 8);
7540	o.footer = parse_Xnum(blob, 8);
7541	blob.l += 2;
7542	return o;
7543}
7544
7545/* [MS-XLS] 2.4.261 */
7546function parse_ShtProps(blob, length, opts) {
7547	var def = {area:false};
7548	if(opts.biff != 5) { blob.l += length; return def; }
7549	var d = blob.read_shift(1); blob.l += 3;
7550	if((d & 0x10)) def.area = true;
7551	return def;
7552}
7553
7554/* [MS-XLS] 2.4.241 */
7555function write_RRTabId(n/*:number*/) {
7556	var out = new_buf(2 * n);
7557	for(var i = 0; i < n; ++i) out.write_shift(2, i+1);
7558	return out;
7559}
7560
7561var parse_Blank = parse_XLSCell; /* [MS-XLS] 2.4.20 Just the cell */
7562var parse_Scl = parseuint16a; /* [MS-XLS] 2.4.247 num, den */
7563var parse_String = parse_XLUnicodeString; /* [MS-XLS] 2.4.268 */
7564
7565/* --- Specific to versions before BIFF8 --- */
7566function parse_ImData(blob) {
7567	var cf = blob.read_shift(2);
7568	var env = blob.read_shift(2);
7569	var lcb = blob.read_shift(4);
7570	var o = {fmt:cf, env:env, len:lcb, data:blob.slice(blob.l,blob.l+lcb)};
7571	blob.l += lcb;
7572	return o;
7573}
7574
7575/* BIFF2_??? where ??? is the name from [XLS] */
7576function parse_BIFF2STR(blob, length, opts) {
7577	if(opts.biffguess && opts.biff == 5) opts.biff = 2;
7578	var cell = parse_XLSCell(blob, 6);
7579	++blob.l;
7580	var str = parse_XLUnicodeString2(blob, length-7, opts);
7581	cell.t = 'str';
7582	cell.val = str;
7583	return cell;
7584}
7585
7586function parse_BIFF2NUM(blob/*::, length*/) {
7587	var cell = parse_XLSCell(blob, 6);
7588	++blob.l;
7589	var num = parse_Xnum(blob, 8);
7590	cell.t = 'n';
7591	cell.val = num;
7592	return cell;
7593}
7594function write_BIFF2NUM(r/*:number*/, c/*:number*/, val/*:number*/) {
7595	var out = new_buf(15);
7596	write_BIFF2Cell(out, r, c);
7597	out.write_shift(8, val, 'f');
7598	return out;
7599}
7600
7601function parse_BIFF2INT(blob) {
7602	var cell = parse_XLSCell(blob, 6);
7603	++blob.l;
7604	var num = blob.read_shift(2);
7605	cell.t = 'n';
7606	cell.val = num;
7607	return cell;
7608}
7609function write_BIFF2INT(r/*:number*/, c/*:number*/, val/*:number*/) {
7610	var out = new_buf(9);
7611	write_BIFF2Cell(out, r, c);
7612	out.write_shift(2, val);
7613	return out;
7614}
7615
7616function parse_BIFF2STRING(blob) {
7617	var cch = blob.read_shift(1);
7618	if(cch === 0) { blob.l++; return ""; }
7619	return blob.read_shift(cch, 'sbcs-cont');
7620}
7621
7622/* TODO: convert to BIFF8 font struct */
7623function parse_BIFF2FONTXTRA(blob, length) {
7624	blob.l += 6; // unknown
7625	blob.l += 2; // font weight "bls"
7626	blob.l += 1; // charset
7627	blob.l += 3; // unknown
7628	blob.l += 1; // font family
7629	blob.l += length - 13;
7630}
7631
7632/* TODO: parse rich text runs */
7633function parse_RString(blob, length, opts) {
7634	var end = blob.l + length;
7635	var cell = parse_XLSCell(blob, 6);
7636	var cch = blob.read_shift(2);
7637	var str = parse_XLUnicodeStringNoCch(blob, cch, opts);
7638	blob.l = end;
7639	cell.t = 'str';
7640	cell.val = str;
7641	return cell;
7642}
7643var DBF_SUPPORTED_VERSIONS = [0x02, 0x03, 0x30, 0x31, 0x83, 0x8B, 0x8C, 0xF5];
7644var DBF = /*#__PURE__*/(function() {
7645var dbf_codepage_map = {
7646	/* Code Pages Supported by Visual FoxPro */
7647	/*::[*/0x01/*::]*/:   437,           /*::[*/0x02/*::]*/:   850,
7648	/*::[*/0x03/*::]*/:  1252,           /*::[*/0x04/*::]*/: 10000,
7649	/*::[*/0x64/*::]*/:   852,           /*::[*/0x65/*::]*/:   866,
7650	/*::[*/0x66/*::]*/:   865,           /*::[*/0x67/*::]*/:   861,
7651	/*::[*/0x68/*::]*/:   895,           /*::[*/0x69/*::]*/:   620,
7652	/*::[*/0x6A/*::]*/:   737,           /*::[*/0x6B/*::]*/:   857,
7653	/*::[*/0x78/*::]*/:   950,           /*::[*/0x79/*::]*/:   949,
7654	/*::[*/0x7A/*::]*/:   936,           /*::[*/0x7B/*::]*/:   932,
7655	/*::[*/0x7C/*::]*/:   874,           /*::[*/0x7D/*::]*/:  1255,
7656	/*::[*/0x7E/*::]*/:  1256,           /*::[*/0x96/*::]*/: 10007,
7657	/*::[*/0x97/*::]*/: 10029,           /*::[*/0x98/*::]*/: 10006,
7658	/*::[*/0xC8/*::]*/:  1250,           /*::[*/0xC9/*::]*/:  1251,
7659	/*::[*/0xCA/*::]*/:  1254,           /*::[*/0xCB/*::]*/:  1253,
7660
7661	/* shapefile DBF extension */
7662	/*::[*/0x00/*::]*/: 20127,           /*::[*/0x08/*::]*/:   865,
7663	/*::[*/0x09/*::]*/:   437,           /*::[*/0x0A/*::]*/:   850,
7664	/*::[*/0x0B/*::]*/:   437,           /*::[*/0x0D/*::]*/:   437,
7665	/*::[*/0x0E/*::]*/:   850,           /*::[*/0x0F/*::]*/:   437,
7666	/*::[*/0x10/*::]*/:   850,           /*::[*/0x11/*::]*/:   437,
7667	/*::[*/0x12/*::]*/:   850,           /*::[*/0x13/*::]*/:   932,
7668	/*::[*/0x14/*::]*/:   850,           /*::[*/0x15/*::]*/:   437,
7669	/*::[*/0x16/*::]*/:   850,           /*::[*/0x17/*::]*/:   865,
7670	/*::[*/0x18/*::]*/:   437,           /*::[*/0x19/*::]*/:   437,
7671	/*::[*/0x1A/*::]*/:   850,           /*::[*/0x1B/*::]*/:   437,
7672	/*::[*/0x1C/*::]*/:   863,           /*::[*/0x1D/*::]*/:   850,
7673	/*::[*/0x1F/*::]*/:   852,           /*::[*/0x22/*::]*/:   852,
7674	/*::[*/0x23/*::]*/:   852,           /*::[*/0x24/*::]*/:   860,
7675	/*::[*/0x25/*::]*/:   850,           /*::[*/0x26/*::]*/:   866,
7676	/*::[*/0x37/*::]*/:   850,           /*::[*/0x40/*::]*/:   852,
7677	/*::[*/0x4D/*::]*/:   936,           /*::[*/0x4E/*::]*/:   949,
7678	/*::[*/0x4F/*::]*/:   950,           /*::[*/0x50/*::]*/:   874,
7679	/*::[*/0x57/*::]*/:  1252,           /*::[*/0x58/*::]*/:  1252,
7680	/*::[*/0x59/*::]*/:  1252,           /*::[*/0x6C/*::]*/:   863,
7681	/*::[*/0x86/*::]*/:   737,           /*::[*/0x87/*::]*/:   852,
7682	/*::[*/0x88/*::]*/:   857,           /*::[*/0xCC/*::]*/:  1257,
7683
7684	/*::[*/0xFF/*::]*/: 16969
7685};
7686var dbf_reverse_map = evert({
7687	/*::[*/0x01/*::]*/:   437,           /*::[*/0x02/*::]*/:   850,
7688	/*::[*/0x03/*::]*/:  1252,           /*::[*/0x04/*::]*/: 10000,
7689	/*::[*/0x64/*::]*/:   852,           /*::[*/0x65/*::]*/:   866,
7690	/*::[*/0x66/*::]*/:   865,           /*::[*/0x67/*::]*/:   861,
7691	/*::[*/0x68/*::]*/:   895,           /*::[*/0x69/*::]*/:   620,
7692	/*::[*/0x6A/*::]*/:   737,           /*::[*/0x6B/*::]*/:   857,
7693	/*::[*/0x78/*::]*/:   950,           /*::[*/0x79/*::]*/:   949,
7694	/*::[*/0x7A/*::]*/:   936,           /*::[*/0x7B/*::]*/:   932,
7695	/*::[*/0x7C/*::]*/:   874,           /*::[*/0x7D/*::]*/:  1255,
7696	/*::[*/0x7E/*::]*/:  1256,           /*::[*/0x96/*::]*/: 10007,
7697	/*::[*/0x97/*::]*/: 10029,           /*::[*/0x98/*::]*/: 10006,
7698	/*::[*/0xC8/*::]*/:  1250,           /*::[*/0xC9/*::]*/:  1251,
7699	/*::[*/0xCA/*::]*/:  1254,           /*::[*/0xCB/*::]*/:  1253,
7700	/*::[*/0x00/*::]*/: 20127
7701});
7702/* TODO: find an actual specification */
7703function dbf_to_aoa(buf, opts)/*:AOA*/ {
7704	var out/*:AOA*/ = [];
7705	var d/*:Block*/ = (new_raw_buf(1)/*:any*/);
7706	switch(opts.type) {
7707		case 'base64': d = s2a(Base64_decode(buf)); break;
7708		case 'binary': d = s2a(buf); break;
7709		case 'buffer':
7710		case 'array': d = buf; break;
7711	}
7712	prep_blob(d, 0);
7713
7714	/* header */
7715	var ft = d.read_shift(1);
7716	var memo = !!(ft & 0x88);
7717	var vfp = false, l7 = false;
7718	switch(ft) {
7719		case 0x02: break; // dBASE II
7720		case 0x03: break; // dBASE III
7721		case 0x30: vfp = true; memo = true; break; // VFP
7722		case 0x31: vfp = true; memo = true; break; // VFP with autoincrement
7723		// 0x43 dBASE IV SQL table files
7724		// 0x63 dBASE IV SQL system files
7725		case 0x83: break; // dBASE III with memo
7726		case 0x8B: break; // dBASE IV with memo
7727		case 0x8C: l7 = true; break; // dBASE Level 7 with memo
7728		// case 0xCB dBASE IV SQL table files with memo
7729		case 0xF5: break; // FoxPro 2.x with memo
7730		// case 0xFB FoxBASE
7731		default: throw new Error("DBF Unsupported Version: " + ft.toString(16));
7732	}
7733
7734	var nrow = 0, fpos = 0x0209;
7735	if(ft == 0x02) nrow = d.read_shift(2);
7736	d.l += 3; // dBASE II stores DDMMYY date, others use YYMMDD
7737	if(ft != 0x02) nrow = d.read_shift(4);
7738	if(nrow > 1048576) nrow = 1e6;
7739
7740	if(ft != 0x02) fpos = d.read_shift(2); // header length
7741	var rlen = d.read_shift(2); // record length
7742
7743	var /*flags = 0,*/ current_cp = opts.codepage || 1252;
7744	if(ft != 0x02) { // 20 reserved bytes
7745		d.l+=16;
7746		/*flags = */d.read_shift(1);
7747		//if(memo && ((flags & 0x02) === 0)) throw new Error("DBF Flags " + flags.toString(16) + " ft " + ft.toString(16));
7748
7749		/* codepage present in FoxPro and dBASE Level 7 */
7750		if(d[d.l] !== 0) current_cp = dbf_codepage_map[d[d.l]];
7751		d.l+=1;
7752
7753		d.l+=2;
7754	}
7755	if(l7) d.l += 36; // Level 7: 32 byte "Language driver name", 4 byte reserved
7756
7757/*:: type DBFField = { name:string; len:number; type:string; } */
7758	var fields/*:Array<DBFField>*/ = [], field/*:DBFField*/ = ({}/*:any*/);
7759	var hend = Math.min(d.length, (ft == 0x02 ? 0x209 : (fpos - 10 - (vfp ? 264 : 0))));
7760	var ww = l7 ? 32 : 11;
7761	while(d.l < hend && d[d.l] != 0x0d) {
7762		field = ({}/*:any*/);
7763		field.name = (typeof $cptable !== "undefined" ? $cptable.utils.decode(current_cp, d.slice(d.l, d.l+ww)) : a2s(d.slice(d.l, d.l + ww))).replace(/[\u0000\r\n].*$/g,"");
7764		d.l += ww;
7765		field.type = String.fromCharCode(d.read_shift(1));
7766		if(ft != 0x02 && !l7) field.offset = d.read_shift(4);
7767		field.len = d.read_shift(1);
7768		if(ft == 0x02) field.offset = d.read_shift(2);
7769		field.dec = d.read_shift(1);
7770		if(field.name.length) fields.push(field);
7771		if(ft != 0x02) d.l += l7 ? 13 : 14;
7772		switch(field.type) {
7773			case 'B': // Double (VFP) / Binary (dBASE L7)
7774				if((!vfp || field.len != 8) && opts.WTF) console.log('Skipping ' + field.name + ':' + field.type);
7775				break;
7776			case 'G': // General (FoxPro and dBASE L7)
7777			case 'P': // Picture (FoxPro and dBASE L7)
7778				if(opts.WTF) console.log('Skipping ' + field.name + ':' + field.type);
7779				break;
7780			case '+': // Autoincrement (dBASE L7 only)
7781			case '0': // _NullFlags (VFP only)
7782			case '@': // Timestamp (dBASE L7 only)
7783			case 'C': // Character (dBASE II)
7784			case 'D': // Date (dBASE III)
7785			case 'F': // Float (dBASE IV)
7786			case 'I': // Long (VFP and dBASE L7)
7787			case 'L': // Logical (dBASE II)
7788			case 'M': // Memo (dBASE III)
7789			case 'N': // Number (dBASE II)
7790			case 'O': // Double (dBASE L7 only)
7791			case 'T': // Datetime (VFP only)
7792			case 'Y': // Currency (VFP only)
7793				break;
7794			default: throw new Error('Unknown Field Type: ' + field.type);
7795		}
7796	}
7797
7798	if(d[d.l] !== 0x0D) d.l = fpos-1;
7799	if(d.read_shift(1) !== 0x0D) throw new Error("DBF Terminator not found " + d.l + " " + d[d.l]);
7800	d.l = fpos;
7801
7802	/* data */
7803	var R = 0, C = 0;
7804	out[0] = [];
7805	for(C = 0; C != fields.length; ++C) out[0][C] = fields[C].name;
7806	while(nrow-- > 0) {
7807		if(d[d.l] === 0x2A) {
7808			// TODO: record marked as deleted -- create a hidden row?
7809			d.l+=rlen;
7810			continue;
7811		}
7812		++d.l;
7813		out[++R] = []; C = 0;
7814		for(C = 0; C != fields.length; ++C) {
7815			var dd = d.slice(d.l, d.l+fields[C].len); d.l+=fields[C].len;
7816			prep_blob(dd, 0);
7817			var s = typeof $cptable !== "undefined" ? $cptable.utils.decode(current_cp, dd) : a2s(dd);
7818			switch(fields[C].type) {
7819				case 'C':
7820					// NOTE: it is conventional to write '  /  /  ' for empty dates
7821					if(s.trim().length) out[R][C] = s.replace(/\s+$/,"");
7822					break;
7823				case 'D':
7824					if(s.length === 8) out[R][C] = new Date(+s.slice(0,4), +s.slice(4,6)-1, +s.slice(6,8));
7825					else out[R][C] = s;
7826					break;
7827				case 'F': out[R][C] = parseFloat(s.trim()); break;
7828				case '+': case 'I': out[R][C] = l7 ? dd.read_shift(-4, 'i') ^ 0x80000000 : dd.read_shift(4, 'i'); break;
7829				case 'L': switch(s.trim().toUpperCase()) {
7830					case 'Y': case 'T': out[R][C] = true; break;
7831					case 'N': case 'F': out[R][C] = false; break;
7832					case '': case '?': break;
7833					default: throw new Error("DBF Unrecognized L:|" + s + "|");
7834					} break;
7835				case 'M': /* TODO: handle memo files */
7836					if(!memo) throw new Error("DBF Unexpected MEMO for type " + ft.toString(16));
7837					out[R][C] = "##MEMO##" + (l7 ? parseInt(s.trim(), 10): dd.read_shift(4));
7838					break;
7839				case 'N':
7840					s = s.replace(/\u0000/g,"").trim();
7841					// NOTE: dBASE II interprets "  .  " as 0
7842					if(s && s != ".") out[R][C] = +s || 0; break;
7843				case '@':
7844					// NOTE: dBASE specs appear to be incorrect
7845					out[R][C] = new Date(dd.read_shift(-8, 'f') - 0x388317533400);
7846					break;
7847				case 'T': out[R][C] = new Date((dd.read_shift(4) - 0x253D8C) * 0x5265C00 + dd.read_shift(4)); break;
7848				case 'Y': out[R][C] = dd.read_shift(4,'i')/1e4 + (dd.read_shift(4, 'i')/1e4)*Math.pow(2,32); break;
7849				case 'O': out[R][C] = -dd.read_shift(-8, 'f'); break;
7850				case 'B': if(vfp && fields[C].len == 8) { out[R][C] = dd.read_shift(8,'f'); break; }
7851					/* falls through */
7852				case 'G': case 'P': dd.l += fields[C].len; break;
7853				case '0':
7854					if(fields[C].name === '_NullFlags') break;
7855					/* falls through */
7856				default: throw new Error("DBF Unsupported data type " + fields[C].type);
7857			}
7858		}
7859	}
7860	if(ft != 0x02) if(d.l < d.length && d[d.l++] != 0x1A) throw new Error("DBF EOF Marker missing " + (d.l-1) + " of " + d.length + " " + d[d.l-1].toString(16));
7861	if(opts && opts.sheetRows) out = out.slice(0, opts.sheetRows);
7862	opts.DBF = fields;
7863	return out;
7864}
7865
7866function dbf_to_sheet(buf, opts)/*:Worksheet*/ {
7867	var o = opts || {};
7868	if(!o.dateNF) o.dateNF = "yyyymmdd";
7869	var ws = aoa_to_sheet(dbf_to_aoa(buf, o), o);
7870	ws["!cols"] = o.DBF.map(function(field) { return {
7871		wch: field.len,
7872		DBF: field
7873	};});
7874	delete o.DBF;
7875	return ws;
7876}
7877
7878function dbf_to_workbook(buf, opts)/*:Workbook*/ {
7879	try {
7880		var o = sheet_to_workbook(dbf_to_sheet(buf, opts), opts);
7881		o.bookType = "dbf";
7882		return o;
7883	} catch(e) { if(opts && opts.WTF) throw e; }
7884	return ({SheetNames:[],Sheets:{}});
7885}
7886
7887var _RLEN = { 'B': 8, 'C': 250, 'L': 1, 'D': 8, '?': 0, '': 0 };
7888function sheet_to_dbf(ws/*:Worksheet*/, opts/*:WriteOpts*/) {
7889	var o = opts || {};
7890	var old_cp = current_codepage;
7891	if(+o.codepage >= 0) set_cp(+o.codepage);
7892	if(o.type == "string") throw new Error("Cannot write DBF to JS string");
7893	var ba = buf_array();
7894	var aoa/*:AOA*/ = sheet_to_json(ws, {header:1, raw:true, cellDates:true});
7895	var headers = aoa[0], data = aoa.slice(1), cols = ws["!cols"] || [];
7896	var i = 0, j = 0, hcnt = 0, rlen = 1;
7897	for(i = 0; i < headers.length; ++i) {
7898		if(((cols[i]||{}).DBF||{}).name) { headers[i] = cols[i].DBF.name; ++hcnt; continue; }
7899		if(headers[i] == null) continue;
7900		++hcnt;
7901		if(typeof headers[i] === 'number') headers[i] = headers[i].toString(10);
7902		if(typeof headers[i] !== 'string') throw new Error("DBF Invalid column name " + headers[i] + " |" + (typeof headers[i]) + "|");
7903		if(headers.indexOf(headers[i]) !== i) for(j=0; j<1024;++j)
7904			if(headers.indexOf(headers[i] + "_" + j) == -1) { headers[i] += "_" + j; break; }
7905	}
7906	var range = safe_decode_range(ws['!ref']);
7907	var coltypes/*:Array<string>*/ = [];
7908	var colwidths/*:Array<number>*/ = [];
7909	var coldecimals/*:Array<number>*/ = [];
7910	for(i = 0; i <= range.e.c - range.s.c; ++i) {
7911		var guess = '', _guess = '', maxlen = 0;
7912		var col/*:Array<any>*/ = [];
7913		for(j=0; j < data.length; ++j) {
7914			if(data[j][i] != null) col.push(data[j][i]);
7915		}
7916		if(col.length == 0 || headers[i] == null) { coltypes[i] = '?'; continue; }
7917		for(j = 0; j < col.length; ++j) {
7918			switch(typeof col[j]) {
7919				/* TODO: check if L2 compat is desired */
7920				case 'number': _guess = 'B'; break;
7921				case 'string': _guess = 'C'; break;
7922				case 'boolean': _guess = 'L'; break;
7923				case 'object': _guess = col[j] instanceof Date ? 'D' : 'C'; break;
7924				default: _guess = 'C';
7925			}
7926			/* TODO: cache the values instead of encoding twice */
7927			maxlen = Math.max(maxlen, (typeof $cptable !== "undefined" && typeof col[j] == "string" ? $cptable.utils.encode(current_ansi, col[j]): String(col[j])).length);
7928			guess = guess && guess != _guess ? 'C' : _guess;
7929			//if(guess == 'C') break;
7930		}
7931		if(maxlen > 250) maxlen = 250;
7932		_guess = ((cols[i]||{}).DBF||{}).type;
7933		/* TODO: more fine grained control over DBF type resolution */
7934		if(_guess == 'C') {
7935			if(cols[i].DBF.len > maxlen) maxlen = cols[i].DBF.len;
7936		}
7937		if(guess == 'B' && _guess == 'N') {
7938			guess = 'N';
7939			coldecimals[i] = cols[i].DBF.dec;
7940			maxlen = cols[i].DBF.len;
7941		}
7942		colwidths[i] = guess == 'C' || _guess == 'N' ? maxlen : (_RLEN[guess] || 0);
7943		rlen += colwidths[i];
7944		coltypes[i] = guess;
7945	}
7946
7947	var h = ba.next(32);
7948	h.write_shift(4, 0x13021130);
7949	h.write_shift(4, data.length);
7950	h.write_shift(2, 296 + 32 * hcnt);
7951	h.write_shift(2, rlen);
7952	for(i=0; i < 4; ++i) h.write_shift(4, 0);
7953	var cp = +dbf_reverse_map[/*::String(*/current_codepage/*::)*/] || 0x03;
7954	h.write_shift(4, 0x00000000 | (cp<<8));
7955	if(dbf_codepage_map[cp] != +o.codepage) {
7956		if(o.codepage) console.error("DBF Unsupported codepage " + current_codepage + ", using 1252");
7957		current_codepage = 1252;
7958	}
7959
7960	for(i = 0, j = 0; i < headers.length; ++i) {
7961		if(headers[i] == null) continue;
7962		var hf = ba.next(32);
7963		/* TODO: test how applications handle non-ASCII field names */
7964		var _f = (headers[i].slice(-10) + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00").slice(0, 11);
7965		hf.write_shift(1, _f, "sbcs");
7966		hf.write_shift(1, coltypes[i] == '?' ? 'C' : coltypes[i], "sbcs");
7967		hf.write_shift(4, j);
7968		hf.write_shift(1, colwidths[i] || _RLEN[coltypes[i]] || 0);
7969		hf.write_shift(1, coldecimals[i] || 0);
7970		hf.write_shift(1, 0x02);
7971		hf.write_shift(4, 0);
7972		hf.write_shift(1, 0);
7973		hf.write_shift(4, 0);
7974		hf.write_shift(4, 0);
7975		j += (colwidths[i] || _RLEN[coltypes[i]] || 0);
7976	}
7977
7978	var hb = ba.next(264);
7979	hb.write_shift(4, 0x0000000D);
7980	for(i=0; i < 65;++i) hb.write_shift(4, 0x00000000);
7981	for(i=0; i < data.length; ++i) {
7982		var rout = ba.next(rlen);
7983		rout.write_shift(1, 0);
7984		for(j=0; j<headers.length; ++j) {
7985			if(headers[j] == null) continue;
7986			switch(coltypes[j]) {
7987				case 'L': rout.write_shift(1, data[i][j] == null ? 0x3F : data[i][j] ? 0x54 : 0x46); break;
7988				case 'B': rout.write_shift(8, data[i][j]||0, 'f'); break;
7989				case 'N':
7990					var _n = "0";
7991					if(typeof data[i][j] == "number") _n = data[i][j].toFixed(coldecimals[j]||0);
7992					for(hcnt=0; hcnt < colwidths[j]-_n.length; ++hcnt) rout.write_shift(1, 0x20);
7993					rout.write_shift(1, _n, "sbcs");
7994					break;
7995				case 'D':
7996					if(!data[i][j]) rout.write_shift(8, "00000000", "sbcs");
7997					else {
7998						rout.write_shift(4, ("0000"+data[i][j].getFullYear()).slice(-4), "sbcs");
7999						rout.write_shift(2, ("00"+(data[i][j].getMonth()+1)).slice(-2), "sbcs");
8000						rout.write_shift(2, ("00"+data[i][j].getDate()).slice(-2), "sbcs");
8001					} break;
8002				case 'C':
8003					var _l = rout.l;
8004					var _s = String(data[i][j] != null ? data[i][j] : "").slice(0, colwidths[j]);
8005					rout.write_shift(1, _s, "cpstr");
8006					_l += colwidths[j] - rout.l;
8007					for(hcnt=0; hcnt < _l; ++hcnt) rout.write_shift(1, 0x20); break;
8008			}
8009		}
8010		// data
8011	}
8012	current_codepage = old_cp;
8013	ba.next(1).write_shift(1, 0x1A);
8014	return ba.end();
8015}
8016	return {
8017		to_workbook: dbf_to_workbook,
8018		to_sheet: dbf_to_sheet,
8019		from_sheet: sheet_to_dbf
8020	};
8021})();
8022
8023var SYLK = /*#__PURE__*/(function() {
8024	/* TODO: stress test sequences */
8025	var sylk_escapes = ({
8026		AA:'À', BA:'Á', CA:'Â', DA:195, HA:'Ä', JA:197,
8027		AE:'È', BE:'É', CE:'Ê',         HE:'Ë',
8028		AI:'Ì', BI:'Í', CI:'Î',         HI:'Ï',
8029		AO:'Ò', BO:'Ó', CO:'Ô', DO:213, HO:'Ö',
8030		AU:'Ù', BU:'Ú', CU:'Û',         HU:'Ü',
8031		Aa:'à', Ba:'á', Ca:'â', Da:227, Ha:'ä', Ja:229,
8032		Ae:'è', Be:'é', Ce:'ê',         He:'ë',
8033		Ai:'ì', Bi:'í', Ci:'î',         Hi:'ï',
8034		Ao:'ò', Bo:'ó', Co:'ô', Do:245, Ho:'ö',
8035		Au:'ù', Bu:'ú', Cu:'û',         Hu:'ü',
8036		KC:'Ç', Kc:'ç', q:'æ',  z:'œ',  a:'Æ',  j:'Œ',
8037		DN:209, Dn:241, Hy:255,
8038		S:169,  c:170,  R:174,  "B ":180,
8039		/*::[*/0/*::]*/:176,    /*::[*/1/*::]*/:177,  /*::[*/2/*::]*/:178,
8040		/*::[*/3/*::]*/:179,    /*::[*/5/*::]*/:181,  /*::[*/6/*::]*/:182,
8041		/*::[*/7/*::]*/:183,    Q:185,  k:186,  b:208,  i:216,  l:222,  s:240,  y:248,
8042		"!":161, '"':162, "#":163, "(":164, "%":165, "'":167, "H ":168,
8043		"+":171, ";":187, "<":188, "=":189, ">":190, "?":191, "{":223
8044	}/*:any*/);
8045	var sylk_char_regex = new RegExp("\u001BN(" + keys(sylk_escapes).join("|").replace(/\|\|\|/, "|\\||").replace(/([?()+])/g,"\\$1") + "|\\|)", "gm");
8046	var sylk_char_fn = function(_, $1){ var o = sylk_escapes[$1]; return typeof o == "number" ? _getansi(o) : o; };
8047	var decode_sylk_char = function($$, $1, $2) { var newcc = (($1.charCodeAt(0) - 0x20)<<4) | ($2.charCodeAt(0) - 0x30); return newcc == 59 ? $$ : _getansi(newcc); };
8048	sylk_escapes["|"] = 254;
8049	/* https://oss.sheetjs.com/notes/sylk/ for more details */
8050	function sylk_to_aoa(d/*:RawData*/, opts)/*:[AOA, Worksheet]*/ {
8051		switch(opts.type) {
8052			case 'base64': return sylk_to_aoa_str(Base64_decode(d), opts);
8053			case 'binary': return sylk_to_aoa_str(d, opts);
8054			case 'buffer': return sylk_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
8055			case 'array': return sylk_to_aoa_str(cc2str(d), opts);
8056		}
8057		throw new Error("Unrecognized type " + opts.type);
8058	}
8059	function sylk_to_aoa_str(str/*:string*/, opts)/*:[AOA, Worksheet]*/ {
8060		var records = str.split(/[\n\r]+/), R = -1, C = -1, ri = 0, rj = 0, arr/*:AOA*/ = [];
8061		var formats/*:Array<string>*/ = [];
8062		var next_cell_format/*:string|null*/ = null;
8063		var sht = {}, rowinfo/*:Array<RowInfo>*/ = [], colinfo/*:Array<ColInfo>*/ = [], cw/*:Array<string>*/ = [];
8064		var Mval = 0, j;
8065		var wb = { Workbook: { WBProps: {}, Names: [] } };
8066		if(+opts.codepage >= 0) set_cp(+opts.codepage);
8067		for (; ri !== records.length; ++ri) {
8068			Mval = 0;
8069			var rstr=records[ri].trim().replace(/\x1B([\x20-\x2F])([\x30-\x3F])/g, decode_sylk_char).replace(sylk_char_regex, sylk_char_fn);
8070			var record=rstr.replace(/;;/g, "\u0000").split(";").map(function(x) { return x.replace(/\u0000/g, ";"); });
8071			var RT=record[0], val;
8072			if(rstr.length > 0) switch(RT) {
8073			case 'ID': break; /* header */
8074			case 'E': break; /* EOF */
8075			case 'B': break; /* dimensions */
8076			case 'O': /* workbook options */
8077			for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
8078				case 'V': {
8079					var d1904 = parseInt(record[rj].slice(1), 10);
8080					// NOTE: it is technically an error if d1904 >= 5 or < 0
8081					if(d1904 >= 1 && d1904 <= 4) wb.Workbook.WBProps.date1904 = true;
8082				} break;
8083			} break;
8084			case 'W': break; /* window */
8085			case 'P':
8086				switch(record[1].charAt(0)){
8087					case 'P': formats.push(rstr.slice(3).replace(/;;/g, ";")); break;
8088				} break;
8089			case 'NN': { /* defined name */
8090				var nn = {Sheet: 0};
8091				for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
8092					case 'N': nn.Name = record[rj].slice(1); break;
8093					case 'E': nn.Ref = (opts && opts.sheet || "Sheet1") + "!" + rc_to_a1(record[rj].slice(1)); break;
8094				}
8095				wb.Workbook.Names.push(nn);
8096			} break;
8097			// case 'NE': // ??
8098			// case 'NU': // ??
8099			case 'C': /* cell */
8100			var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1, formula = "", cell_t = "z";
8101			for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
8102				case 'A': break; // TODO: comment
8103				case 'X': C = parseInt(record[rj].slice(1), 10)-1; C_seen_X = true; break;
8104				case 'Y':
8105					R = parseInt(record[rj].slice(1), 10)-1; if(!C_seen_X) C = 0;
8106					for(j = arr.length; j <= R; ++j) arr[j] = [];
8107					break;
8108				case 'K':
8109					val = record[rj].slice(1);
8110					if(val.charAt(0) === '"') { val = val.slice(1,val.length - 1); cell_t = "s"; }
8111					else if(val === 'TRUE' || val === 'FALSE') { val = val === 'TRUE'; cell_t = "b"; }
8112					else if(!isNaN(fuzzynum(val))) {
8113						val = fuzzynum(val); cell_t = "n";
8114						if(next_cell_format !== null && fmt_is_date(next_cell_format) && opts.cellDates) { val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val); cell_t = "d"; }
8115					} else if(!isNaN(fuzzydate(val).getDate())) {
8116						val = parseDate(val); cell_t = "d";
8117						if(!opts.cellDates) { cell_t = "n"; val = datenum(val, wb.Workbook.WBProps.date1904); }
8118					}
8119					if(typeof $cptable !== 'undefined' && typeof val == "string" && ((opts||{}).type != "string") && (opts||{}).codepage) val = $cptable.utils.decode(opts.codepage, val);
8120					C_seen_K = true;
8121					break;
8122				case 'E':
8123					C_seen_E = true;
8124					formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
8125					break;
8126				case 'S':
8127					C_seen_S = true;
8128					break;
8129				case 'G': break; // unknown
8130				case 'R': _R = parseInt(record[rj].slice(1), 10)-1; break;
8131				case 'C': _C = parseInt(record[rj].slice(1), 10)-1; break;
8132				// case 'P': // ??
8133				// case 'D': // ??
8134				default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
8135			}
8136			if(C_seen_K) {
8137				if(!arr[R][C]) arr[R][C] = { t: cell_t, v: val };
8138				else { arr[R][C].t = cell_t; arr[R][C].v = val; }
8139				if(next_cell_format) arr[R][C].z = next_cell_format;
8140				if(opts.cellText !== false && next_cell_format) arr[R][C].w = SSF_format(arr[R][C].z, arr[R][C].v, { date1904: wb.Workbook.WBProps.date1904 });
8141				next_cell_format = null;
8142			}
8143			if(C_seen_S) {
8144				if(C_seen_E) throw new Error("SYLK shared formula cannot have own formula");
8145				var shrbase = _R > -1 && arr[_R][_C];
8146				if(!shrbase || !shrbase[1]) throw new Error("SYLK shared formula cannot find base");
8147				formula = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
8148			}
8149			if(formula) {
8150				if(!arr[R][C]) arr[R][C] = { t: 'n', f: formula };
8151				else arr[R][C].f = formula;
8152			}
8153			break;
8154			case 'F': /* Format */
8155			var F_seen = 0;
8156			for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
8157				case 'X': C = parseInt(record[rj].slice(1), 10)-1; ++F_seen; break;
8158				case 'Y':
8159					R = parseInt(record[rj].slice(1), 10)-1; /*C = 0;*/
8160					for(j = arr.length; j <= R; ++j) arr[j] = [];
8161					break;
8162				case 'M': Mval = parseInt(record[rj].slice(1), 10) / 20; break;
8163				case 'F': break; /* ??? */
8164				case 'G': break; /* hide grid */
8165				case 'P':
8166					next_cell_format = formats[parseInt(record[rj].slice(1), 10)];
8167					break;
8168				case 'S': break; /* cell style */
8169				case 'D': break; /* column */
8170				case 'N': break; /* font */
8171				case 'W':
8172					cw = record[rj].slice(1).split(" ");
8173					for(j = parseInt(cw[0], 10); j <= parseInt(cw[1], 10); ++j) {
8174						Mval = parseInt(cw[2], 10);
8175						colinfo[j-1] = Mval === 0 ? {hidden:true}: {wch:Mval};
8176					} break;
8177				case 'C': /* default column format */
8178					C = parseInt(record[rj].slice(1), 10)-1;
8179					if(!colinfo[C]) colinfo[C] = {};
8180					break;
8181				case 'R': /* row properties */
8182					R = parseInt(record[rj].slice(1), 10)-1;
8183					if(!rowinfo[R]) rowinfo[R] = {};
8184					if(Mval > 0) { rowinfo[R].hpt = Mval; rowinfo[R].hpx = pt2px(Mval); }
8185					else if(Mval === 0) rowinfo[R].hidden = true;
8186					break;
8187				// case 'K': // ??
8188				// case 'E': // ??
8189				default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
8190			}
8191			if(F_seen < 1) next_cell_format = null; break;
8192			default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
8193			}
8194		}
8195		if(rowinfo.length > 0) sht['!rows'] = rowinfo;
8196		if(colinfo.length > 0) sht['!cols'] = colinfo;
8197		colinfo.forEach(function(col) { process_col(col); });
8198		if(opts && opts.sheetRows) arr = arr.slice(0, opts.sheetRows);
8199		return [arr, sht, wb];
8200	}
8201
8202	function sylk_to_workbook(d/*:RawData*/, opts)/*:Workbook*/ {
8203		var aoasht = sylk_to_aoa(d, opts);
8204		var aoa = aoasht[0], ws = aoasht[1], wb = aoasht[2];
8205		var _opts = dup(opts); _opts.date1904 = (((wb||{}).Workbook || {}).WBProps || {}).date1904;
8206		var o = aoa_to_sheet(aoa, _opts);
8207		keys(ws).forEach(function(k) { o[k] = ws[k]; });
8208		var outwb = sheet_to_workbook(o, opts);
8209		keys(wb).forEach(function(k) { outwb[k] = wb[k]; });
8210		outwb.bookType = "sylk";
8211		return outwb;
8212	}
8213
8214	function write_ws_cell_sylk(cell/*:Cell*/, ws/*:Worksheet*/, R/*:number*/, C/*:number*//*::, opts*/)/*:string*/ {
8215		var o = "C;Y" + (R+1) + ";X" + (C+1) + ";K";
8216		switch(cell.t) {
8217			case 'n':
8218				o += (cell.v||0);
8219				if(cell.f && !cell.F) o += ";E" + a1_to_rc(cell.f, {r:R, c:C}); break;
8220			case 'b': o += cell.v ? "TRUE" : "FALSE"; break;
8221			case 'e': o += cell.w || cell.v; break;
8222			case 'd': o += '"' + (cell.w || cell.v) + '"'; break;
8223			case 's': o += '"' + (cell.v == null ? "" : String(cell.v)).replace(/"/g,"").replace(/;/g, ";;") + '"'; break;
8224		}
8225		return o;
8226	}
8227
8228	function write_ws_cols_sylk(out, cols) {
8229		cols.forEach(function(col, i) {
8230			var rec = "F;W" + (i+1) + " " + (i+1) + " ";
8231			if(col.hidden) rec += "0";
8232			else {
8233				if(typeof col.width == 'number' && !col.wpx) col.wpx = width2px(col.width);
8234				if(typeof col.wpx == 'number' && !col.wch) col.wch = px2char(col.wpx);
8235				if(typeof col.wch == 'number') rec += Math.round(col.wch);
8236			}
8237			if(rec.charAt(rec.length - 1) != " ") out.push(rec);
8238		});
8239	}
8240
8241	function write_ws_rows_sylk(out/*:Array<string>*/, rows/*:Array<RowInfo>*/) {
8242		rows.forEach(function(row, i) {
8243			var rec = "F;";
8244			if(row.hidden) rec += "M0;";
8245			else if(row.hpt) rec += "M" + 20 * row.hpt + ";";
8246			else if(row.hpx) rec += "M" + 20 * px2pt(row.hpx) + ";";
8247			if(rec.length > 2) out.push(rec + "R" + (i+1));
8248		});
8249	}
8250
8251	function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/, wb/*:?WorkBook*/)/*:string*/ {
8252		/* TODO: codepage */
8253		var preamble/*:Array<string>*/ = ["ID;PSheetJS;N;E"], o/*:Array<string>*/ = [];
8254		var r = safe_decode_range(ws['!ref']), cell/*:Cell*/;
8255		var dense = Array.isArray(ws);
8256		var RS = "\r\n";
8257		var d1904 = (((wb||{}).Workbook||{}).WBProps||{}).date1904;
8258
8259		preamble.push("P;PGeneral");
8260		preamble.push("F;P0;DG0G8;M255");
8261		if(ws['!cols']) write_ws_cols_sylk(preamble, ws['!cols']);
8262		if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']);
8263
8264		preamble.push("B;Y" + (r.e.r - r.s.r + 1) + ";X" + (r.e.c - r.s.c + 1) + ";D" + [r.s.c,r.s.r,r.e.c,r.e.r].join(" "));
8265		preamble.push("O;L;D;B" + (d1904 ? ";V4" : "") + ";K47;G100 0.001");
8266		for(var R = r.s.r; R <= r.e.r; ++R) {
8267			var p = [];
8268			for(var C = r.s.c; C <= r.e.c; ++C) {
8269				var coord = encode_cell({r:R,c:C});
8270				cell = dense ? (ws[R]||[])[C]: ws[coord];
8271				if(!cell || (cell.v == null && (!cell.f || cell.F))) continue;
8272				p.push(write_ws_cell_sylk(cell, ws, R, C, opts)); // TODO: pass date1904 info
8273			}
8274			o.push(p.join(RS));
8275		}
8276		return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
8277	}
8278
8279	return {
8280		to_workbook: sylk_to_workbook,
8281		from_sheet: sheet_to_sylk
8282	};
8283})();
8284
8285var DIF = /*#__PURE__*/(function() {
8286	function dif_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
8287		switch(opts.type) {
8288			case 'base64': return dif_to_aoa_str(Base64_decode(d), opts);
8289			case 'binary': return dif_to_aoa_str(d, opts);
8290			case 'buffer': return dif_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
8291			case 'array': return dif_to_aoa_str(cc2str(d), opts);
8292		}
8293		throw new Error("Unrecognized type " + opts.type);
8294	}
8295	function dif_to_aoa_str(str/*:string*/, opts)/*:AOA*/ {
8296		var records = str.split('\n'), R = -1, C = -1, ri = 0, arr/*:AOA*/ = [];
8297		for (; ri !== records.length; ++ri) {
8298			if (records[ri].trim() === 'BOT') { arr[++R] = []; C = 0; continue; }
8299			if (R < 0) continue;
8300			var metadata = records[ri].trim().split(",");
8301			var type = metadata[0], value = metadata[1];
8302			++ri;
8303			var data = records[ri] || "";
8304			while(((data.match(/["]/g)||[]).length & 1) && ri < records.length - 1) data += "\n" + records[++ri];
8305			data = data.trim();
8306			switch (+type) {
8307				case -1:
8308					if (data === 'BOT') { arr[++R] = []; C = 0; continue; }
8309					else if (data !== 'EOD') throw new Error("Unrecognized DIF special command " + data);
8310					break;
8311				case 0:
8312					if(data === 'TRUE') arr[R][C] = true;
8313					else if(data === 'FALSE') arr[R][C] = false;
8314					else if(!isNaN(fuzzynum(value))) arr[R][C] = fuzzynum(value);
8315					else if(!isNaN(fuzzydate(value).getDate())) arr[R][C] = parseDate(value);
8316					else arr[R][C] = value;
8317					++C; break;
8318				case 1:
8319					data = data.slice(1,data.length-1);
8320					data = data.replace(/""/g, '"');
8321					if(DIF_XL && data && data.match(/^=".*"$/)) data = data.slice(2, -1);
8322					arr[R][C++] = data !== '' ? data : null;
8323					break;
8324			}
8325			if (data === 'EOD') break;
8326		}
8327		if(opts && opts.sheetRows) arr = arr.slice(0, opts.sheetRows);
8328		return arr;
8329	}
8330
8331	function dif_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(dif_to_aoa(str, opts), opts); }
8332	function dif_to_workbook(str/*:string*/, opts)/*:Workbook*/ {
8333		var o = sheet_to_workbook(dif_to_sheet(str, opts), opts);
8334		o.bookType = "dif";
8335		return o;
8336	}
8337
8338	function make_value(v/*:number*/, s/*:string*/)/*:string*/ { return "0," + String(v) + "\r\n" + s; }
8339	function make_value_str(s/*:string*/)/*:string*/ { return "1,0\r\n\"" + s.replace(/"/g,'""') + '"'; }
8340	function sheet_to_dif(ws/*:Worksheet*//*::, opts:?any*/)/*:string*/ {
8341		var _DIF_XL = DIF_XL;
8342		var r = safe_decode_range(ws['!ref']);
8343		var dense = Array.isArray(ws);
8344		var o/*:Array<string>*/ = [
8345			"TABLE\r\n0,1\r\n\"sheetjs\"\r\n",
8346			"VECTORS\r\n0," + (r.e.r - r.s.r + 1) + "\r\n\"\"\r\n",
8347			"TUPLES\r\n0," + (r.e.c - r.s.c + 1) + "\r\n\"\"\r\n",
8348			"DATA\r\n0,0\r\n\"\"\r\n"
8349		];
8350		for(var R = r.s.r; R <= r.e.r; ++R) {
8351			var p = "-1,0\r\nBOT\r\n";
8352			for(var C = r.s.c; C <= r.e.c; ++C) {
8353				var cell/*:Cell*/ = dense ? (ws[R] && ws[R][C]) : ws[encode_cell({r:R,c:C})];
8354				if(cell == null) { p +=("1,0\r\n\"\"\r\n"); continue;}
8355				switch(cell.t) {
8356					case 'n':
8357						if(_DIF_XL) {
8358							if(cell.w != null) p +=("0," + cell.w + "\r\nV");
8359							else if(cell.v != null) p +=(make_value(cell.v, "V")); // TODO: should this call SSF_format?
8360							else if(cell.f != null && !cell.F) p +=(make_value_str("=" + cell.f));
8361							else p +=("1,0\r\n\"\"");
8362						} else {
8363							if(cell.v == null) p +=("1,0\r\n\"\"");
8364							else p +=(make_value(cell.v, "V"));
8365						}
8366						break;
8367					case 'b':
8368						p +=(cell.v ? make_value(1, "TRUE") : make_value(0, "FALSE"));
8369						break;
8370					case 's':
8371						p +=(make_value_str((!_DIF_XL || isNaN(+cell.v)) ? cell.v : '="' + cell.v + '"'));
8372						break;
8373					case 'd':
8374						if(!cell.w) cell.w = SSF_format(cell.z || table_fmt[14], datenum(parseDate(cell.v)));
8375						if(_DIF_XL) p +=(make_value(cell.w, "V"));
8376						else p +=(make_value_str(cell.w));
8377						break;
8378					default: p +=("1,0\r\n\"\"");
8379				}
8380				p += "\r\n";
8381			}
8382			o.push(p);
8383		}
8384		return o.join("") + "-1,0\r\nEOD";
8385	}
8386	return {
8387		to_workbook: dif_to_workbook,
8388		to_sheet: dif_to_sheet,
8389		from_sheet: sheet_to_dif
8390	};
8391})();
8392
8393var ETH = /*#__PURE__*/(function() {
8394	function decode(s/*:string*/)/*:string*/ { return s.replace(/\\b/g,"\\").replace(/\\c/g,":").replace(/\\n/g,"\n"); }
8395	function encode(s/*:string*/)/*:string*/ { return s.replace(/\\/g, "\\b").replace(/:/g, "\\c").replace(/\n/g,"\\n"); }
8396
8397	function eth_to_aoa(str/*:string*/, opts)/*:AOA*/ {
8398		var records = str.split('\n'), R = -1, C = -1, ri = 0, arr/*:AOA*/ = [];
8399		for (; ri !== records.length; ++ri) {
8400			var record = records[ri].trim().split(":");
8401			if(record[0] !== 'cell') continue;
8402			var addr = decode_cell(record[1]);
8403			if(arr.length <= addr.r) for(R = arr.length; R <= addr.r; ++R) if(!arr[R]) arr[R] = [];
8404			R = addr.r; C = addr.c;
8405			switch(record[2]) {
8406				case 't': arr[R][C] = decode(record[3]); break;
8407				case 'v': arr[R][C] = +record[3]; break;
8408				case 'vtf': var _f = record[record.length - 1];
8409					/* falls through */
8410				case 'vtc':
8411					switch(record[3]) {
8412						case 'nl': arr[R][C] = +record[4] ? true : false; break;
8413						default: arr[R][C] = +record[4]; break;
8414					}
8415					if(record[2] == 'vtf') arr[R][C] = [arr[R][C], _f];
8416			}
8417		}
8418		if(opts && opts.sheetRows) arr = arr.slice(0, opts.sheetRows);
8419		return arr;
8420	}
8421
8422	function eth_to_sheet(d/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(eth_to_aoa(d, opts), opts); }
8423	function eth_to_workbook(d/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(eth_to_sheet(d, opts), opts); }
8424
8425	var header = [
8426		"socialcalc:version:1.5",
8427		"MIME-Version: 1.0",
8428		"Content-Type: multipart/mixed; boundary=SocialCalcSpreadsheetControlSave"
8429	].join("\n");
8430
8431	var sep = [
8432		"--SocialCalcSpreadsheetControlSave",
8433		"Content-type: text/plain; charset=UTF-8"
8434	].join("\n") + "\n";
8435
8436	/* TODO: the other parts */
8437	var meta = [
8438		"# SocialCalc Spreadsheet Control Save",
8439		"part:sheet"
8440	].join("\n");
8441
8442	var end = "--SocialCalcSpreadsheetControlSave--";
8443
8444	function sheet_to_eth_data(ws/*:Worksheet*/)/*:string*/ {
8445		if(!ws || !ws['!ref']) return "";
8446		var o/*:Array<string>*/ = [], oo/*:Array<string>*/ = [], cell, coord = "";
8447		var r = decode_range(ws['!ref']);
8448		var dense = Array.isArray(ws);
8449		for(var R = r.s.r; R <= r.e.r; ++R) {
8450			for(var C = r.s.c; C <= r.e.c; ++C) {
8451				coord = encode_cell({r:R,c:C});
8452				cell = dense ? (ws[R]||[])[C] : ws[coord];
8453				if(!cell || cell.v == null || cell.t === 'z') continue;
8454				oo = ["cell", coord, 't'];
8455				switch(cell.t) {
8456					case 's': case 'str': oo.push(encode(cell.v)); break;
8457					case 'n':
8458						if(!cell.f) { oo[2]='v'; oo[3]=cell.v; }
8459						else { oo[2]='vtf'; oo[3]='n'; oo[4]=cell.v; oo[5]=encode(cell.f); }
8460						break;
8461					case 'b':
8462						oo[2] = 'vt'+(cell.f?'f':'c'); oo[3]='nl'; oo[4]=cell.v?"1":"0";
8463						oo[5] = encode(cell.f||(cell.v?'TRUE':'FALSE'));
8464						break;
8465					case 'd':
8466						var t = datenum(parseDate(cell.v));
8467						oo[2] = 'vtc'; oo[3] = 'nd'; oo[4] = ""+t;
8468						oo[5] = cell.w || SSF_format(cell.z || table_fmt[14], t);
8469						break;
8470					case 'e': continue;
8471				}
8472				o.push(oo.join(":"));
8473			}
8474		}
8475		o.push("sheet:c:" + (r.e.c-r.s.c+1) + ":r:" + (r.e.r-r.s.r+1) + ":tvf:1");
8476		o.push("valueformat:1:text-wiki");
8477		//o.push("copiedfrom:" + ws['!ref']); // clipboard only
8478		return o.join("\n");
8479	}
8480
8481	function sheet_to_eth(ws/*:Worksheet*//*::, opts:?any*/)/*:string*/ {
8482		return [header, sep, meta, sep, sheet_to_eth_data(ws), end].join("\n");
8483		// return ["version:1.5", sheet_to_eth_data(ws)].join("\n"); // clipboard form
8484	}
8485
8486	return {
8487		to_workbook: eth_to_workbook,
8488		to_sheet: eth_to_sheet,
8489		from_sheet: sheet_to_eth
8490	};
8491})();
8492
8493var PRN = /*#__PURE__*/(function() {
8494	function set_text_arr(data/*:string*/, arr/*:AOA*/, R/*:number*/, C/*:number*/, o/*:any*/) {
8495		if(o.raw) arr[R][C] = data;
8496		else if(data === ""){/* empty */}
8497		else if(data === 'TRUE') arr[R][C] = true;
8498		else if(data === 'FALSE') arr[R][C] = false;
8499		else if(!isNaN(fuzzynum(data))) arr[R][C] = fuzzynum(data);
8500		else if(!isNaN(fuzzydate(data).getDate())) arr[R][C] = parseDate(data);
8501		else arr[R][C] = data;
8502	}
8503
8504	function prn_to_aoa_str(f/*:string*/, opts)/*:AOA*/ {
8505		var o = opts || {};
8506		var arr/*:AOA*/ = ([]/*:any*/);
8507		if(!f || f.length === 0) return arr;
8508		var lines = f.split(/[\r\n]/);
8509		var L = lines.length - 1;
8510		while(L >= 0 && lines[L].length === 0) --L;
8511		var start = 10, idx = 0;
8512		var R = 0;
8513		for(; R <= L; ++R) {
8514			idx = lines[R].indexOf(" ");
8515			if(idx == -1) idx = lines[R].length; else idx++;
8516			start = Math.max(start, idx);
8517		}
8518		for(R = 0; R <= L; ++R) {
8519			arr[R] = [];
8520			/* TODO: confirm that widths are always 10 */
8521			var C = 0;
8522			set_text_arr(lines[R].slice(0, start).trim(), arr, R, C, o);
8523			for(C = 1; C <= (lines[R].length - start)/10 + 1; ++C)
8524				set_text_arr(lines[R].slice(start+(C-1)*10,start+C*10).trim(),arr,R,C,o);
8525		}
8526		if(o.sheetRows) arr = arr.slice(0, o.sheetRows);
8527		return arr;
8528	}
8529
8530	// List of accepted CSV separators
8531	var guess_seps = {
8532		/*::[*/0x2C/*::]*/: ',',
8533		/*::[*/0x09/*::]*/: "\t",
8534		/*::[*/0x3B/*::]*/: ';',
8535		/*::[*/0x7C/*::]*/: '|'
8536	};
8537
8538	// CSV separator weights to be used in case of equal numbers
8539	var guess_sep_weights = {
8540		/*::[*/0x2C/*::]*/: 3,
8541		/*::[*/0x09/*::]*/: 2,
8542		/*::[*/0x3B/*::]*/: 1,
8543		/*::[*/0x7C/*::]*/: 0
8544	};
8545
8546	function guess_sep(str) {
8547		var cnt = {}, instr = false, end = 0, cc = 0;
8548		for(;end < str.length;++end) {
8549			if((cc=str.charCodeAt(end)) == 0x22) instr = !instr;
8550			else if(!instr && cc in guess_seps) cnt[cc] = (cnt[cc]||0)+1;
8551		}
8552
8553		cc = [];
8554		for(end in cnt) if ( Object.prototype.hasOwnProperty.call(cnt, end) ) {
8555			cc.push([ cnt[end], end ]);
8556		}
8557
8558		if ( !cc.length ) {
8559			cnt = guess_sep_weights;
8560			for(end in cnt) if ( Object.prototype.hasOwnProperty.call(cnt, end) ) {
8561				cc.push([ cnt[end], end ]);
8562			}
8563		}
8564
8565		cc.sort(function(a, b) { return a[0] - b[0] || guess_sep_weights[a[1]] - guess_sep_weights[b[1]]; });
8566
8567		return guess_seps[cc.pop()[1]] || 0x2C;
8568	}
8569
8570	function dsv_to_sheet_str(str/*:string*/, opts)/*:Worksheet*/ {
8571		var o = opts || {};
8572		var sep = "";
8573		if(DENSE != null && o.dense == null) o.dense = DENSE;
8574		var ws/*:Worksheet*/ = o.dense ? ([]/*:any*/) : ({}/*:any*/);
8575		var range/*:Range*/ = ({s: {c:0, r:0}, e: {c:0, r:0}}/*:any*/);
8576
8577		if(str.slice(0,4) == "sep=") {
8578			// If the line ends in \r\n
8579			if(str.charCodeAt(5) == 13 && str.charCodeAt(6) == 10 ) {
8580				sep = str.charAt(4); str = str.slice(7);
8581			}
8582			// If line ends in \r OR \n
8583			else if(str.charCodeAt(5) == 13 || str.charCodeAt(5) == 10 ) {
8584				sep = str.charAt(4); str = str.slice(6);
8585			}
8586			else sep = guess_sep(str.slice(0,1024));
8587		}
8588		else if(o && o.FS) sep = o.FS;
8589		else sep = guess_sep(str.slice(0,1024));
8590		var R = 0, C = 0, v = 0;
8591		var start = 0, end = 0, sepcc = sep.charCodeAt(0), instr = false, cc=0, startcc=str.charCodeAt(0);
8592		var _re/*:?RegExp*/ = o.dateNF != null ? dateNF_regex(o.dateNF) : null;
8593		function finish_cell() {
8594			var s = str.slice(start, end); if(s.slice(-1) == "\r") s = s.slice(0, -1);
8595			var cell = ({}/*:any*/);
8596			if(s.charAt(0) == '"' && s.charAt(s.length - 1) == '"') s = s.slice(1,-1).replace(/""/g,'"');
8597			if(s.length === 0) cell.t = 'z';
8598			else if(o.raw) { cell.t = 's'; cell.v = s; }
8599			else if(s.trim().length === 0) { cell.t = 's'; cell.v = s; }
8600			else if(s.charCodeAt(0) == 0x3D) {
8601				if(s.charCodeAt(1) == 0x22 && s.charCodeAt(s.length - 1) == 0x22) { cell.t = 's'; cell.v = s.slice(2,-1).replace(/""/g,'"'); }
8602				else if(fuzzyfmla(s)) { cell.t = 'n'; cell.f = s.slice(1); }
8603				else { cell.t = 's'; cell.v = s; } }
8604			else if(s == "TRUE") { cell.t = 'b'; cell.v = true; }
8605			else if(s == "FALSE") { cell.t = 'b'; cell.v = false; }
8606			else if(!isNaN(v = fuzzynum(s))) { cell.t = 'n'; if(o.cellText !== false) cell.w = s; cell.v = v; }
8607			else if(!isNaN((v = fuzzydate(s)).getDate()) || _re && s.match(_re)) {
8608				cell.z = o.dateNF || table_fmt[14];
8609				var k = 0;
8610				if(_re && s.match(_re)){ s=dateNF_fix(s, o.dateNF, (s.match(_re)||[])); k=1; v = parseDate(s, k); }
8611				if(o.cellDates) { cell.t = 'd'; cell.v = v; }
8612				else { cell.t = 'n'; cell.v = datenum(v); }
8613				if(o.cellText !== false) cell.w = SSF_format(cell.z, cell.v instanceof Date ? datenum(cell.v):cell.v);
8614				if(!o.cellNF) delete cell.z;
8615			} else {
8616				cell.t = 's';
8617				cell.v = s;
8618			}
8619			if(cell.t == 'z'){}
8620			else if(o.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = cell; }
8621			else ws[encode_cell({c:C,r:R})] = cell;
8622			start = end+1; startcc = str.charCodeAt(start);
8623			if(range.e.c < C) range.e.c = C;
8624			if(range.e.r < R) range.e.r = R;
8625			if(cc == sepcc) ++C; else { C = 0; ++R; if(o.sheetRows && o.sheetRows <= R) return true; }
8626		}
8627		outer: for(;end < str.length;++end) switch((cc=str.charCodeAt(end))) {
8628			case 0x22: if(startcc === 0x22) instr = !instr; break;
8629			case 0x0d:
8630				if(instr) break;
8631				if(str.charCodeAt(end+1) == 0x0a) ++end;
8632				/* falls through */
8633			case sepcc: case 0x0a: if(!instr && finish_cell()) break outer; break;
8634			default: break;
8635		}
8636		if(end - start > 0) finish_cell();
8637
8638		ws['!ref'] = encode_range(range);
8639		return ws;
8640	}
8641
8642	function prn_to_sheet_str(str/*:string*/, opts)/*:Worksheet*/ {
8643		if(!(opts && opts.PRN)) return dsv_to_sheet_str(str, opts);
8644		if(opts.FS) return dsv_to_sheet_str(str, opts);
8645		if(str.slice(0,4) == "sep=") return dsv_to_sheet_str(str, opts);
8646		if(str.indexOf("\t") >= 0 || str.indexOf(",") >= 0 || str.indexOf(";") >= 0) return dsv_to_sheet_str(str, opts);
8647		return aoa_to_sheet(prn_to_aoa_str(str, opts), opts);
8648	}
8649
8650	function prn_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ {
8651		var str = "", bytes = opts.type == 'string' ? [0,0,0,0] : firstbyte(d, opts);
8652		switch(opts.type) {
8653			case 'base64': str = Base64_decode(d); break;
8654			case 'binary': str = d; break;
8655			case 'buffer':
8656				if(opts.codepage == 65001) str = d.toString('utf8'); // TODO: test if buf
8657				else if(opts.codepage && typeof $cptable !== 'undefined') str = $cptable.utils.decode(opts.codepage, d);
8658				else str = has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d);
8659				break;
8660			case 'array': str = cc2str(d); break;
8661			case 'string': str = d; break;
8662			default: throw new Error("Unrecognized type " + opts.type);
8663		}
8664		if(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) str = utf8read(str.slice(3));
8665		else if(opts.type != 'string' && opts.type != 'buffer' && opts.codepage == 65001) str = utf8read(str);
8666		else if((opts.type == 'binary') && typeof $cptable !== 'undefined' && opts.codepage)  str = $cptable.utils.decode(opts.codepage, $cptable.utils.encode(28591,str));
8667		if(str.slice(0,19) == "socialcalc:version:") return ETH.to_sheet(opts.type == 'string' ? str : utf8read(str), opts);
8668		return prn_to_sheet_str(str, opts);
8669	}
8670
8671	function prn_to_workbook(d/*:RawData*/, opts)/*:Workbook*/ { return sheet_to_workbook(prn_to_sheet(d, opts), opts); }
8672
8673	function sheet_to_prn(ws/*:Worksheet*//*::, opts:?any*/)/*:string*/ {
8674		var o/*:Array<string>*/ = [];
8675		var r = safe_decode_range(ws['!ref']), cell/*:Cell*/;
8676		var dense = Array.isArray(ws);
8677		for(var R = r.s.r; R <= r.e.r; ++R) {
8678			var oo/*:Array<string>*/ = [];
8679			for(var C = r.s.c; C <= r.e.c; ++C) {
8680				var coord = encode_cell({r:R,c:C});
8681				cell = dense ? (ws[R]||[])[C] : ws[coord];
8682				if(!cell || cell.v == null) { oo.push("          "); continue; }
8683				var w = (cell.w || (format_cell(cell), cell.w) || "").slice(0,10);
8684				while(w.length < 10) w += " ";
8685				oo.push(w + (C === 0 ? " " : ""));
8686			}
8687			o.push(oo.join(""));
8688		}
8689		return o.join("\n");
8690	}
8691
8692	return {
8693		to_workbook: prn_to_workbook,
8694		to_sheet: prn_to_sheet,
8695		from_sheet: sheet_to_prn
8696	};
8697})();
8698
8699/* Excel defaults to SYLK but warns if data is not valid */
8700function read_wb_ID(d, opts) {
8701	var o = opts || {}, OLD_WTF = !!o.WTF; o.WTF = true;
8702	try {
8703		var out = SYLK.to_workbook(d, o);
8704		o.WTF = OLD_WTF;
8705		return out;
8706	} catch(e) {
8707		o.WTF = OLD_WTF;
8708		if(!e.message.match(/SYLK bad record ID/) && OLD_WTF) throw e;
8709		return PRN.to_workbook(d, opts);
8710	}
8711}
8712
8713var WK_ = /*#__PURE__*/(function() {
8714	function lotushopper(data, cb/*:RecordHopperCB*/, opts/*:any*/) {
8715		if(!data) return;
8716		prep_blob(data, data.l || 0);
8717		var Enum = opts.Enum || WK1Enum;
8718		while(data.l < data.length) {
8719			var RT = data.read_shift(2);
8720			var R = Enum[RT] || Enum[0xFFFF];
8721			var length = data.read_shift(2);
8722			var tgt = data.l + length;
8723			var d = R.f && R.f(data, length, opts);
8724			data.l = tgt;
8725			if(cb(d, R, RT)) return;
8726		}
8727	}
8728
8729	function lotus_to_workbook(d/*:RawData*/, opts) {
8730		switch(opts.type) {
8731			case 'base64': return lotus_to_workbook_buf(s2a(Base64_decode(d)), opts);
8732			case 'binary': return lotus_to_workbook_buf(s2a(d), opts);
8733			case 'buffer':
8734			case 'array': return lotus_to_workbook_buf(d, opts);
8735		}
8736		throw "Unsupported type " + opts.type;
8737	}
8738
8739	function lotus_to_workbook_buf(d, opts)/*:Workbook*/ {
8740		if(!d) return d;
8741		var o = opts || {};
8742		if(DENSE != null && o.dense == null) o.dense = DENSE;
8743		var s/*:Worksheet*/ = ((o.dense ? [] : {})/*:any*/), n = "Sheet1", next_n = "", sidx = 0;
8744		var sheets = {}, snames = [], realnames = [];
8745
8746		var refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
8747		var sheetRows = o.sheetRows || 0;
8748
8749		if(d[4] == 0x51 && d[5] == 0x50 && d[6] == 0x57) return qpw_to_workbook_buf(d, opts);
8750		if(d[2] == 0x00) {
8751			if(d[3] == 0x08 || d[3] == 0x09) {
8752				if(d.length >= 16 && d[14] == 0x05 && d[15] === 0x6c) throw new Error("Unsupported Works 3 for Mac file");
8753			}
8754		}
8755
8756		if(d[2] == 0x02) {
8757			o.Enum = WK1Enum;
8758			lotushopper(d, function(val, R, RT) { switch(RT) {
8759				case 0x00: /* BOF */
8760					o.vers = val;
8761					if(val >= 0x1000) o.qpro = true;
8762					break;
8763				case 0xFF: /* BOF (works 3+) */
8764					o.vers = val;
8765					o.works = true;
8766					break;
8767				case 0x06: refguess = val; break; /* RANGE */
8768				case 0xCC: if(val) next_n = val; break; /* SHEETNAMECS */
8769				case 0xDE: next_n = val; break; /* SHEETNAMELP */
8770				case 0x0F: /* LABEL */
8771				case 0x33: /* STRING */
8772					if((!o.qpro && !o.works || RT == 0x33) && val[1].v.charCodeAt(0) < 0x30) val[1].v = val[1].v.slice(1);
8773					if(o.works || o.works2) val[1].v = val[1].v.replace(/\r\n/g, "\n");
8774					/* falls through */
8775				case 0x0D: /* INTEGER */
8776				case 0x0E: /* NUMBER */
8777				case 0x10: /* FORMULA */
8778					/* TODO: actual translation of the format code */
8779					if(RT == 0x0E && (val[2] & 0x70) == 0x70 && (val[2] & 0x0F) > 1 && (val[2] & 0x0F) < 15) {
8780						val[1].z = o.dateNF || table_fmt[14];
8781						if(o.cellDates) { val[1].t = 'd'; val[1].v = numdate(val[1].v); }
8782					}
8783
8784					if(o.qpro) {
8785						if(val[3] > sidx) {
8786							s["!ref"] = encode_range(refguess);
8787							sheets[n] = s;
8788							snames.push(n);
8789							s = (o.dense ? [] : {});
8790							refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
8791							sidx = val[3]; n = next_n || "Sheet" + (sidx + 1); next_n = "";
8792						}
8793					}
8794
8795					var tmpcell = o.dense ? (s[val[0].r]||[])[val[0].c] : s[encode_cell(val[0])];
8796					if(tmpcell) {
8797						tmpcell.t = val[1].t; tmpcell.v = val[1].v;
8798						if(val[1].z != null) tmpcell.z = val[1].z;
8799						if(val[1].f != null) tmpcell.f = val[1].f;
8800						break;
8801					}
8802					if(o.dense) {
8803						if(!s[val[0].r]) s[val[0].r] = [];
8804						s[val[0].r][val[0].c] = val[1];
8805					} else s[encode_cell(val[0])] = val[1];
8806					break;
8807				case 0x5405: o.works2 = true; break;
8808				default:
8809			}}, o);
8810		} else if(d[2] == 0x1A || d[2] == 0x0E) {
8811			o.Enum = WK3Enum;
8812			if(d[2] == 0x0E) { o.qpro = true; d.l = 0; }
8813			lotushopper(d, function(val, R, RT) { switch(RT) {
8814				case 0xCC: n = val; break; /* SHEETNAMECS */
8815				case 0x16: /* LABEL16 */
8816					if(val[1].v.charCodeAt(0) < 0x30) val[1].v = val[1].v.slice(1);
8817					// TODO: R9 appears to encode control codes this way -- verify against other versions
8818					val[1].v = val[1].v.replace(/\x0F./g, function($$) { return String.fromCharCode($$.charCodeAt(1) - 0x20); }).replace(/\r\n/g, "\n");
8819					/* falls through */
8820				case 0x17: /* NUMBER17 */
8821				case 0x18: /* NUMBER18 */
8822				case 0x19: /* FORMULA19 */
8823				case 0x25: /* NUMBER25 */
8824				case 0x27: /* NUMBER27 */
8825				case 0x28: /* FORMULA28 */
8826					if(val[3] > sidx) {
8827						s["!ref"] = encode_range(refguess);
8828						sheets[n] = s;
8829						snames.push(n);
8830						s = (o.dense ? [] : {});
8831						refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
8832						sidx = val[3]; n = "Sheet" + (sidx + 1);
8833					}
8834					if(sheetRows > 0 && val[0].r >= sheetRows) break;
8835					if(o.dense) {
8836						if(!s[val[0].r]) s[val[0].r] = [];
8837						s[val[0].r][val[0].c] = val[1];
8838					} else s[encode_cell(val[0])] = val[1];
8839					if(refguess.e.c < val[0].c) refguess.e.c = val[0].c;
8840					if(refguess.e.r < val[0].r) refguess.e.r = val[0].r;
8841					break;
8842				case 0x1B: /* XFORMAT */
8843					if(val[0x36b0]) realnames[val[0x36b0][0]] = val[0x36b0][1];
8844					break;
8845				case 0x0601: /* SHEETINFOQP */
8846					realnames[val[0]] = val[1]; if(val[0] == sidx) n = val[1]; break;
8847				default: break;
8848			}}, o);
8849		} else throw new Error("Unrecognized LOTUS BOF " + d[2]);
8850		s["!ref"] = encode_range(refguess);
8851		sheets[next_n || n] = s;
8852		snames.push(next_n || n);
8853		if(!realnames.length) return { SheetNames: snames, Sheets: sheets };
8854		var osheets = {}, rnames = [];
8855		/* TODO: verify no collisions */
8856		for(var i = 0; i < realnames.length; ++i) if(sheets[snames[i]]) {
8857			rnames.push(realnames[i] || snames[i]);
8858			osheets[realnames[i]] = sheets[realnames[i]] || sheets[snames[i]];
8859		} else {
8860			rnames.push(realnames[i]);
8861			osheets[realnames[i]] = ({ "!ref": "A1" });
8862		}
8863		return { SheetNames: rnames, Sheets: osheets };
8864	}
8865
8866	function sheet_to_wk1(ws/*:Worksheet*/, opts/*:WriteOpts*/) {
8867		var o = opts || {};
8868		if(+o.codepage >= 0) set_cp(+o.codepage);
8869		if(o.type == "string") throw new Error("Cannot write WK1 to JS string");
8870		var ba = buf_array();
8871		var range = safe_decode_range(ws["!ref"]);
8872		var dense = Array.isArray(ws);
8873		var cols = [];
8874
8875		write_biff_rec(ba, 0x00, write_BOF_WK1(0x0406));
8876		write_biff_rec(ba, 0x06, write_RANGE(range));
8877		var max_R = Math.min(range.e.r, 8191);
8878		for(var R = range.s.r; R <= max_R; ++R) {
8879			var rr = encode_row(R);
8880			for(var C = range.s.c; C <= range.e.c; ++C) {
8881				if(R === range.s.r) cols[C] = encode_col(C);
8882				var ref = cols[C] + rr;
8883				var cell = dense ? (ws[R]||[])[C] : ws[ref];
8884				if(!cell || cell.t == "z") continue;
8885				/* TODO: formula records */
8886				if(cell.t == "n") {
8887					if((cell.v|0)==cell.v && cell.v >= -32768 && cell.v <= 32767) write_biff_rec(ba, 0x0d, write_INTEGER(R, C, cell.v));
8888					else write_biff_rec(ba, 0x0e, write_NUMBER(R, C, cell.v));
8889				} else {
8890					var str = format_cell(cell);
8891					write_biff_rec(ba, 0x0F, write_LABEL(R, C, str.slice(0, 239)));
8892				}
8893			}
8894		}
8895
8896		write_biff_rec(ba, 0x01);
8897		return ba.end();
8898	}
8899
8900	function book_to_wk3(wb/*:Workbook*/, opts/*:WriteOpts*/) {
8901		var o = opts || {};
8902		if(+o.codepage >= 0) set_cp(+o.codepage);
8903		if(o.type == "string") throw new Error("Cannot write WK3 to JS string");
8904		var ba = buf_array();
8905
8906		write_biff_rec(ba, 0x00, write_BOF_WK3(wb));
8907
8908		for(var i = 0, cnt = 0; i < wb.SheetNames.length; ++i) if((wb.Sheets[wb.SheetNames[i]] || {})["!ref"]) write_biff_rec(ba, 0x1b, write_XFORMAT_SHEETNAME(wb.SheetNames[i], cnt++));
8909
8910		var wsidx = 0;
8911		for(i = 0; i < wb.SheetNames.length; ++i) {
8912			var ws = wb.Sheets[wb.SheetNames[i]];
8913			if(!ws || !ws["!ref"]) continue;
8914			var range = safe_decode_range(ws["!ref"]);
8915			var dense = Array.isArray(ws);
8916			var cols = [];
8917			var max_R = Math.min(range.e.r, 8191);
8918			for(var R = range.s.r; R <= max_R; ++R) {
8919				var rr = encode_row(R);
8920				for(var C = range.s.c; C <= range.e.c; ++C) {
8921					if(R === range.s.r) cols[C] = encode_col(C);
8922					var ref = cols[C] + rr;
8923					var cell = dense ? (ws[R]||[])[C] : ws[ref];
8924					if(!cell || cell.t == "z") continue;
8925					/* TODO: FORMULA19 NUMBER18 records */
8926					if(cell.t == "n") {
8927						write_biff_rec(ba, 0x17, write_NUMBER_17(R, C, wsidx, cell.v));
8928					} else {
8929						var str = format_cell(cell);
8930						/* TODO: max len? */
8931						write_biff_rec(ba, 0x16, write_LABEL_16(R, C, wsidx, str.slice(0, 239)));
8932					}
8933				}
8934			}
8935			++wsidx;
8936		}
8937
8938		write_biff_rec(ba, 0x01);
8939		return ba.end();
8940	}
8941
8942
8943	function write_BOF_WK1(v/*:number*/) {
8944		var out = new_buf(2);
8945		out.write_shift(2, v);
8946		return out;
8947	}
8948
8949	function write_BOF_WK3(wb/*:Workbook*/) {
8950		var out = new_buf(26);
8951		out.write_shift(2, 0x1000);
8952		out.write_shift(2, 0x0004);
8953		out.write_shift(4, 0x0000);
8954		var rows = 0, cols = 0, wscnt = 0;
8955		for(var i = 0; i < wb.SheetNames.length; ++i) {
8956			var name = wb.SheetNames[i];
8957			var ws = wb.Sheets[name];
8958			if(!ws || !ws["!ref"]) continue;
8959			++wscnt;
8960			var range = decode_range(ws["!ref"]);
8961			if(rows < range.e.r) rows = range.e.r;
8962			if(cols < range.e.c) cols = range.e.c;
8963		}
8964		if(rows > 8191) rows = 8191;
8965		out.write_shift(2, rows);
8966		out.write_shift(1, wscnt);
8967		out.write_shift(1, cols);
8968		out.write_shift(2, 0x00);
8969		out.write_shift(2, 0x00);
8970		out.write_shift(1, 0x01);
8971		out.write_shift(1, 0x02);
8972		out.write_shift(4, 0);
8973		out.write_shift(4, 0);
8974		return out;
8975	}
8976
8977	function parse_RANGE(blob, length, opts) {
8978		var o = {s:{c:0,r:0},e:{c:0,r:0}};
8979		if(length == 8 && opts.qpro) {
8980			o.s.c = blob.read_shift(1);
8981			blob.l++;
8982			o.s.r = blob.read_shift(2);
8983			o.e.c = blob.read_shift(1);
8984			blob.l++;
8985			o.e.r = blob.read_shift(2);
8986			return o;
8987		}
8988		o.s.c = blob.read_shift(2);
8989		o.s.r = blob.read_shift(2);
8990		if(length == 12 && opts.qpro) blob.l += 2;
8991		o.e.c = blob.read_shift(2);
8992		o.e.r = blob.read_shift(2);
8993		if(length == 12 && opts.qpro) blob.l += 2;
8994		if(o.s.c == 0xFFFF) o.s.c = o.e.c = o.s.r = o.e.r = 0;
8995		return o;
8996	}
8997	function write_RANGE(range) {
8998		var out = new_buf(8);
8999		out.write_shift(2, range.s.c);
9000		out.write_shift(2, range.s.r);
9001		out.write_shift(2, range.e.c);
9002		out.write_shift(2, range.e.r);
9003		return out;
9004	}
9005
9006	function parse_cell(blob, length, opts) {
9007		var o = [{c:0,r:0}, {t:'n',v:0}, 0, 0];
9008		if(opts.qpro && opts.vers != 0x5120) {
9009			o[0].c = blob.read_shift(1);
9010			o[3] = blob.read_shift(1);
9011			o[0].r = blob.read_shift(2);
9012			blob.l+=2;
9013		} else if(opts.works) { // TODO: verify with more complex works3-4 examples
9014			o[0].c = blob.read_shift(2); o[0].r = blob.read_shift(2);
9015			o[2] = blob.read_shift(2);
9016		} else {
9017			o[2] = blob.read_shift(1);
9018			o[0].c = blob.read_shift(2); o[0].r = blob.read_shift(2);
9019		}
9020		return o;
9021	}
9022
9023	function parse_LABEL(blob, length, opts) {
9024		var tgt = blob.l + length;
9025		var o = parse_cell(blob, length, opts);
9026		o[1].t = 's';
9027		if(opts.vers == 0x5120) {
9028			blob.l++;
9029			var len = blob.read_shift(1);
9030			o[1].v = blob.read_shift(len, 'utf8');
9031			return o;
9032		}
9033		if(opts.qpro) blob.l++;
9034		o[1].v = blob.read_shift(tgt - blob.l, 'cstr');
9035		return o;
9036	}
9037	function write_LABEL(R, C, s) {
9038		/* TODO: encoding */
9039		var o = new_buf(7 + s.length);
9040		o.write_shift(1, 0xFF);
9041		o.write_shift(2, C);
9042		o.write_shift(2, R);
9043		o.write_shift(1, 0x27); // ??
9044		for(var i = 0; i < o.length; ++i) {
9045			var cc = s.charCodeAt(i);
9046			o.write_shift(1, cc >= 0x80 ? 0x5F : cc);
9047		}
9048		o.write_shift(1, 0);
9049		return o;
9050	}
9051	function parse_STRING(blob, length, opts) {
9052		var tgt = blob.l + length;
9053		var o = parse_cell(blob, length, opts);
9054		o[1].t = 's';
9055		if(opts.vers == 0x5120) {
9056			var len = blob.read_shift(1);
9057			o[1].v = blob.read_shift(len, 'utf8');
9058			return o;
9059		}
9060		o[1].v = blob.read_shift(tgt - blob.l, 'cstr');
9061		return o;
9062	}
9063
9064	function parse_INTEGER(blob, length, opts) {
9065		var o = parse_cell(blob, length, opts);
9066		o[1].v = blob.read_shift(2, 'i');
9067		return o;
9068	}
9069	function write_INTEGER(R, C, v) {
9070		var o = new_buf(7);
9071		o.write_shift(1, 0xFF);
9072		o.write_shift(2, C);
9073		o.write_shift(2, R);
9074		o.write_shift(2, v, 'i');
9075		return o;
9076	}
9077
9078	function parse_NUMBER(blob, length, opts) {
9079		var o = parse_cell(blob, length, opts);
9080		o[1].v = blob.read_shift(8, 'f');
9081		return o;
9082	}
9083	function write_NUMBER(R, C, v) {
9084		var o = new_buf(13);
9085		o.write_shift(1, 0xFF);
9086		o.write_shift(2, C);
9087		o.write_shift(2, R);
9088		o.write_shift(8, v, 'f');
9089		return o;
9090	}
9091
9092	function parse_FORMULA(blob, length, opts) {
9093		var tgt = blob.l + length;
9094		var o = parse_cell(blob, length, opts);
9095		/* TODO: formula */
9096		o[1].v = blob.read_shift(8, 'f');
9097		if(opts.qpro) blob.l = tgt;
9098		else {
9099			var flen = blob.read_shift(2);
9100			wk1_fmla_to_csf(blob.slice(blob.l, blob.l + flen), o);
9101			blob.l += flen;
9102		}
9103		return o;
9104	}
9105
9106	function wk1_parse_rc(B, V, col) {
9107		var rel = V & 0x8000;
9108		V &= ~0x8000;
9109		V = (rel ? B : 0) + ((V >= 0x2000) ? V - 0x4000 : V);
9110		return (rel ? "" : "$") + (col ? encode_col(V) : encode_row(V));
9111	}
9112	/* var oprec = [
9113		8, 8, 8, 8, 8, 8, 8, 8, 6, 4, 4, 5, 5, 7, 3, 3,
9114		3, 3, 3, 3, 1, 1, 2, 6, 8, 8, 8, 8, 8, 8, 8, 8
9115	]; */
9116	/* TODO: flesh out */
9117	var FuncTab = {
9118		0x1F: ["NA", 0],
9119		// 0x20: ["ERR", 0],
9120		0x21: ["ABS", 1],
9121		0x22: ["TRUNC", 1],
9122		0x23: ["SQRT", 1],
9123		0x24: ["LOG", 1],
9124		0x25: ["LN", 1],
9125		0x26: ["PI", 0],
9126		0x27: ["SIN", 1],
9127		0x28: ["COS", 1],
9128		0x29: ["TAN", 1],
9129		0x2A: ["ATAN2", 2],
9130		0x2B: ["ATAN", 1],
9131		0x2C: ["ASIN", 1],
9132		0x2D: ["ACOS", 1],
9133		0x2E: ["EXP", 1],
9134		0x2F: ["MOD", 2],
9135		// 0x30
9136		0x31: ["ISNA", 1],
9137		0x32: ["ISERR", 1],
9138		0x33: ["FALSE", 0],
9139		0x34: ["TRUE", 0],
9140		0x35: ["RAND", 0],
9141		// 0x36 DATE
9142		// 0x37 NOW
9143		// 0x38 PMT
9144		// 0x39 PV
9145		// 0x3A FV
9146		// 0x3B IF
9147		// 0x3C DAY
9148		// 0x3D MONTH
9149		// 0x3E YEAR
9150		0x3F: ["ROUND", 2],
9151		// 0x40 TIME
9152		// 0x41 HOUR
9153		// 0x42 MINUTE
9154		// 0x43 SECOND
9155		0x44: ["ISNUMBER", 1],
9156		0x45: ["ISTEXT", 1],
9157		0x46: ["LEN", 1],
9158		0x47: ["VALUE", 1],
9159		// 0x48: ["FIXED", ?? 1],
9160		0x49: ["MID", 3],
9161		0x4A: ["CHAR", 1],
9162		// 0x4B
9163		// 0x4C FIND
9164		// 0x4D DATEVALUE
9165		// 0x4E TIMEVALUE
9166		// 0x4F CELL
9167		0x50: ["SUM", 69],
9168		0x51: ["AVERAGEA", 69],
9169		0x52: ["COUNTA", 69],
9170		0x53: ["MINA", 69],
9171		0x54: ["MAXA", 69],
9172		// 0x55 VLOOKUP
9173		// 0x56 NPV
9174		// 0x57 VAR
9175		// 0x58 STD
9176		// 0x59 IRR
9177		// 0x5A HLOOKUP
9178		// 0x5B DSUM
9179		// 0x5C DAVERAGE
9180		// 0x5D DCOUNTA
9181		// 0x5E DMIN
9182		// 0x5F DMAX
9183		// 0x60 DVARP
9184		// 0x61 DSTDEVP
9185		// 0x62 INDEX
9186		// 0x63 COLS
9187		// 0x64 ROWS
9188		// 0x65 REPEAT
9189		0x66: ["UPPER", 1],
9190		0x67: ["LOWER", 1],
9191		// 0x68 LEFT
9192		// 0x69 RIGHT
9193		// 0x6A REPLACE
9194		0x6B: ["PROPER", 1],
9195		// 0x6C CELL
9196		0x6D: ["TRIM", 1],
9197		// 0x6E CLEAN
9198		0x6F: ["T", 1]
9199		// 0x70 V
9200	};
9201	var BinOpTab = [
9202		  "",   "",   "",   "",   "",   "",   "",   "", // eslint-disable-line no-mixed-spaces-and-tabs
9203		  "",  "+",  "-",  "*",  "/",  "^",  "=", "<>", // eslint-disable-line no-mixed-spaces-and-tabs
9204		"<=", ">=",  "<",  ">",   "",   "",   "",   "", // eslint-disable-line no-mixed-spaces-and-tabs
9205		 "&",   "",   "",   "",   "",   "",   "",   ""  // eslint-disable-line no-mixed-spaces-and-tabs
9206	];
9207
9208	function wk1_fmla_to_csf(blob, o) {
9209		prep_blob(blob, 0);
9210		var out = [], argc = 0, R = "", C = "", argL = "", argR = "";
9211		while(blob.l < blob.length) {
9212			var cc = blob[blob.l++];
9213			switch(cc) {
9214				case 0x00: out.push(blob.read_shift(8, 'f')); break;
9215				case 0x01: {
9216					C = wk1_parse_rc(o[0].c, blob.read_shift(2), true);
9217					R = wk1_parse_rc(o[0].r, blob.read_shift(2), false);
9218					out.push(C + R);
9219				} break;
9220				case 0x02: {
9221					var c = wk1_parse_rc(o[0].c, blob.read_shift(2), true);
9222					var r = wk1_parse_rc(o[0].r, blob.read_shift(2), false);
9223					C = wk1_parse_rc(o[0].c, blob.read_shift(2), true);
9224					R = wk1_parse_rc(o[0].r, blob.read_shift(2), false);
9225					out.push(c + r + ":" + C + R);
9226				} break;
9227				case 0x03:
9228					if(blob.l < blob.length) { console.error("WK1 premature formula end"); return; }
9229					break;
9230				case 0x04: out.push("(" + out.pop() + ")"); break;
9231				case 0x05: out.push(blob.read_shift(2)); break;
9232				case 0x06: {
9233					/* TODO: text encoding */
9234					var Z = ""; while((cc = blob[blob.l++])) Z += String.fromCharCode(cc);
9235					out.push('"' + Z.replace(/"/g, '""') + '"');
9236				} break;
9237
9238				case 0x08: out.push("-" + out.pop()); break;
9239				case 0x17: out.push("+" + out.pop()); break;
9240				case 0x16: out.push("NOT(" + out.pop() + ")"); break;
9241
9242				case 0x14: case 0x15: {
9243					argR = out.pop(); argL = out.pop();
9244					out.push(["AND", "OR"][cc - 0x14] + "(" + argL + "," + argR + ")");
9245				} break;
9246
9247				default:
9248					if(cc < 0x20 && BinOpTab[cc]) {
9249						argR = out.pop(); argL = out.pop();
9250						out.push(argL + BinOpTab[cc] + argR);
9251					} else if(FuncTab[cc]) {
9252						argc = FuncTab[cc][1];
9253						if(argc == 69) argc = blob[blob.l++];
9254						if(argc > out.length) { console.error("WK1 bad formula parse 0x" + cc.toString(16) + ":|" + out.join("|") + "|"); return; }
9255						var args = out.slice(-argc);
9256						out.length -= argc;
9257						out.push(FuncTab[cc][0] + "(" + args.join(",") + ")");
9258					}
9259					else if(cc <= 0x07) return console.error("WK1 invalid opcode " + cc.toString(16));
9260					else if(cc <= 0x18) return console.error("WK1 unsupported op " + cc.toString(16));
9261					else if(cc <= 0x1E) return console.error("WK1 invalid opcode " + cc.toString(16));
9262					else if(cc <= 0x73) return console.error("WK1 unsupported function opcode " + cc.toString(16));
9263					// possible future functions ??
9264					else return console.error("WK1 unrecognized opcode " + cc.toString(16));
9265			}
9266		}
9267		if(out.length == 1) o[1].f = "" + out[0];
9268		else console.error("WK1 bad formula parse |" + out.join("|") + "|");
9269	}
9270
9271
9272	function parse_cell_3(blob/*::, length*/) {
9273		var o = [{c:0,r:0}, {t:'n',v:0}, 0];
9274		o[0].r = blob.read_shift(2); o[3] = blob[blob.l++]; o[0].c = blob[blob.l++];
9275		return o;
9276	}
9277
9278	function parse_LABEL_16(blob, length) {
9279		var o = parse_cell_3(blob, length);
9280		o[1].t = 's';
9281		o[1].v = blob.read_shift(length - 4, 'cstr');
9282		return o;
9283	}
9284	function write_LABEL_16(R, C, wsidx, s) {
9285		/* TODO: encoding */
9286		var o = new_buf(6 + s.length);
9287		o.write_shift(2, R);
9288		o.write_shift(1, wsidx);
9289		o.write_shift(1, C);
9290		o.write_shift(1, 0x27);
9291		for(var i = 0; i < s.length; ++i) {
9292			var cc = s.charCodeAt(i);
9293			o.write_shift(1, cc >= 0x80 ? 0x5F : cc);
9294		}
9295		o.write_shift(1, 0);
9296		return o;
9297	}
9298
9299	function parse_NUMBER_18(blob, length) {
9300		var o = parse_cell_3(blob, length);
9301		o[1].v = blob.read_shift(2);
9302		var v = o[1].v >> 1;
9303		if(o[1].v & 0x1) {
9304			switch(v & 0x07) {
9305				case 0: v = (v >> 3) * 5000; break;
9306				case 1: v = (v >> 3) * 500; break;
9307				case 2: v = (v >> 3) / 20; break;
9308				case 3: v = (v >> 3) / 200; break;
9309				case 4: v = (v >> 3) / 2000; break;
9310				case 5: v = (v >> 3) / 20000; break;
9311				case 6: v = (v >> 3) / 16; break;
9312				case 7: v = (v >> 3) / 64; break;
9313			}
9314		}
9315		o[1].v = v;
9316		return o;
9317	}
9318
9319	function parse_NUMBER_17(blob, length) {
9320		var o = parse_cell_3(blob, length);
9321		var v1 = blob.read_shift(4);
9322		var v2 = blob.read_shift(4);
9323		var e = blob.read_shift(2);
9324		if(e == 0xFFFF) {
9325			if(v1 === 0 && v2 === 0xC0000000) { o[1].t = "e"; o[1].v = 0x0F; } // ERR -> #VALUE!
9326			else if(v1 === 0 && v2 === 0xD0000000) { o[1].t = "e"; o[1].v = 0x2A; } // NA -> #N/A
9327			else o[1].v = 0;
9328			return o;
9329		}
9330		var s = e & 0x8000; e = (e&0x7FFF) - 16446;
9331		o[1].v = (1 - s*2) * (v2 * Math.pow(2, e+32) + v1 * Math.pow(2, e));
9332		return o;
9333	}
9334	function write_NUMBER_17(R, C, wsidx, v) {
9335		var o = new_buf(14);
9336		o.write_shift(2, R);
9337		o.write_shift(1, wsidx);
9338		o.write_shift(1, C);
9339		if(v == 0) {
9340			o.write_shift(4, 0);
9341			o.write_shift(4, 0);
9342			o.write_shift(2, 0xFFFF);
9343			return o;
9344		}
9345		var s = 0, e = 0, v1 = 0, v2 = 0;
9346		if(v < 0) { s = 1; v = -v; }
9347		e = Math.log2(v) | 0;
9348		v /= Math.pow(2, e-31);
9349		v2 = (v)>>>0;
9350		if((v2&0x80000000) == 0) { v/=2; ++e; v2 = v >>> 0; }
9351		v -= v2;
9352		v2 |= 0x80000000;
9353		v2 >>>= 0;
9354		v *= Math.pow(2, 32);
9355		v1 = v>>>0;
9356		o.write_shift(4, v1);
9357		o.write_shift(4, v2);
9358		e += 0x3FFF + (s ? 0x8000 : 0);
9359		o.write_shift(2, e);
9360		return o;
9361	}
9362
9363	function parse_FORMULA_19(blob, length) {
9364		var o = parse_NUMBER_17(blob, 14);
9365		blob.l += length - 14; /* TODO: WK3 formula */
9366		return o;
9367	}
9368
9369	function parse_NUMBER_25(blob, length) {
9370		var o = parse_cell_3(blob, length);
9371		var v1 = blob.read_shift(4);
9372		o[1].v = v1 >> 6;
9373		return o;
9374	}
9375
9376	function parse_NUMBER_27(blob, length) {
9377		var o = parse_cell_3(blob, length);
9378		var v1 = blob.read_shift(8,'f');
9379		o[1].v = v1;
9380		return o;
9381	}
9382
9383	function parse_FORMULA_28(blob, length) {
9384		var o = parse_NUMBER_27(blob, 12);
9385		blob.l += length - 12; /* TODO: formula */
9386		return o;
9387	}
9388
9389	function parse_SHEETNAMECS(blob, length) {
9390		return blob[blob.l + length - 1] == 0 ? blob.read_shift(length, 'cstr') : "";
9391	}
9392
9393	function parse_SHEETNAMELP(blob, length) {
9394		var len = blob[blob.l++];
9395		if(len > length - 1) len = length - 1;
9396		var o = ""; while(o.length < len) o += String.fromCharCode(blob[blob.l++]);
9397		return o;
9398	}
9399
9400	function parse_SHEETINFOQP(blob, length, opts) {
9401		if(!opts.qpro || length < 21) return;
9402		var id = blob.read_shift(1);
9403		blob.l += 17;
9404		blob.l += 1; //var len = blob.read_shift(1);
9405		blob.l += 2;
9406		var nm = blob.read_shift(length - 21, 'cstr');
9407		return [id, nm];
9408	}
9409
9410	function parse_XFORMAT(blob, length) {
9411		var o = {}, tgt = blob.l + length;
9412		while(blob.l < tgt) {
9413			var dt = blob.read_shift(2);
9414			if(dt == 0x36b0) {
9415				o[dt] = [0, ""];
9416				o[dt][0] = blob.read_shift(2);
9417				while(blob[blob.l]) { o[dt][1] += String.fromCharCode(blob[blob.l]); blob.l++; } blob.l++;
9418			}
9419			// TODO: 0x3a99 ??
9420		}
9421		return o;
9422	}
9423	function write_XFORMAT_SHEETNAME(name, wsidx) {
9424		var out = new_buf(5 + name.length);
9425		out.write_shift(2, 0x36b0);
9426		out.write_shift(2, wsidx);
9427		for(var i = 0; i < name.length; ++i) {
9428			var cc = name.charCodeAt(i);
9429			out[out.l++] = cc > 0x7F ? 0x5F : cc;
9430		}
9431		out[out.l++] = 0;
9432		return out;
9433	}
9434
9435	var WK1Enum = {
9436		/*::[*/0x0000/*::]*/: { n:"BOF", f:parseuint16 },
9437		/*::[*/0x0001/*::]*/: { n:"EOF" },
9438		/*::[*/0x0002/*::]*/: { n:"CALCMODE" },
9439		/*::[*/0x0003/*::]*/: { n:"CALCORDER" },
9440		/*::[*/0x0004/*::]*/: { n:"SPLIT" },
9441		/*::[*/0x0005/*::]*/: { n:"SYNC" },
9442		/*::[*/0x0006/*::]*/: { n:"RANGE", f:parse_RANGE },
9443		/*::[*/0x0007/*::]*/: { n:"WINDOW1" },
9444		/*::[*/0x0008/*::]*/: { n:"COLW1" },
9445		/*::[*/0x0009/*::]*/: { n:"WINTWO" },
9446		/*::[*/0x000A/*::]*/: { n:"COLW2" },
9447		/*::[*/0x000B/*::]*/: { n:"NAME" },
9448		/*::[*/0x000C/*::]*/: { n:"BLANK" },
9449		/*::[*/0x000D/*::]*/: { n:"INTEGER", f:parse_INTEGER },
9450		/*::[*/0x000E/*::]*/: { n:"NUMBER", f:parse_NUMBER },
9451		/*::[*/0x000F/*::]*/: { n:"LABEL", f:parse_LABEL },
9452		/*::[*/0x0010/*::]*/: { n:"FORMULA", f:parse_FORMULA },
9453		/*::[*/0x0018/*::]*/: { n:"TABLE" },
9454		/*::[*/0x0019/*::]*/: { n:"ORANGE" },
9455		/*::[*/0x001A/*::]*/: { n:"PRANGE" },
9456		/*::[*/0x001B/*::]*/: { n:"SRANGE" },
9457		/*::[*/0x001C/*::]*/: { n:"FRANGE" },
9458		/*::[*/0x001D/*::]*/: { n:"KRANGE1" },
9459		/*::[*/0x0020/*::]*/: { n:"HRANGE" },
9460		/*::[*/0x0023/*::]*/: { n:"KRANGE2" },
9461		/*::[*/0x0024/*::]*/: { n:"PROTEC" },
9462		/*::[*/0x0025/*::]*/: { n:"FOOTER" },
9463		/*::[*/0x0026/*::]*/: { n:"HEADER" },
9464		/*::[*/0x0027/*::]*/: { n:"SETUP" },
9465		/*::[*/0x0028/*::]*/: { n:"MARGINS" },
9466		/*::[*/0x0029/*::]*/: { n:"LABELFMT" },
9467		/*::[*/0x002A/*::]*/: { n:"TITLES" },
9468		/*::[*/0x002B/*::]*/: { n:"SHEETJS" },
9469		/*::[*/0x002D/*::]*/: { n:"GRAPH" },
9470		/*::[*/0x002E/*::]*/: { n:"NGRAPH" },
9471		/*::[*/0x002F/*::]*/: { n:"CALCCOUNT" },
9472		/*::[*/0x0030/*::]*/: { n:"UNFORMATTED" },
9473		/*::[*/0x0031/*::]*/: { n:"CURSORW12" },
9474		/*::[*/0x0032/*::]*/: { n:"WINDOW" },
9475		/*::[*/0x0033/*::]*/: { n:"STRING", f:parse_STRING },
9476		/*::[*/0x0037/*::]*/: { n:"PASSWORD" },
9477		/*::[*/0x0038/*::]*/: { n:"LOCKED" },
9478		/*::[*/0x003C/*::]*/: { n:"QUERY" },
9479		/*::[*/0x003D/*::]*/: { n:"QUERYNAME" },
9480		/*::[*/0x003E/*::]*/: { n:"PRINT" },
9481		/*::[*/0x003F/*::]*/: { n:"PRINTNAME" },
9482		/*::[*/0x0040/*::]*/: { n:"GRAPH2" },
9483		/*::[*/0x0041/*::]*/: { n:"GRAPHNAME" },
9484		/*::[*/0x0042/*::]*/: { n:"ZOOM" },
9485		/*::[*/0x0043/*::]*/: { n:"SYMSPLIT" },
9486		/*::[*/0x0044/*::]*/: { n:"NSROWS" },
9487		/*::[*/0x0045/*::]*/: { n:"NSCOLS" },
9488		/*::[*/0x0046/*::]*/: { n:"RULER" },
9489		/*::[*/0x0047/*::]*/: { n:"NNAME" },
9490		/*::[*/0x0048/*::]*/: { n:"ACOMM" },
9491		/*::[*/0x0049/*::]*/: { n:"AMACRO" },
9492		/*::[*/0x004A/*::]*/: { n:"PARSE" },
9493		/*::[*/0x0066/*::]*/: { n:"PRANGES??" },
9494		/*::[*/0x0067/*::]*/: { n:"RRANGES??" },
9495		/*::[*/0x0068/*::]*/: { n:"FNAME??" },
9496		/*::[*/0x0069/*::]*/: { n:"MRANGES??" },
9497		/*::[*/0x00CC/*::]*/: { n:"SHEETNAMECS", f:parse_SHEETNAMECS },
9498		/*::[*/0x00DE/*::]*/: { n:"SHEETNAMELP", f:parse_SHEETNAMELP },
9499		/*::[*/0x00FF/*::]*/: { n:"BOF", f:parseuint16 },
9500		/*::[*/0xFFFF/*::]*/: { n:"" }
9501	};
9502
9503	var WK3Enum = {
9504		/*::[*/0x0000/*::]*/: { n:"BOF" },
9505		/*::[*/0x0001/*::]*/: { n:"EOF" },
9506		/*::[*/0x0002/*::]*/: { n:"PASSWORD" },
9507		/*::[*/0x0003/*::]*/: { n:"CALCSET" },
9508		/*::[*/0x0004/*::]*/: { n:"WINDOWSET" },
9509		/*::[*/0x0005/*::]*/: { n:"SHEETCELLPTR" },
9510		/*::[*/0x0006/*::]*/: { n:"SHEETLAYOUT" },
9511		/*::[*/0x0007/*::]*/: { n:"COLUMNWIDTH" },
9512		/*::[*/0x0008/*::]*/: { n:"HIDDENCOLUMN" },
9513		/*::[*/0x0009/*::]*/: { n:"USERRANGE" },
9514		/*::[*/0x000A/*::]*/: { n:"SYSTEMRANGE" },
9515		/*::[*/0x000B/*::]*/: { n:"ZEROFORCE" },
9516		/*::[*/0x000C/*::]*/: { n:"SORTKEYDIR" },
9517		/*::[*/0x000D/*::]*/: { n:"FILESEAL" },
9518		/*::[*/0x000E/*::]*/: { n:"DATAFILLNUMS" },
9519		/*::[*/0x000F/*::]*/: { n:"PRINTMAIN" },
9520		/*::[*/0x0010/*::]*/: { n:"PRINTSTRING" },
9521		/*::[*/0x0011/*::]*/: { n:"GRAPHMAIN" },
9522		/*::[*/0x0012/*::]*/: { n:"GRAPHSTRING" },
9523		/*::[*/0x0013/*::]*/: { n:"??" },
9524		/*::[*/0x0014/*::]*/: { n:"ERRCELL" },
9525		/*::[*/0x0015/*::]*/: { n:"NACELL" },
9526		/*::[*/0x0016/*::]*/: { n:"LABEL16", f:parse_LABEL_16},
9527		/*::[*/0x0017/*::]*/: { n:"NUMBER17", f:parse_NUMBER_17 },
9528		/*::[*/0x0018/*::]*/: { n:"NUMBER18", f:parse_NUMBER_18 },
9529		/*::[*/0x0019/*::]*/: { n:"FORMULA19", f:parse_FORMULA_19},
9530		/*::[*/0x001A/*::]*/: { n:"FORMULA1A" },
9531		/*::[*/0x001B/*::]*/: { n:"XFORMAT", f:parse_XFORMAT },
9532		/*::[*/0x001C/*::]*/: { n:"DTLABELMISC" },
9533		/*::[*/0x001D/*::]*/: { n:"DTLABELCELL" },
9534		/*::[*/0x001E/*::]*/: { n:"GRAPHWINDOW" },
9535		/*::[*/0x001F/*::]*/: { n:"CPA" },
9536		/*::[*/0x0020/*::]*/: { n:"LPLAUTO" },
9537		/*::[*/0x0021/*::]*/: { n:"QUERY" },
9538		/*::[*/0x0022/*::]*/: { n:"HIDDENSHEET" },
9539		/*::[*/0x0023/*::]*/: { n:"??" },
9540		/*::[*/0x0025/*::]*/: { n:"NUMBER25", f:parse_NUMBER_25 },
9541		/*::[*/0x0026/*::]*/: { n:"??" },
9542		/*::[*/0x0027/*::]*/: { n:"NUMBER27", f:parse_NUMBER_27 },
9543		/*::[*/0x0028/*::]*/: { n:"FORMULA28", f:parse_FORMULA_28 },
9544		/*::[*/0x008E/*::]*/: { n:"??" },
9545		/*::[*/0x0093/*::]*/: { n:"??" },
9546		/*::[*/0x0096/*::]*/: { n:"??" },
9547		/*::[*/0x0097/*::]*/: { n:"??" },
9548		/*::[*/0x0098/*::]*/: { n:"??" },
9549		/*::[*/0x0099/*::]*/: { n:"??" },
9550		/*::[*/0x009A/*::]*/: { n:"??" },
9551		/*::[*/0x009B/*::]*/: { n:"??" },
9552		/*::[*/0x009C/*::]*/: { n:"??" },
9553		/*::[*/0x00A3/*::]*/: { n:"??" },
9554		/*::[*/0x00AE/*::]*/: { n:"??" },
9555		/*::[*/0x00AF/*::]*/: { n:"??" },
9556		/*::[*/0x00B0/*::]*/: { n:"??" },
9557		/*::[*/0x00B1/*::]*/: { n:"??" },
9558		/*::[*/0x00B8/*::]*/: { n:"??" },
9559		/*::[*/0x00B9/*::]*/: { n:"??" },
9560		/*::[*/0x00BA/*::]*/: { n:"??" },
9561		/*::[*/0x00BB/*::]*/: { n:"??" },
9562		/*::[*/0x00BC/*::]*/: { n:"??" },
9563		/*::[*/0x00C3/*::]*/: { n:"??" },
9564		/*::[*/0x00C9/*::]*/: { n:"??" },
9565		/*::[*/0x00CC/*::]*/: { n:"SHEETNAMECS", f:parse_SHEETNAMECS },
9566		/*::[*/0x00CD/*::]*/: { n:"??" },
9567		/*::[*/0x00CE/*::]*/: { n:"??" },
9568		/*::[*/0x00CF/*::]*/: { n:"??" },
9569		/*::[*/0x00D0/*::]*/: { n:"??" },
9570		/*::[*/0x0100/*::]*/: { n:"??" },
9571		/*::[*/0x0103/*::]*/: { n:"??" },
9572		/*::[*/0x0104/*::]*/: { n:"??" },
9573		/*::[*/0x0105/*::]*/: { n:"??" },
9574		/*::[*/0x0106/*::]*/: { n:"??" },
9575		/*::[*/0x0107/*::]*/: { n:"??" },
9576		/*::[*/0x0109/*::]*/: { n:"??" },
9577		/*::[*/0x010A/*::]*/: { n:"??" },
9578		/*::[*/0x010B/*::]*/: { n:"??" },
9579		/*::[*/0x010C/*::]*/: { n:"??" },
9580		/*::[*/0x010E/*::]*/: { n:"??" },
9581		/*::[*/0x010F/*::]*/: { n:"??" },
9582		/*::[*/0x0180/*::]*/: { n:"??" },
9583		/*::[*/0x0185/*::]*/: { n:"??" },
9584		/*::[*/0x0186/*::]*/: { n:"??" },
9585		/*::[*/0x0189/*::]*/: { n:"??" },
9586		/*::[*/0x018C/*::]*/: { n:"??" },
9587		/*::[*/0x0200/*::]*/: { n:"??" },
9588		/*::[*/0x0202/*::]*/: { n:"??" },
9589		/*::[*/0x0201/*::]*/: { n:"??" },
9590		/*::[*/0x0204/*::]*/: { n:"??" },
9591		/*::[*/0x0205/*::]*/: { n:"??" },
9592		/*::[*/0x0280/*::]*/: { n:"??" },
9593		/*::[*/0x0281/*::]*/: { n:"??" },
9594		/*::[*/0x0282/*::]*/: { n:"??" },
9595		/*::[*/0x0283/*::]*/: { n:"??" },
9596		/*::[*/0x0284/*::]*/: { n:"??" },
9597		/*::[*/0x0285/*::]*/: { n:"??" },
9598		/*::[*/0x0286/*::]*/: { n:"??" },
9599		/*::[*/0x0287/*::]*/: { n:"??" },
9600		/*::[*/0x0288/*::]*/: { n:"??" },
9601		/*::[*/0x0292/*::]*/: { n:"??" },
9602		/*::[*/0x0293/*::]*/: { n:"??" },
9603		/*::[*/0x0294/*::]*/: { n:"??" },
9604		/*::[*/0x0295/*::]*/: { n:"??" },
9605		/*::[*/0x0296/*::]*/: { n:"??" },
9606		/*::[*/0x0299/*::]*/: { n:"??" },
9607		/*::[*/0x029A/*::]*/: { n:"??" },
9608		/*::[*/0x0300/*::]*/: { n:"??" },
9609		/*::[*/0x0304/*::]*/: { n:"??" },
9610		/*::[*/0x0601/*::]*/: { n:"SHEETINFOQP", f:parse_SHEETINFOQP },
9611		/*::[*/0x0640/*::]*/: { n:"??" },
9612		/*::[*/0x0642/*::]*/: { n:"??" },
9613		/*::[*/0x0701/*::]*/: { n:"??" },
9614		/*::[*/0x0702/*::]*/: { n:"??" },
9615		/*::[*/0x0703/*::]*/: { n:"??" },
9616		/*::[*/0x0704/*::]*/: { n:"??" },
9617		/*::[*/0x0780/*::]*/: { n:"??" },
9618		/*::[*/0x0800/*::]*/: { n:"??" },
9619		/*::[*/0x0801/*::]*/: { n:"??" },
9620		/*::[*/0x0804/*::]*/: { n:"??" },
9621		/*::[*/0x0A80/*::]*/: { n:"??" },
9622		/*::[*/0x2AF6/*::]*/: { n:"??" },
9623		/*::[*/0x3231/*::]*/: { n:"??" },
9624		/*::[*/0x6E49/*::]*/: { n:"??" },
9625		/*::[*/0x6F44/*::]*/: { n:"??" },
9626		/*::[*/0xFFFF/*::]*/: { n:"" }
9627	};
9628
9629	/* QPW uses a different set of record types */
9630	function qpw_to_workbook_buf(d, opts)/*:Workbook*/ {
9631		prep_blob(d, 0);
9632		var o = opts || {};
9633		if(DENSE != null && o.dense == null) o.dense = DENSE;
9634		var s/*:Worksheet*/ = ((o.dense ? [] : {})/*:any*/);
9635		var SST = [], sname = "", formulae = [];
9636		var range = {s:{r:-1,c:-1}, e:{r:-1,c:-1}};
9637		var cnt = 0, type = 0, C = 0, R = 0;
9638		var wb = { SheetNames: [], Sheets: {} };
9639		outer: while(d.l < d.length) {
9640			var RT = d.read_shift(2), length = d.read_shift(2);
9641			var p = d.slice(d.l, d.l + length);
9642			prep_blob(p, 0);
9643			switch(RT) {
9644				case 0x01: /* BOF */
9645					if(p.read_shift(4) != 0x39575051) throw "Bad QPW9 BOF!";
9646					break;
9647				case 0x02: /* EOF */ break outer;
9648
9649				/* TODO: The behavior here should be consistent with Numbers: QP Notebook ~ .TN.SheetArchive, QP Sheet ~ .TST.TableModelArchive */
9650				case 0x0401: /* BON */ break;
9651				case 0x0402: /* EON */ /* TODO: backfill missing sheets based on BON cnt */ break;
9652
9653				case 0x0407: { /* SST */
9654					p.l += 12;
9655					while(p.l < p.length) {
9656						cnt = p.read_shift(2);
9657						type = p.read_shift(1);
9658						SST.push(p.read_shift(cnt, 'cstr'));
9659					}
9660				} break;
9661				case 0x0408: { /* FORMULAE */
9662					//p.l += 12;
9663					//while(p.l < p.length) {
9664					//	cnt = p.read_shift(2);
9665					//	formulae.push(p.slice(p.l, p.l + cnt + 1)); p.l += cnt + 1;
9666					//}
9667				} break;
9668
9669				case 0x0601: { /* BOS */
9670					var sidx = p.read_shift(2);
9671					s = ((o.dense ? [] : {})/*:any*/);
9672					range.s.c = p.read_shift(2);
9673					range.e.c = p.read_shift(2);
9674					range.s.r = p.read_shift(4);
9675					range.e.r = p.read_shift(4);
9676					p.l += 4;
9677					if(p.l + 2 < p.length) {
9678						cnt = p.read_shift(2);
9679						type = p.read_shift(1);
9680						sname = cnt == 0 ? "" : p.read_shift(cnt, 'cstr');
9681					}
9682					if(!sname) sname = encode_col(sidx);
9683					/* TODO: backfill empty sheets */
9684				} break;
9685				case 0x0602: { /* EOS */
9686					/* NOTE: QP valid range A1:IV1000000 */
9687					if(range.s.c > 0xFF || range.s.r > 999999) break;
9688					if(range.e.c < range.s.c) range.e.c = range.s.c;
9689					if(range.e.r < range.s.r) range.e.r = range.s.r;
9690					s["!ref"] = encode_range(range);
9691					book_append_sheet(wb, s, sname); // TODO: a barrel roll
9692				} break;
9693
9694				case 0x0A01: { /* COL (like XLS Row, modulo the layout transposition) */
9695					C = p.read_shift(2);
9696					if(range.e.c < C) range.e.c = C;
9697					if(range.s.c > C) range.s.c = C;
9698					R = p.read_shift(4);
9699					if(range.s.r > R) range.s.r = R;
9700					R = p.read_shift(4);
9701					if(range.e.r < R) range.e.r = R;
9702				} break;
9703
9704				case 0x0C01: { /* MulCells (like XLS MulRK, but takes advantage of common column data patterns) */
9705					R = p.read_shift(4), cnt = p.read_shift(4);
9706					if(range.s.r > R) range.s.r = R;
9707					if(range.e.r < R + cnt - 1) range.e.r = R + cnt - 1;
9708					while(p.l < p.length) {
9709						var cell = { t: "z" };
9710						var flags = p.read_shift(1);
9711						if(flags & 0x80) p.l += 2;
9712						var mul = (flags & 0x40) ? p.read_shift(2) - 1: 0;
9713						switch(flags & 0x1F) {
9714							case 1: break;
9715							case 2: cell = { t: "n", v: p.read_shift(2) }; break;
9716							case 3: cell = { t: "n", v: p.read_shift(2, 'i') }; break;
9717							case 5: cell = { t: "n", v: p.read_shift(8, 'f') }; break;
9718							case 7: cell = { t: "s", v: SST[type = p.read_shift(4) - 1] }; break;
9719							case 8: cell = { t: "n", v: p.read_shift(8, 'f') }; p.l += 2; /* cell.f = formulae[p.read_shift(4)]; */ p.l += 4; break;
9720							default: throw "Unrecognized QPW cell type " + (flags & 0x1F);
9721						}
9722						var delta = 0;
9723						if(flags & 0x20) switch(flags & 0x1F) {
9724							case 2: delta = p.read_shift(2); break;
9725							case 3: delta = p.read_shift(2, 'i'); break;
9726							case 7: delta = p.read_shift(2); break;
9727							default: throw "Unsupported delta for QPW cell type " + (flags & 0x1F);
9728						}
9729						if(!(!o.sheetStubs && cell.t == "z")) {
9730							if(Array.isArray(s)) {
9731								if(!s[R]) s[R] = [];
9732								s[R][C] = cell;
9733							} else s[encode_cell({r:R, c:C})] = cell;
9734						}
9735						++R; --cnt;
9736						while(mul-- > 0 && cnt >= 0) {
9737							if(flags & 0x20) switch(flags & 0x1F) {
9738								case 2: cell = { t: "n", v: (cell.v + delta) & 0xFFFF }; break;
9739								case 3: cell = { t: "n", v: (cell.v + delta) & 0xFFFF }; if(cell.v > 0x7FFF) cell.v -= 0x10000; break;
9740								case 7: cell = { t: "s", v: SST[type = (type + delta) >>> 0] }; break;
9741								default: throw "Cannot apply delta for QPW cell type " + (flags & 0x1F);
9742							} else switch(flags & 0x1F) {
9743								case 1: cell = { t: "z" }; break;
9744								case 2: cell = { t: "n", v: p.read_shift(2) }; break;
9745								case 7: cell = { t: "s", v: SST[type = p.read_shift(4) - 1] }; break;
9746								default: throw "Cannot apply repeat for QPW cell type " + (flags & 0x1F);
9747							}
9748							if(!(!o.sheetStubs && cell.t == "z")) {
9749								if(Array.isArray(s)) {
9750									if(!s[R]) s[R] = [];
9751									s[R][C] = cell;
9752								} else s[encode_cell({r:R, c:C})] = cell;
9753							}
9754							++R; --cnt;
9755						}
9756					}
9757				} break;
9758
9759				default: break;
9760			}
9761			d.l += length;
9762		}
9763		return wb;
9764	}
9765
9766	return {
9767		sheet_to_wk1: sheet_to_wk1,
9768		book_to_wk3: book_to_wk3,
9769		to_workbook: lotus_to_workbook
9770	};
9771})();
9772/* 18.4.7 rPr CT_RPrElt */
9773function parse_rpr(rpr) {
9774	var font = {}, m = rpr.match(tagregex), i = 0;
9775	var pass = false;
9776	if(m) for(;i!=m.length; ++i) {
9777		var y = parsexmltag(m[i]);
9778		switch(y[0].replace(/\w*:/g,"")) {
9779			/* 18.8.12 condense CT_BooleanProperty */
9780			/* ** not required . */
9781			case '<condense': break;
9782			/* 18.8.17 extend CT_BooleanProperty */
9783			/* ** not required . */
9784			case '<extend': break;
9785			/* 18.8.36 shadow CT_BooleanProperty */
9786			/* ** not required . */
9787			case '<shadow':
9788				if(!y.val) break;
9789				/* falls through */
9790			case '<shadow>':
9791			case '<shadow/>': font.shadow = 1; break;
9792			case '</shadow>': break;
9793
9794			/* 18.4.1 charset CT_IntProperty TODO */
9795			case '<charset':
9796				if(y.val == '1') break;
9797				font.cp = CS2CP[parseInt(y.val, 10)];
9798				break;
9799
9800			/* 18.4.2 outline CT_BooleanProperty TODO */
9801			case '<outline':
9802				if(!y.val) break;
9803				/* falls through */
9804			case '<outline>':
9805			case '<outline/>': font.outline = 1; break;
9806			case '</outline>': break;
9807
9808			/* 18.4.5 rFont CT_FontName */
9809			case '<rFont': font.name = y.val; break;
9810
9811			/* 18.4.11 sz CT_FontSize */
9812			case '<sz': font.sz = y.val; break;
9813
9814			/* 18.4.10 strike CT_BooleanProperty */
9815			case '<strike':
9816				if(!y.val) break;
9817				/* falls through */
9818			case '<strike>':
9819			case '<strike/>': font.strike = 1; break;
9820			case '</strike>': break;
9821
9822			/* 18.4.13 u CT_UnderlineProperty */
9823			case '<u':
9824				if(!y.val) break;
9825				switch(y.val) {
9826					case 'double': font.uval = "double"; break;
9827					case 'singleAccounting': font.uval = "single-accounting"; break;
9828					case 'doubleAccounting': font.uval = "double-accounting"; break;
9829				}
9830				/* falls through */
9831			case '<u>':
9832			case '<u/>': font.u = 1; break;
9833			case '</u>': break;
9834
9835			/* 18.8.2 b */
9836			case '<b':
9837				if(y.val == '0') break;
9838				/* falls through */
9839			case '<b>':
9840			case '<b/>': font.b = 1; break;
9841			case '</b>': break;
9842
9843			/* 18.8.26 i */
9844			case '<i':
9845				if(y.val == '0') break;
9846				/* falls through */
9847			case '<i>':
9848			case '<i/>': font.i = 1; break;
9849			case '</i>': break;
9850
9851			/* 18.3.1.15 color CT_Color TODO: tint, theme, auto, indexed */
9852			case '<color':
9853				if(y.rgb) font.color = y.rgb.slice(2,8);
9854				break;
9855			case '<color>': case '<color/>': case '</color>': break;
9856
9857			/* 18.8.18 family ST_FontFamily */
9858			case '<family': font.family = y.val; break;
9859			case '<family>': case '<family/>': case '</family>': break;
9860
9861			/* 18.4.14 vertAlign CT_VerticalAlignFontProperty TODO */
9862			case '<vertAlign': font.valign = y.val; break;
9863			case '<vertAlign>': case '<vertAlign/>': case '</vertAlign>': break;
9864
9865			/* 18.8.35 scheme CT_FontScheme TODO */
9866			case '<scheme': break;
9867			case '<scheme>': case '<scheme/>': case '</scheme>': break;
9868
9869			/* 18.2.10 extLst CT_ExtensionList ? */
9870			case '<extLst': case '<extLst>': case '</extLst>': break;
9871			case '<ext': pass = true; break;
9872			case '</ext>': pass = false; break;
9873			default:
9874				if(y[0].charCodeAt(1) !== 47 && !pass) throw new Error('Unrecognized rich format ' + y[0]);
9875		}
9876	}
9877	return font;
9878}
9879
9880var parse_rs = /*#__PURE__*/(function() {
9881	var tregex = matchtag("t"), rpregex = matchtag("rPr");
9882	/* 18.4.4 r CT_RElt */
9883	function parse_r(r) {
9884		/* 18.4.12 t ST_Xstring */
9885		var t = r.match(tregex)/*, cp = 65001*/;
9886		if(!t) return {t:"s", v:""};
9887
9888		var o/*:Cell*/ = ({t:'s', v:unescapexml(t[1])}/*:any*/);
9889		var rpr = r.match(rpregex);
9890		if(rpr) o.s = parse_rpr(rpr[1]);
9891		return o;
9892	}
9893	var rregex = /<(?:\w+:)?r>/g, rend = /<\/(?:\w+:)?r>/;
9894	return function parse_rs(rs) {
9895		return rs.replace(rregex,"").split(rend).map(parse_r).filter(function(r) { return r.v; });
9896	};
9897})();
9898
9899
9900/* Parse a list of <r> tags */
9901var rs_to_html = /*#__PURE__*/(function parse_rs_factory() {
9902	var nlregex = /(\r\n|\n)/g;
9903	function parse_rpr2(font, intro, outro) {
9904		var style/*:Array<string>*/ = [];
9905
9906		if(font.u) style.push("text-decoration: underline;");
9907		if(font.uval) style.push("text-underline-style:" + font.uval + ";");
9908		if(font.sz) style.push("font-size:" + font.sz + "pt;");
9909		if(font.outline) style.push("text-effect: outline;");
9910		if(font.shadow) style.push("text-shadow: auto;");
9911		intro.push('<span style="' + style.join("") + '">');
9912
9913		if(font.b) { intro.push("<b>"); outro.push("</b>"); }
9914		if(font.i) { intro.push("<i>"); outro.push("</i>"); }
9915		if(font.strike) { intro.push("<s>"); outro.push("</s>"); }
9916
9917		var align = font.valign || "";
9918		if(align == "superscript" || align == "super") align = "sup";
9919		else if(align == "subscript") align = "sub";
9920		if(align != "") { intro.push("<" + align + ">"); outro.push("</" + align + ">"); }
9921
9922		outro.push("</span>");
9923		return font;
9924	}
9925
9926	/* 18.4.4 r CT_RElt */
9927	function r_to_html(r) {
9928		var terms/*:[Array<string>, string, Array<string>]*/ = [[],r.v,[]];
9929		if(!r.v) return "";
9930
9931		if(r.s) parse_rpr2(r.s, terms[0], terms[2]);
9932
9933		return terms[0].join("") + terms[1].replace(nlregex,'<br/>') + terms[2].join("");
9934	}
9935
9936	return function parse_rs(rs) {
9937		return rs.map(r_to_html).join("");
9938	};
9939})();
9940
9941/* 18.4.8 si CT_Rst */
9942var sitregex = /<(?:\w+:)?t[^>]*>([^<]*)<\/(?:\w+:)?t>/g, sirregex = /<(?:\w+:)?r>/;
9943var sirphregex = /<(?:\w+:)?rPh.*?>([\s\S]*?)<\/(?:\w+:)?rPh>/g;
9944function parse_si(x, opts) {
9945	var html = opts ? opts.cellHTML : true;
9946	var z = {};
9947	if(!x) return { t: "" };
9948	//var y;
9949	/* 18.4.12 t ST_Xstring (Plaintext String) */
9950	// TODO: is whitespace actually valid here?
9951	if(x.match(/^\s*<(?:\w+:)?t[^>]*>/)) {
9952		z.t = unescapexml(utf8read(x.slice(x.indexOf(">")+1).split(/<\/(?:\w+:)?t>/)[0]||""), true);
9953		z.r = utf8read(x);
9954		if(html) z.h = escapehtml(z.t);
9955	}
9956	/* 18.4.4 r CT_RElt (Rich Text Run) */
9957	else if((/*y = */x.match(sirregex))) {
9958		z.r = utf8read(x);
9959		z.t = unescapexml(utf8read((x.replace(sirphregex, '').match(sitregex)||[]).join("").replace(tagregex,"")), true);
9960		if(html) z.h = rs_to_html(parse_rs(z.r));
9961	}
9962	/* 18.4.3 phoneticPr CT_PhoneticPr (TODO: needed for Asian support) */
9963	/* 18.4.6 rPh CT_PhoneticRun (TODO: needed for Asian support) */
9964	return z;
9965}
9966
9967/* 18.4 Shared String Table */
9968var sstr0 = /<(?:\w+:)?sst([^>]*)>([\s\S]*)<\/(?:\w+:)?sst>/;
9969var sstr1 = /<(?:\w+:)?(?:si|sstItem)>/g;
9970var sstr2 = /<\/(?:\w+:)?(?:si|sstItem)>/;
9971function parse_sst_xml(data/*:string*/, opts)/*:SST*/ {
9972	var s/*:SST*/ = ([]/*:any*/), ss = "";
9973	if(!data) return s;
9974	/* 18.4.9 sst CT_Sst */
9975	var sst = data.match(sstr0);
9976	if(sst) {
9977		ss = sst[2].replace(sstr1,"").split(sstr2);
9978		for(var i = 0; i != ss.length; ++i) {
9979			var o = parse_si(ss[i].trim(), opts);
9980			if(o != null) s[s.length] = o;
9981		}
9982		sst = parsexmltag(sst[1]); s.Count = sst.count; s.Unique = sst.uniqueCount;
9983	}
9984	return s;
9985}
9986
9987var straywsregex = /^\s|\s$|[\t\n\r]/;
9988function write_sst_xml(sst/*:SST*/, opts)/*:string*/ {
9989	if(!opts.bookSST) return "";
9990	var o = [XML_HEADER];
9991	o[o.length] = (writextag('sst', null, {
9992		xmlns: XMLNS_main[0],
9993		count: sst.Count,
9994		uniqueCount: sst.Unique
9995	}));
9996	for(var i = 0; i != sst.length; ++i) { if(sst[i] == null) continue;
9997		var s/*:XLString*/ = sst[i];
9998		var sitag = "<si>";
9999		if(s.r) sitag += s.r;
10000		else {
10001			sitag += "<t";
10002			if(!s.t) s.t = "";
10003			if(typeof s.t !== "string") s.t = String(s.t);
10004			if(s.t.match(straywsregex)) sitag += ' xml:space="preserve"';
10005			sitag += ">" + escapexml(s.t) + "</t>";
10006		}
10007		sitag += "</si>";
10008		o[o.length] = (sitag);
10009	}
10010	if(o.length>2){ o[o.length] = ('</sst>'); o[1]=o[1].replace("/>",">"); }
10011	return o.join("");
10012}
10013/* [MS-XLSB] 2.4.221 BrtBeginSst */
10014function parse_BrtBeginSst(data) {
10015	return [data.read_shift(4), data.read_shift(4)];
10016}
10017
10018/* [MS-XLSB] 2.1.7.45 Shared Strings */
10019function parse_sst_bin(data, opts)/*:SST*/ {
10020	var s/*:SST*/ = ([]/*:any*/);
10021	var pass = false;
10022	recordhopper(data, function hopper_sst(val, R, RT) {
10023		switch(RT) {
10024			case 0x009F: /* BrtBeginSst */
10025				s.Count = val[0]; s.Unique = val[1]; break;
10026			case 0x0013: /* BrtSSTItem */
10027				s.push(val); break;
10028			case 0x00A0: /* BrtEndSst */
10029				return true;
10030
10031			case 0x0023: /* BrtFRTBegin */
10032				pass = true; break;
10033			case 0x0024: /* BrtFRTEnd */
10034				pass = false; break;
10035
10036			default:
10037				if(R.T){}
10038				if(!pass || opts.WTF) throw new Error("Unexpected record 0x" + RT.toString(16));
10039		}
10040	});
10041	return s;
10042}
10043
10044function write_BrtBeginSst(sst, o) {
10045	if(!o) o = new_buf(8);
10046	o.write_shift(4, sst.Count);
10047	o.write_shift(4, sst.Unique);
10048	return o;
10049}
10050
10051var write_BrtSSTItem = write_RichStr;
10052
10053function write_sst_bin(sst/*::, opts*/) {
10054	var ba = buf_array();
10055	write_record(ba, 0x009F /* BrtBeginSst */, write_BrtBeginSst(sst));
10056	for(var i = 0; i < sst.length; ++i) write_record(ba, 0x0013 /* BrtSSTItem */, write_BrtSSTItem(sst[i]));
10057	/* FRTSST */
10058	write_record(ba, 0x00A0 /* BrtEndSst */);
10059	return ba.end();
10060}
10061function _JS2ANSI(str/*:string*/)/*:Array<number>*/ {
10062	if(typeof $cptable !== 'undefined') return $cptable.utils.encode(current_ansi, str);
10063	var o/*:Array<number>*/ = [], oo = str.split("");
10064	for(var i = 0; i < oo.length; ++i) o[i] = oo[i].charCodeAt(0);
10065	return o;
10066}
10067
10068/* [MS-OFFCRYPTO] 2.1.4 Version */
10069function parse_CRYPTOVersion(blob, length/*:?number*/) {
10070	var o/*:any*/ = {};
10071	o.Major = blob.read_shift(2);
10072	o.Minor = blob.read_shift(2);
10073	/*:: if(length == null) return o; */
10074	if(length >= 4) blob.l += length - 4;
10075	return o;
10076}
10077
10078/* [MS-OFFCRYPTO] 2.1.5 DataSpaceVersionInfo */
10079function parse_DataSpaceVersionInfo(blob) {
10080	var o = {};
10081	o.id = blob.read_shift(0, 'lpp4');
10082	o.R = parse_CRYPTOVersion(blob, 4);
10083	o.U = parse_CRYPTOVersion(blob, 4);
10084	o.W = parse_CRYPTOVersion(blob, 4);
10085	return o;
10086}
10087
10088/* [MS-OFFCRYPTO] 2.1.6.1 DataSpaceMapEntry Structure */
10089function parse_DataSpaceMapEntry(blob) {
10090	var len = blob.read_shift(4);
10091	var end = blob.l + len - 4;
10092	var o = {};
10093	var cnt = blob.read_shift(4);
10094	var comps/*:Array<{t:number, v:string}>*/ = [];
10095	/* [MS-OFFCRYPTO] 2.1.6.2 DataSpaceReferenceComponent Structure */
10096	while(cnt-- > 0) comps.push({ t: blob.read_shift(4), v: blob.read_shift(0, 'lpp4') });
10097	o.name = blob.read_shift(0, 'lpp4');
10098	o.comps = comps;
10099	if(blob.l != end) throw new Error("Bad DataSpaceMapEntry: " + blob.l + " != " + end);
10100	return o;
10101}
10102
10103/* [MS-OFFCRYPTO] 2.1.6 DataSpaceMap */
10104function parse_DataSpaceMap(blob) {
10105	var o = [];
10106	blob.l += 4; // must be 0x8
10107	var cnt = blob.read_shift(4);
10108	while(cnt-- > 0) o.push(parse_DataSpaceMapEntry(blob));
10109	return o;
10110}
10111
10112/* [MS-OFFCRYPTO] 2.1.7 DataSpaceDefinition */
10113function parse_DataSpaceDefinition(blob)/*:Array<string>*/ {
10114	var o/*:Array<string>*/ = [];
10115	blob.l += 4; // must be 0x8
10116	var cnt = blob.read_shift(4);
10117	while(cnt-- > 0) o.push(blob.read_shift(0, 'lpp4'));
10118	return o;
10119}
10120
10121/* [MS-OFFCRYPTO] 2.1.8 DataSpaceDefinition */
10122function parse_TransformInfoHeader(blob) {
10123	var o = {};
10124	/*var len = */blob.read_shift(4);
10125	blob.l += 4; // must be 0x1
10126	o.id = blob.read_shift(0, 'lpp4');
10127	o.name = blob.read_shift(0, 'lpp4');
10128	o.R = parse_CRYPTOVersion(blob, 4);
10129	o.U = parse_CRYPTOVersion(blob, 4);
10130	o.W = parse_CRYPTOVersion(blob, 4);
10131	return o;
10132}
10133
10134function parse_Primary(blob) {
10135	/* [MS-OFFCRYPTO] 2.2.6 IRMDSTransformInfo */
10136	var hdr = parse_TransformInfoHeader(blob);
10137	/* [MS-OFFCRYPTO] 2.1.9 EncryptionTransformInfo */
10138	hdr.ename = blob.read_shift(0, '8lpp4');
10139	hdr.blksz = blob.read_shift(4);
10140	hdr.cmode = blob.read_shift(4);
10141	if(blob.read_shift(4) != 0x04) throw new Error("Bad !Primary record");
10142	return hdr;
10143}
10144
10145/* [MS-OFFCRYPTO] 2.3.2 Encryption Header */
10146function parse_EncryptionHeader(blob, length/*:number*/) {
10147	var tgt = blob.l + length;
10148	var o = {};
10149	o.Flags = (blob.read_shift(4) & 0x3F);
10150	blob.l += 4;
10151	o.AlgID = blob.read_shift(4);
10152	var valid = false;
10153	switch(o.AlgID) {
10154		case 0x660E: case 0x660F: case 0x6610: valid = (o.Flags == 0x24); break;
10155		case 0x6801: valid = (o.Flags == 0x04); break;
10156		case 0: valid = (o.Flags == 0x10 || o.Flags == 0x04 || o.Flags == 0x24); break;
10157		default: throw 'Unrecognized encryption algorithm: ' + o.AlgID;
10158	}
10159	if(!valid) throw new Error("Encryption Flags/AlgID mismatch");
10160	o.AlgIDHash = blob.read_shift(4);
10161	o.KeySize = blob.read_shift(4);
10162	o.ProviderType = blob.read_shift(4);
10163	blob.l += 8;
10164	o.CSPName = blob.read_shift((tgt-blob.l)>>1, 'utf16le');
10165	blob.l = tgt;
10166	return o;
10167}
10168
10169/* [MS-OFFCRYPTO] 2.3.3 Encryption Verifier */
10170function parse_EncryptionVerifier(blob, length/*:number*/) {
10171	var o = {}, tgt = blob.l + length;
10172	blob.l += 4; // SaltSize must be 0x10
10173	o.Salt = blob.slice(blob.l, blob.l+16); blob.l += 16;
10174	o.Verifier = blob.slice(blob.l, blob.l+16); blob.l += 16;
10175	/*var sz = */blob.read_shift(4);
10176	o.VerifierHash = blob.slice(blob.l, tgt); blob.l = tgt;
10177	return o;
10178}
10179
10180/* [MS-OFFCRYPTO] 2.3.4.* EncryptionInfo Stream */
10181function parse_EncryptionInfo(blob) {
10182	var vers = parse_CRYPTOVersion(blob);
10183	switch(vers.Minor) {
10184		case 0x02: return [vers.Minor, parse_EncInfoStd(blob, vers)];
10185		case 0x03: return [vers.Minor, parse_EncInfoExt(blob, vers)];
10186		case 0x04: return [vers.Minor, parse_EncInfoAgl(blob, vers)];
10187	}
10188	throw new Error("ECMA-376 Encrypted file unrecognized Version: " + vers.Minor);
10189}
10190
10191/* [MS-OFFCRYPTO] 2.3.4.5  EncryptionInfo Stream (Standard Encryption) */
10192function parse_EncInfoStd(blob/*::, vers*/) {
10193	var flags = blob.read_shift(4);
10194	if((flags & 0x3F) != 0x24) throw new Error("EncryptionInfo mismatch");
10195	var sz = blob.read_shift(4);
10196	//var tgt = blob.l + sz;
10197	var hdr = parse_EncryptionHeader(blob, sz);
10198	var verifier = parse_EncryptionVerifier(blob, blob.length - blob.l);
10199	return { t:"Std", h:hdr, v:verifier };
10200}
10201/* [MS-OFFCRYPTO] 2.3.4.6  EncryptionInfo Stream (Extensible Encryption) */
10202function parse_EncInfoExt(/*::blob, vers*/) { throw new Error("File is password-protected: ECMA-376 Extensible"); }
10203/* [MS-OFFCRYPTO] 2.3.4.10 EncryptionInfo Stream (Agile Encryption) */
10204function parse_EncInfoAgl(blob/*::, vers*/) {
10205	var KeyData = ["saltSize","blockSize","keyBits","hashSize","cipherAlgorithm","cipherChaining","hashAlgorithm","saltValue"];
10206	blob.l+=4;
10207	var xml = blob.read_shift(blob.length - blob.l, 'utf8');
10208	var o = {};
10209	xml.replace(tagregex, function xml_agile(x) {
10210		var y/*:any*/ = parsexmltag(x);
10211		switch(strip_ns(y[0])) {
10212			case '<?xml': break;
10213			case '<encryption': case '</encryption>': break;
10214			case '<keyData': KeyData.forEach(function(k) { o[k] = y[k]; }); break;
10215			case '<dataIntegrity': o.encryptedHmacKey = y.encryptedHmacKey; o.encryptedHmacValue = y.encryptedHmacValue; break;
10216			case '<keyEncryptors>': case '<keyEncryptors': o.encs = []; break;
10217			case '</keyEncryptors>': break;
10218
10219			case '<keyEncryptor': o.uri = y.uri; break;
10220			case '</keyEncryptor>': break;
10221			case '<encryptedKey': o.encs.push(y); break;
10222			default: throw y[0];
10223		}
10224	});
10225	return o;
10226}
10227
10228/* [MS-OFFCRYPTO] 2.3.5.1 RC4 CryptoAPI Encryption Header */
10229function parse_RC4CryptoHeader(blob, length/*:number*/) {
10230	var o = {};
10231	var vers = o.EncryptionVersionInfo = parse_CRYPTOVersion(blob, 4); length -= 4;
10232	if(vers.Minor != 2) throw new Error('unrecognized minor version code: ' + vers.Minor);
10233	if(vers.Major > 4 || vers.Major < 2) throw new Error('unrecognized major version code: ' + vers.Major);
10234	o.Flags = blob.read_shift(4); length -= 4;
10235	var sz = blob.read_shift(4); length -= 4;
10236	o.EncryptionHeader = parse_EncryptionHeader(blob, sz); length -= sz;
10237	o.EncryptionVerifier = parse_EncryptionVerifier(blob, length);
10238	return o;
10239}
10240/* [MS-OFFCRYPTO] 2.3.6.1 RC4 Encryption Header */
10241function parse_RC4Header(blob/*::, length*/) {
10242	var o = {};
10243	var vers = o.EncryptionVersionInfo = parse_CRYPTOVersion(blob, 4);
10244	if(vers.Major != 1 || vers.Minor != 1) throw 'unrecognized version code ' + vers.Major + ' : ' + vers.Minor;
10245	o.Salt = blob.read_shift(16);
10246	o.EncryptedVerifier = blob.read_shift(16);
10247	o.EncryptedVerifierHash = blob.read_shift(16);
10248	return o;
10249}
10250
10251/* [MS-OFFCRYPTO] 2.3.7.1 Binary Document Password Verifier Derivation */
10252function crypto_CreatePasswordVerifier_Method1(Password/*:string*/) {
10253	var Verifier = 0x0000, PasswordArray;
10254	var PasswordDecoded = _JS2ANSI(Password);
10255	var len = PasswordDecoded.length + 1, i, PasswordByte;
10256	var Intermediate1, Intermediate2, Intermediate3;
10257	PasswordArray = new_raw_buf(len);
10258	PasswordArray[0] = PasswordDecoded.length;
10259	for(i = 1; i != len; ++i) PasswordArray[i] = PasswordDecoded[i-1];
10260	for(i = len-1; i >= 0; --i) {
10261		PasswordByte = PasswordArray[i];
10262		Intermediate1 = ((Verifier & 0x4000) === 0x0000) ? 0 : 1;
10263		Intermediate2 = (Verifier << 1) & 0x7FFF;
10264		Intermediate3 = Intermediate1 | Intermediate2;
10265		Verifier = Intermediate3 ^ PasswordByte;
10266	}
10267	return Verifier ^ 0xCE4B;
10268}
10269
10270/* [MS-OFFCRYPTO] 2.3.7.2 Binary Document XOR Array Initialization */
10271var crypto_CreateXorArray_Method1 = /*#__PURE__*/(function() {
10272	var PadArray = [0xBB, 0xFF, 0xFF, 0xBA, 0xFF, 0xFF, 0xB9, 0x80, 0x00, 0xBE, 0x0F, 0x00, 0xBF, 0x0F, 0x00];
10273	var InitialCode = [0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C, 0x0E10, 0xF1CE, 0x313E, 0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A, 0x4EC3];
10274	var XorMatrix = [0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09, 0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF, 0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0, 0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40, 0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5, 0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A, 0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9, 0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0, 0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC, 0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10, 0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168, 0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C, 0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD, 0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC, 0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4];
10275	var Ror = function(Byte) { return ((Byte/2) | (Byte*128)) & 0xFF; };
10276	var XorRor = function(byte1, byte2) { return Ror(byte1 ^ byte2); };
10277	var CreateXorKey_Method1 = function(Password) {
10278		var XorKey = InitialCode[Password.length - 1];
10279		var CurrentElement = 0x68;
10280		for(var i = Password.length-1; i >= 0; --i) {
10281			var Char = Password[i];
10282			for(var j = 0; j != 7; ++j) {
10283				if(Char & 0x40) XorKey ^= XorMatrix[CurrentElement];
10284				Char *= 2; --CurrentElement;
10285			}
10286		}
10287		return XorKey;
10288	};
10289	return function(password/*:string*/) {
10290		var Password = _JS2ANSI(password);
10291		var XorKey = CreateXorKey_Method1(Password);
10292		var Index = Password.length;
10293		var ObfuscationArray = new_raw_buf(16);
10294		for(var i = 0; i != 16; ++i) ObfuscationArray[i] = 0x00;
10295		var Temp, PasswordLastChar, PadIndex;
10296		if((Index & 1) === 1) {
10297			Temp = XorKey >> 8;
10298			ObfuscationArray[Index] = XorRor(PadArray[0], Temp);
10299			--Index;
10300			Temp = XorKey & 0xFF;
10301			PasswordLastChar = Password[Password.length - 1];
10302			ObfuscationArray[Index] = XorRor(PasswordLastChar, Temp);
10303		}
10304		while(Index > 0) {
10305			--Index;
10306			Temp = XorKey >> 8;
10307			ObfuscationArray[Index] = XorRor(Password[Index], Temp);
10308			--Index;
10309			Temp = XorKey & 0xFF;
10310			ObfuscationArray[Index] = XorRor(Password[Index], Temp);
10311		}
10312		Index = 15;
10313		PadIndex = 15 - Password.length;
10314		while(PadIndex > 0) {
10315			Temp = XorKey >> 8;
10316			ObfuscationArray[Index] = XorRor(PadArray[PadIndex], Temp);
10317			--Index;
10318			--PadIndex;
10319			Temp = XorKey & 0xFF;
10320			ObfuscationArray[Index] = XorRor(Password[Index], Temp);
10321			--Index;
10322			--PadIndex;
10323		}
10324		return ObfuscationArray;
10325	};
10326})();
10327
10328/* [MS-OFFCRYPTO] 2.3.7.3 Binary Document XOR Data Transformation Method 1 */
10329var crypto_DecryptData_Method1 = function(password/*:string*/, Data, XorArrayIndex, XorArray, O) {
10330	/* If XorArray is set, use it; if O is not set, make changes in-place */
10331	if(!O) O = Data;
10332	if(!XorArray) XorArray = crypto_CreateXorArray_Method1(password);
10333	var Index, Value;
10334	for(Index = 0; Index != Data.length; ++Index) {
10335		Value = Data[Index];
10336		Value ^= XorArray[XorArrayIndex];
10337		Value = ((Value>>5) | (Value<<3)) & 0xFF;
10338		O[Index] = Value;
10339		++XorArrayIndex;
10340	}
10341	return [O, XorArrayIndex, XorArray];
10342};
10343
10344var crypto_MakeXorDecryptor = function(password/*:string*/) {
10345	var XorArrayIndex = 0, XorArray = crypto_CreateXorArray_Method1(password);
10346	return function(Data) {
10347		var O = crypto_DecryptData_Method1("", Data, XorArrayIndex, XorArray);
10348		XorArrayIndex = O[1];
10349		return O[0];
10350	};
10351};
10352
10353/* 2.5.343 */
10354function parse_XORObfuscation(blob, length, opts, out) {
10355	var o = ({ key: parseuint16(blob), verificationBytes: parseuint16(blob) }/*:any*/);
10356	if(opts.password) o.verifier = crypto_CreatePasswordVerifier_Method1(opts.password);
10357	out.valid = o.verificationBytes === o.verifier;
10358	if(out.valid) out.insitu = crypto_MakeXorDecryptor(opts.password);
10359	return o;
10360}
10361
10362/* 2.4.117 */
10363function parse_FilePassHeader(blob, length/*:number*/, oo) {
10364	var o = oo || {}; o.Info = blob.read_shift(2); blob.l -= 2;
10365	if(o.Info === 1) o.Data = parse_RC4Header(blob, length);
10366	else o.Data = parse_RC4CryptoHeader(blob, length);
10367	return o;
10368}
10369function parse_FilePass(blob, length/*:number*/, opts) {
10370	var o = ({ Type: opts.biff >= 8 ? blob.read_shift(2) : 0 }/*:any*/); /* wEncryptionType */
10371	if(o.Type) parse_FilePassHeader(blob, length-2, o);
10372	else parse_XORObfuscation(blob, opts.biff >= 8 ? length : length - 2, opts, o);
10373	return o;
10374}
10375
10376
10377function rtf_to_sheet(d, opts) {
10378  switch (opts.type) {
10379    case "base64":
10380      return rtf_to_sheet_str(Base64_decode(d), opts);
10381    case "binary":
10382      return rtf_to_sheet_str(d, opts);
10383    case "buffer":
10384      return rtf_to_sheet_str(has_buf && Buffer.isBuffer(d) ? d.toString("binary") : a2s(d), opts);
10385    case "array":
10386      return rtf_to_sheet_str(cc2str(d), opts);
10387  }
10388  throw new Error("Unrecognized type " + opts.type);
10389}
10390function rtf_to_sheet_str(str, opts) {
10391  var o = opts || {};
10392  var ws = o.dense ? [] : {};
10393  var rows = str.match(/\\trowd[\s\S]*?\\row\b/g);
10394  if (!rows)
10395    throw new Error("RTF missing table");
10396  var range = { s: { c: 0, r: 0 }, e: { c: 0, r: rows.length - 1 } };
10397  rows.forEach(function(rowtf, R) {
10398    if (Array.isArray(ws))
10399      ws[R] = [];
10400    var rtfre = /\\[\w\-]+\b/g;
10401    var last_index = 0;
10402    var res;
10403    var C = -1;
10404    var payload = [];
10405    while ((res = rtfre.exec(rowtf)) != null) {
10406      var data = rowtf.slice(last_index, rtfre.lastIndex - res[0].length);
10407      if (data.charCodeAt(0) == 32)
10408        data = data.slice(1);
10409      if (data.length)
10410        payload.push(data);
10411      switch (res[0]) {
10412        case "\\cell":
10413          ++C;
10414          if (payload.length) {
10415            var cell = { v: payload.join(""), t: "s" };
10416            if (cell.v == "TRUE" || cell.v == "FALSE") {
10417              cell.v = cell.v == "TRUE";
10418              cell.t = "b";
10419            } else if (!isNaN(fuzzynum(cell.v))) {
10420              cell.t = "n";
10421              if (o.cellText !== false)
10422                cell.w = cell.v;
10423              cell.v = fuzzynum(cell.v);
10424            }
10425            if (Array.isArray(ws))
10426              ws[R][C] = cell;
10427            else
10428              ws[encode_cell({ r: R, c: C })] = cell;
10429          }
10430          payload = [];
10431          break;
10432        case "\\par":
10433          payload.push("\n");
10434          break;
10435      }
10436      last_index = rtfre.lastIndex;
10437    }
10438    if (C > range.e.c)
10439      range.e.c = C;
10440  });
10441  ws["!ref"] = encode_range(range);
10442  return ws;
10443}
10444function rtf_to_workbook(d, opts) {
10445  var wb = sheet_to_workbook(rtf_to_sheet(d, opts), opts);
10446  wb.bookType = "rtf";
10447  return wb;
10448}
10449function sheet_to_rtf(ws, opts) {
10450  var o = ["{\\rtf1\\ansi"];
10451  if (!ws["!ref"])
10452    return o[0] + "}";
10453  var r = safe_decode_range(ws["!ref"]), cell;
10454  var dense = Array.isArray(ws);
10455  for (var R = r.s.r; R <= r.e.r; ++R) {
10456    o.push("\\trowd\\trautofit1");
10457    for (var C = r.s.c; C <= r.e.c; ++C)
10458      o.push("\\cellx" + (C + 1));
10459    o.push("\\pard\\intbl");
10460    for (C = r.s.c; C <= r.e.c; ++C) {
10461      var coord = encode_cell({ r: R, c: C });
10462      cell = dense ? (ws[R] || [])[C] : ws[coord];
10463      if (!cell || cell.v == null && (!cell.f || cell.F)) {
10464        o.push(" \\cell");
10465        continue;
10466      }
10467      o.push(" " + (cell.w || (format_cell(cell), cell.w) || "").replace(/[\r\n]/g, "\\par "));
10468      o.push("\\cell");
10469    }
10470    o.push("\\pard\\intbl\\row");
10471  }
10472  return o.join("") + "}";
10473}
10474function hex2RGB(h) {
10475	var o = h.slice(h[0]==="#"?1:0).slice(0,6);
10476	return [parseInt(o.slice(0,2),16),parseInt(o.slice(2,4),16),parseInt(o.slice(4,6),16)];
10477}
10478function rgb2Hex(rgb) {
10479	for(var i=0,o=1; i!=3; ++i) o = o*256 + (rgb[i]>255?255:rgb[i]<0?0:rgb[i]);
10480	return o.toString(16).toUpperCase().slice(1);
10481}
10482
10483function rgb2HSL(rgb) {
10484	var R = rgb[0]/255, G = rgb[1]/255, B=rgb[2]/255;
10485	var M = Math.max(R, G, B), m = Math.min(R, G, B), C = M - m;
10486	if(C === 0) return [0, 0, R];
10487
10488	var H6 = 0, S = 0, L2 = (M + m);
10489	S = C / (L2 > 1 ? 2 - L2 : L2);
10490	switch(M){
10491		case R: H6 = ((G - B) / C + 6)%6; break;
10492		case G: H6 = ((B - R) / C + 2); break;
10493		case B: H6 = ((R - G) / C + 4); break;
10494	}
10495	return [H6 / 6, S, L2 / 2];
10496}
10497
10498function hsl2RGB(hsl){
10499	var H = hsl[0], S = hsl[1], L = hsl[2];
10500	var C = S * 2 * (L < 0.5 ? L : 1 - L), m = L - C/2;
10501	var rgb = [m,m,m], h6 = 6*H;
10502
10503	var X;
10504	if(S !== 0) switch(h6|0) {
10505		case 0: case 6: X = C * h6; rgb[0] += C; rgb[1] += X; break;
10506		case 1: X = C * (2 - h6);   rgb[0] += X; rgb[1] += C; break;
10507		case 2: X = C * (h6 - 2);   rgb[1] += C; rgb[2] += X; break;
10508		case 3: X = C * (4 - h6);   rgb[1] += X; rgb[2] += C; break;
10509		case 4: X = C * (h6 - 4);   rgb[2] += C; rgb[0] += X; break;
10510		case 5: X = C * (6 - h6);   rgb[2] += X; rgb[0] += C; break;
10511	}
10512	for(var i = 0; i != 3; ++i) rgb[i] = Math.round(rgb[i]*255);
10513	return rgb;
10514}
10515
10516/* 18.8.3 bgColor tint algorithm */
10517function rgb_tint(hex, tint) {
10518	if(tint === 0) return hex;
10519	var hsl = rgb2HSL(hex2RGB(hex));
10520	if (tint < 0) hsl[2] = hsl[2] * (1 + tint);
10521	else hsl[2] = 1 - (1 - hsl[2]) * (1 - tint);
10522	return rgb2Hex(hsl2RGB(hsl));
10523}
10524
10525/* 18.3.1.13 width calculations */
10526/* [MS-OI29500] 2.1.595 Column Width & Formatting */
10527var DEF_MDW = 6, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
10528function width2px(width) { return Math.floor(( width + (Math.round(128/MDW))/256 )* MDW ); }
10529function px2char(px) { return (Math.floor((px - 5)/MDW * 100 + 0.5))/100; }
10530function char2width(chr) { return (Math.round((chr * MDW + 5)/MDW*256))/256; }
10531//function px2char_(px) { return (((px - 5)/MDW * 100 + 0.5))/100; }
10532//function char2width_(chr) { return (((chr * MDW + 5)/MDW*256))/256; }
10533function cycle_width(collw) { return char2width(px2char(width2px(collw))); }
10534/* XLSX/XLSB/XLS specify width in units of MDW */
10535function find_mdw_colw(collw) {
10536	var delta = Math.abs(collw - cycle_width(collw)), _MDW = MDW;
10537	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; }
10538	MDW = _MDW;
10539}
10540/* XLML specifies width in terms of pixels */
10541/*function find_mdw_wpx(wpx) {
10542	var delta = Infinity, guess = 0, _MDW = MIN_MDW;
10543	for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) {
10544		guess = char2width_(px2char_(wpx))*256;
10545		guess = (guess) % 1;
10546		if(guess > 0.5) guess--;
10547		if(Math.abs(guess) < delta) { delta = Math.abs(guess); _MDW = MDW; }
10548	}
10549	MDW = _MDW;
10550}*/
10551
10552function process_col(coll/*:ColInfo*/) {
10553	if(coll.width) {
10554		coll.wpx = width2px(coll.width);
10555		coll.wch = px2char(coll.wpx);
10556		coll.MDW = MDW;
10557	} else if(coll.wpx) {
10558		coll.wch = px2char(coll.wpx);
10559		coll.width = char2width(coll.wch);
10560		coll.MDW = MDW;
10561	} else if(typeof coll.wch == 'number') {
10562		coll.width = char2width(coll.wch);
10563		coll.wpx = width2px(coll.width);
10564		coll.MDW = MDW;
10565	}
10566	if(coll.customWidth) delete coll.customWidth;
10567}
10568
10569var DEF_PPI = 96, PPI = DEF_PPI;
10570function px2pt(px) { return px * 96 / PPI; }
10571function pt2px(pt) { return pt * PPI / 96; }
10572
10573/* [MS-EXSPXML3] 2.4.54 ST_enmPattern */
10574var XLMLPatternTypeMap = {
10575	"None": "none",
10576	"Solid": "solid",
10577	"Gray50": "mediumGray",
10578	"Gray75": "darkGray",
10579	"Gray25": "lightGray",
10580	"HorzStripe": "darkHorizontal",
10581	"VertStripe": "darkVertical",
10582	"ReverseDiagStripe": "darkDown",
10583	"DiagStripe": "darkUp",
10584	"DiagCross": "darkGrid",
10585	"ThickDiagCross": "darkTrellis",
10586	"ThinHorzStripe": "lightHorizontal",
10587	"ThinVertStripe": "lightVertical",
10588	"ThinReverseDiagStripe": "lightDown",
10589	"ThinHorzCross": "lightGrid"
10590};
10591
10592/* 18.8.5 borders CT_Borders */
10593function parse_borders(t, styles, themes, opts) {
10594	styles.Borders = [];
10595	var border = {};
10596	var pass = false;
10597	(t[0].match(tagregex)||[]).forEach(function(x) {
10598		var y = parsexmltag(x);
10599		switch(strip_ns(y[0])) {
10600			case '<borders': case '<borders>': case '</borders>': break;
10601
10602			/* 18.8.4 border CT_Border */
10603			case '<border': case '<border>': case '<border/>':
10604				border = /*::(*/{}/*:: :any)*/;
10605				if(y.diagonalUp) border.diagonalUp = parsexmlbool(y.diagonalUp);
10606				if(y.diagonalDown) border.diagonalDown = parsexmlbool(y.diagonalDown);
10607				styles.Borders.push(border);
10608				break;
10609			case '</border>': break;
10610
10611			/* note: not in spec, appears to be CT_BorderPr */
10612			case '<left/>': break;
10613			case '<left': case '<left>': break;
10614			case '</left>': break;
10615
10616			/* note: not in spec, appears to be CT_BorderPr */
10617			case '<right/>': break;
10618			case '<right': case '<right>': break;
10619			case '</right>': break;
10620
10621			/* 18.8.43 top CT_BorderPr */
10622			case '<top/>': break;
10623			case '<top': case '<top>': break;
10624			case '</top>': break;
10625
10626			/* 18.8.6 bottom CT_BorderPr */
10627			case '<bottom/>': break;
10628			case '<bottom': case '<bottom>': break;
10629			case '</bottom>': break;
10630
10631			/* 18.8.13 diagonal CT_BorderPr */
10632			case '<diagonal': case '<diagonal>': case '<diagonal/>': break;
10633			case '</diagonal>': break;
10634
10635			/* 18.8.25 horizontal CT_BorderPr */
10636			case '<horizontal': case '<horizontal>': case '<horizontal/>': break;
10637			case '</horizontal>': break;
10638
10639			/* 18.8.44 vertical CT_BorderPr */
10640			case '<vertical': case '<vertical>': case '<vertical/>': break;
10641			case '</vertical>': break;
10642
10643			/* 18.8.37 start CT_BorderPr */
10644			case '<start': case '<start>': case '<start/>': break;
10645			case '</start>': break;
10646
10647			/* 18.8.16 end CT_BorderPr */
10648			case '<end': case '<end>': case '<end/>': break;
10649			case '</end>': break;
10650
10651			/* 18.8.? color CT_Color */
10652			case '<color': case '<color>':
10653				break;
10654			case '<color/>': case '</color>': break;
10655
10656			/* 18.2.10 extLst CT_ExtensionList ? */
10657			case '<extLst': case '<extLst>': case '</extLst>': break;
10658			case '<ext': pass = true; break;
10659			case '</ext>': pass = false; break;
10660			default: if(opts && opts.WTF) {
10661				if(!pass) throw new Error('unrecognized ' + y[0] + ' in borders');
10662			}
10663		}
10664	});
10665}
10666
10667/* 18.8.21 fills CT_Fills */
10668function parse_fills(t, styles, themes, opts) {
10669	styles.Fills = [];
10670	var fill = {};
10671	var pass = false;
10672	(t[0].match(tagregex)||[]).forEach(function(x) {
10673		var y = parsexmltag(x);
10674		switch(strip_ns(y[0])) {
10675			case '<fills': case '<fills>': case '</fills>': break;
10676
10677			/* 18.8.20 fill CT_Fill */
10678			case '<fill>': case '<fill': case '<fill/>':
10679				fill = {}; styles.Fills.push(fill); break;
10680			case '</fill>': break;
10681
10682			/* 18.8.24 gradientFill CT_GradientFill */
10683			case '<gradientFill>': break;
10684			case '<gradientFill':
10685			case '</gradientFill>': styles.Fills.push(fill); fill = {}; break;
10686
10687			/* 18.8.32 patternFill CT_PatternFill */
10688			case '<patternFill': case '<patternFill>':
10689				if(y.patternType) fill.patternType = y.patternType;
10690				break;
10691			case '<patternFill/>': case '</patternFill>': break;
10692
10693			/* 18.8.3 bgColor CT_Color */
10694			case '<bgColor':
10695				if(!fill.bgColor) fill.bgColor = {};
10696				if(y.indexed) fill.bgColor.indexed = parseInt(y.indexed, 10);
10697				if(y.theme) fill.bgColor.theme = parseInt(y.theme, 10);
10698				if(y.tint) fill.bgColor.tint = parseFloat(y.tint);
10699				/* Excel uses ARGB strings */
10700				if(y.rgb) fill.bgColor.rgb = y.rgb.slice(-6);
10701				break;
10702			case '<bgColor/>': case '</bgColor>': break;
10703
10704			/* 18.8.19 fgColor CT_Color */
10705			case '<fgColor':
10706				if(!fill.fgColor) fill.fgColor = {};
10707				if(y.theme) fill.fgColor.theme = parseInt(y.theme, 10);
10708				if(y.tint) fill.fgColor.tint = parseFloat(y.tint);
10709				/* Excel uses ARGB strings */
10710				if(y.rgb != null) fill.fgColor.rgb = y.rgb.slice(-6);
10711				break;
10712			case '<fgColor/>': case '</fgColor>': break;
10713
10714			/* 18.8.38 stop CT_GradientStop */
10715			case '<stop': case '<stop/>': break;
10716			case '</stop>': break;
10717
10718			/* 18.8.? color CT_Color */
10719			case '<color': case '<color/>': break;
10720			case '</color>': break;
10721
10722			/* 18.2.10 extLst CT_ExtensionList ? */
10723			case '<extLst': case '<extLst>': case '</extLst>': break;
10724			case '<ext': pass = true; break;
10725			case '</ext>': pass = false; break;
10726			default: if(opts && opts.WTF) {
10727				if(!pass) throw new Error('unrecognized ' + y[0] + ' in fills');
10728			}
10729		}
10730	});
10731}
10732
10733/* 18.8.23 fonts CT_Fonts */
10734function parse_fonts(t, styles, themes, opts) {
10735	styles.Fonts = [];
10736	var font = {};
10737	var pass = false;
10738	(t[0].match(tagregex)||[]).forEach(function(x) {
10739		var y = parsexmltag(x);
10740		switch(strip_ns(y[0])) {
10741			case '<fonts': case '<fonts>': case '</fonts>': break;
10742
10743			/* 18.8.22 font CT_Font */
10744			case '<font': case '<font>': break;
10745			case '</font>': case '<font/>':
10746				styles.Fonts.push(font);
10747				font = {};
10748				break;
10749
10750			/* 18.8.29 name CT_FontName */
10751			case '<name': if(y.val) font.name = utf8read(y.val); break;
10752			case '<name/>': case '</name>': break;
10753
10754			/* 18.8.2  b CT_BooleanProperty */
10755			case '<b': font.bold = y.val ? parsexmlbool(y.val) : 1; break;
10756			case '<b/>': font.bold = 1; break;
10757
10758			/* 18.8.26 i CT_BooleanProperty */
10759			case '<i': font.italic = y.val ? parsexmlbool(y.val) : 1; break;
10760			case '<i/>': font.italic = 1; break;
10761
10762			/* 18.4.13 u CT_UnderlineProperty */
10763			case '<u':
10764				switch(y.val) {
10765					case "none": font.underline = 0x00; break;
10766					case "single": font.underline = 0x01; break;
10767					case "double": font.underline = 0x02; break;
10768					case "singleAccounting": font.underline = 0x21; break;
10769					case "doubleAccounting": font.underline = 0x22; break;
10770				} break;
10771			case '<u/>': font.underline = 1; break;
10772
10773			/* 18.4.10 strike CT_BooleanProperty */
10774			case '<strike': font.strike = y.val ? parsexmlbool(y.val) : 1; break;
10775			case '<strike/>': font.strike = 1; break;
10776
10777			/* 18.4.2  outline CT_BooleanProperty */
10778			case '<outline': font.outline = y.val ? parsexmlbool(y.val) : 1; break;
10779			case '<outline/>': font.outline = 1; break;
10780
10781			/* 18.8.36 shadow CT_BooleanProperty */
10782			case '<shadow': font.shadow = y.val ? parsexmlbool(y.val) : 1; break;
10783			case '<shadow/>': font.shadow = 1; break;
10784
10785			/* 18.8.12 condense CT_BooleanProperty */
10786			case '<condense': font.condense = y.val ? parsexmlbool(y.val) : 1; break;
10787			case '<condense/>': font.condense = 1; break;
10788
10789			/* 18.8.17 extend CT_BooleanProperty */
10790			case '<extend': font.extend = y.val ? parsexmlbool(y.val) : 1; break;
10791			case '<extend/>': font.extend = 1; break;
10792
10793			/* 18.4.11 sz CT_FontSize */
10794			case '<sz': if(y.val) font.sz = +y.val; break;
10795			case '<sz/>': case '</sz>': break;
10796
10797			/* 18.4.14 vertAlign CT_VerticalAlignFontProperty */
10798			case '<vertAlign': if(y.val) font.vertAlign = y.val; break;
10799			case '<vertAlign/>': case '</vertAlign>': break;
10800
10801			/* 18.8.18 family CT_FontFamily */
10802			case '<family': if(y.val) font.family = parseInt(y.val,10); break;
10803			case '<family/>': case '</family>': break;
10804
10805			/* 18.8.35 scheme CT_FontScheme */
10806			case '<scheme': if(y.val) font.scheme = y.val; break;
10807			case '<scheme/>': case '</scheme>': break;
10808
10809			/* 18.4.1 charset CT_IntProperty */
10810			case '<charset':
10811				if(y.val == '1') break;
10812				y.codepage = CS2CP[parseInt(y.val, 10)];
10813				break;
10814
10815			/* 18.?.? color CT_Color */
10816			case '<color':
10817				if(!font.color) font.color = {};
10818				if(y.auto) font.color.auto = parsexmlbool(y.auto);
10819
10820				if(y.rgb) font.color.rgb = y.rgb.slice(-6);
10821				else if(y.indexed) {
10822					font.color.index = parseInt(y.indexed, 10);
10823					var icv = XLSIcv[font.color.index];
10824					if(font.color.index == 81) icv = XLSIcv[1];
10825					if(!icv) icv = XLSIcv[1]; //throw new Error(x); // note: 206 is valid
10826					font.color.rgb = icv[0].toString(16) + icv[1].toString(16) + icv[2].toString(16);
10827				} else if(y.theme) {
10828					font.color.theme = parseInt(y.theme, 10);
10829					if(y.tint) font.color.tint = parseFloat(y.tint);
10830					if(y.theme && themes.themeElements && themes.themeElements.clrScheme) {
10831						font.color.rgb = rgb_tint(themes.themeElements.clrScheme[font.color.theme].rgb, font.color.tint || 0);
10832					}
10833				}
10834
10835				break;
10836			case '<color/>': case '</color>': break;
10837
10838			/* note: sometimes mc:AlternateContent appears bare */
10839			case '<AlternateContent': pass = true; break;
10840			case '</AlternateContent>': pass = false; break;
10841
10842			/* 18.2.10 extLst CT_ExtensionList ? */
10843			case '<extLst': case '<extLst>': case '</extLst>': break;
10844			case '<ext': pass = true; break;
10845			case '</ext>': pass = false; break;
10846			default: if(opts && opts.WTF) {
10847				if(!pass) throw new Error('unrecognized ' + y[0] + ' in fonts');
10848			}
10849		}
10850	});
10851}
10852
10853/* 18.8.31 numFmts CT_NumFmts */
10854function parse_numFmts(t, styles, opts) {
10855	styles.NumberFmt = [];
10856	var k/*Array<number>*/ = (keys(table_fmt)/*:any*/);
10857	for(var i=0; i < k.length; ++i) styles.NumberFmt[k[i]] = table_fmt[k[i]];
10858	var m = t[0].match(tagregex);
10859	if(!m) return;
10860	for(i=0; i < m.length; ++i) {
10861		var y = parsexmltag(m[i]);
10862		switch(strip_ns(y[0])) {
10863			case '<numFmts': case '</numFmts>': case '<numFmts/>': case '<numFmts>': break;
10864			case '<numFmt': {
10865				var f=unescapexml(utf8read(y.formatCode)), j=parseInt(y.numFmtId,10);
10866				styles.NumberFmt[j] = f;
10867				if(j>0) {
10868					if(j > 0x188) {
10869						for(j = 0x188; j > 0x3c; --j) if(styles.NumberFmt[j] == null) break;
10870						styles.NumberFmt[j] = f;
10871					}
10872					SSF__load(f,j);
10873				}
10874			} break;
10875			case '</numFmt>': break;
10876			default: if(opts.WTF) throw new Error('unrecognized ' + y[0] + ' in numFmts');
10877		}
10878	}
10879}
10880
10881function write_numFmts(NF/*:{[n:number|string]:string}*//*::, opts*/) {
10882	var o = ["<numFmts>"];
10883	[[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) {
10884		for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) o[o.length] = (writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])}));
10885	});
10886	if(o.length === 1) return "";
10887	o[o.length] = ("</numFmts>");
10888	o[0] = writextag('numFmts', null, { count:o.length-2 }).replace("/>", ">");
10889	return o.join("");
10890}
10891
10892/* 18.8.10 cellXfs CT_CellXfs */
10893var cellXF_uint = [ "numFmtId", "fillId", "fontId", "borderId", "xfId" ];
10894var cellXF_bool = [ "applyAlignment", "applyBorder", "applyFill", "applyFont", "applyNumberFormat", "applyProtection", "pivotButton", "quotePrefix" ];
10895function parse_cellXfs(t, styles, opts) {
10896	styles.CellXf = [];
10897	var xf;
10898	var pass = false;
10899	(t[0].match(tagregex)||[]).forEach(function(x) {
10900		var y = parsexmltag(x), i = 0;
10901		switch(strip_ns(y[0])) {
10902			case '<cellXfs': case '<cellXfs>': case '<cellXfs/>': case '</cellXfs>': break;
10903
10904			/* 18.8.45 xf CT_Xf */
10905			case '<xf': case '<xf/>':
10906				xf = y;
10907				delete xf[0];
10908				for(i = 0; i < cellXF_uint.length; ++i) if(xf[cellXF_uint[i]])
10909					xf[cellXF_uint[i]] = parseInt(xf[cellXF_uint[i]], 10);
10910				for(i = 0; i < cellXF_bool.length; ++i) if(xf[cellXF_bool[i]])
10911					xf[cellXF_bool[i]] = parsexmlbool(xf[cellXF_bool[i]]);
10912				if(styles.NumberFmt && xf.numFmtId > 0x188) {
10913					for(i = 0x188; i > 0x3c; --i) if(styles.NumberFmt[xf.numFmtId] == styles.NumberFmt[i]) { xf.numFmtId = i; break; }
10914				}
10915				styles.CellXf.push(xf); break;
10916			case '</xf>': break;
10917
10918			/* 18.8.1 alignment CT_CellAlignment */
10919			case '<alignment': case '<alignment/>':
10920				var alignment = {};
10921				if(y.vertical) alignment.vertical = y.vertical;
10922				if(y.horizontal) alignment.horizontal = y.horizontal;
10923				if(y.textRotation != null) alignment.textRotation = y.textRotation;
10924				if(y.indent) alignment.indent = y.indent;
10925				if(y.wrapText) alignment.wrapText = parsexmlbool(y.wrapText);
10926				xf.alignment = alignment;
10927				break;
10928			case '</alignment>': break;
10929
10930			/* 18.8.33 protection CT_CellProtection */
10931			case '<protection':
10932				break;
10933			case '</protection>': case '<protection/>': break;
10934
10935			/* note: sometimes mc:AlternateContent appears bare */
10936			case '<AlternateContent': pass = true; break;
10937			case '</AlternateContent>': pass = false; break;
10938
10939			/* 18.2.10 extLst CT_ExtensionList ? */
10940			case '<extLst': case '<extLst>': case '</extLst>': break;
10941			case '<ext': pass = true; break;
10942			case '</ext>': pass = false; break;
10943			default: if(opts && opts.WTF) {
10944				if(!pass) throw new Error('unrecognized ' + y[0] + ' in cellXfs');
10945			}
10946		}
10947	});
10948}
10949
10950function write_cellXfs(cellXfs)/*:string*/ {
10951	var o/*:Array<string>*/ = [];
10952	o[o.length] = (writextag('cellXfs',null));
10953	cellXfs.forEach(function(c) {
10954		o[o.length] = (writextag('xf', null, c));
10955	});
10956	o[o.length] = ("</cellXfs>");
10957	if(o.length === 2) return "";
10958	o[0] = writextag('cellXfs',null, {count:o.length-2}).replace("/>",">");
10959	return o.join("");
10960}
10961
10962/* 18.8 Styles CT_Stylesheet*/
10963var parse_sty_xml= /*#__PURE__*/(function make_pstyx() {
10964var numFmtRegex = /<(?:\w+:)?numFmts([^>]*)>[\S\s]*?<\/(?:\w+:)?numFmts>/;
10965var cellXfRegex = /<(?:\w+:)?cellXfs([^>]*)>[\S\s]*?<\/(?:\w+:)?cellXfs>/;
10966var fillsRegex = /<(?:\w+:)?fills([^>]*)>[\S\s]*?<\/(?:\w+:)?fills>/;
10967var fontsRegex = /<(?:\w+:)?fonts([^>]*)>[\S\s]*?<\/(?:\w+:)?fonts>/;
10968var bordersRegex = /<(?:\w+:)?borders([^>]*)>[\S\s]*?<\/(?:\w+:)?borders>/;
10969
10970return function parse_sty_xml(data, themes, opts) {
10971	var styles = {};
10972	if(!data) return styles;
10973	data = data.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
10974	/* 18.8.39 styleSheet CT_Stylesheet */
10975	var t;
10976
10977	/* 18.8.31 numFmts CT_NumFmts ? */
10978	if((t=data.match(numFmtRegex))) parse_numFmts(t, styles, opts);
10979
10980	/* 18.8.23 fonts CT_Fonts ? */
10981	if((t=data.match(fontsRegex))) parse_fonts(t, styles, themes, opts);
10982
10983	/* 18.8.21 fills CT_Fills ? */
10984	if((t=data.match(fillsRegex))) parse_fills(t, styles, themes, opts);
10985
10986	/* 18.8.5  borders CT_Borders ? */
10987	if((t=data.match(bordersRegex))) parse_borders(t, styles, themes, opts);
10988
10989	/* 18.8.9  cellStyleXfs CT_CellStyleXfs ? */
10990	/* 18.8.8  cellStyles CT_CellStyles ? */
10991
10992	/* 18.8.10 cellXfs CT_CellXfs ? */
10993	if((t=data.match(cellXfRegex))) parse_cellXfs(t, styles, opts);
10994
10995	/* 18.8.15 dxfs CT_Dxfs ? */
10996	/* 18.8.42 tableStyles CT_TableStyles ? */
10997	/* 18.8.11 colors CT_Colors ? */
10998	/* 18.2.10 extLst CT_ExtensionList ? */
10999
11000	return styles;
11001};
11002})();
11003
11004function write_sty_xml(wb/*:Workbook*/, opts)/*:string*/ {
11005	var o = [XML_HEADER, writextag('styleSheet', null, {
11006		'xmlns': XMLNS_main[0],
11007		'xmlns:vt': XMLNS.vt
11008	})], w;
11009	if(wb.SSF && (w = write_numFmts(wb.SSF)) != null) o[o.length] = w;
11010	o[o.length] = ('<fonts count="1"><font><sz val="12"/><color theme="1"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font></fonts>');
11011	o[o.length] = ('<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
11012	o[o.length] = ('<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>');
11013	o[o.length] = ('<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>');
11014	if((w = write_cellXfs(opts.cellXfs))) o[o.length] = (w);
11015	o[o.length] = ('<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>');
11016	o[o.length] = ('<dxfs count="0"/>');
11017	o[o.length] = ('<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleMedium4"/>');
11018
11019	if(o.length>2){ o[o.length] = ('</styleSheet>'); o[1]=o[1].replace("/>",">"); }
11020	return o.join("");
11021}
11022/* [MS-XLSB] 2.4.657 BrtFmt */
11023function parse_BrtFmt(data, length/*:number*/) {
11024	var numFmtId = data.read_shift(2);
11025	var stFmtCode = parse_XLWideString(data,length-2);
11026	return [numFmtId, stFmtCode];
11027}
11028function write_BrtFmt(i/*:number*/, f/*:string*/, o) {
11029	if(!o) o = new_buf(6 + 4 * f.length);
11030	o.write_shift(2, i);
11031	write_XLWideString(f, o);
11032	var out = (o.length > o.l) ? o.slice(0, o.l) : o;
11033	if(o.l == null) o.l = o.length;
11034	return out;
11035}
11036
11037/* [MS-XLSB] 2.4.659 BrtFont TODO */
11038function parse_BrtFont(data, length/*:number*/, opts) {
11039	var out = ({}/*:any*/);
11040
11041	out.sz = data.read_shift(2) / 20;
11042
11043	var grbit = parse_FontFlags(data, 2, opts);
11044	if(grbit.fItalic) out.italic = 1;
11045	if(grbit.fCondense) out.condense = 1;
11046	if(grbit.fExtend) out.extend = 1;
11047	if(grbit.fShadow) out.shadow = 1;
11048	if(grbit.fOutline) out.outline = 1;
11049	if(grbit.fStrikeout) out.strike = 1;
11050
11051	var bls = data.read_shift(2);
11052	if(bls === 0x02BC) out.bold = 1;
11053
11054	switch(data.read_shift(2)) {
11055		/* case 0: out.vertAlign = "baseline"; break; */
11056		case 1: out.vertAlign = "superscript"; break;
11057		case 2: out.vertAlign = "subscript"; break;
11058	}
11059
11060	var underline = data.read_shift(1);
11061	if(underline != 0) out.underline = underline;
11062
11063	var family = data.read_shift(1);
11064	if(family > 0) out.family = family;
11065
11066	var bCharSet = data.read_shift(1);
11067	if(bCharSet > 0) out.charset = bCharSet;
11068
11069	data.l++;
11070	out.color = parse_BrtColor(data, 8);
11071
11072	switch(data.read_shift(1)) {
11073		/* case 0: out.scheme = "none": break; */
11074		case 1: out.scheme = "major"; break;
11075		case 2: out.scheme = "minor"; break;
11076	}
11077
11078	out.name = parse_XLWideString(data, length - 21);
11079
11080	return out;
11081}
11082function write_BrtFont(font/*:any*/, o) {
11083	if(!o) o = new_buf(25+4*32);
11084	o.write_shift(2, font.sz * 20);
11085	write_FontFlags(font, o);
11086	o.write_shift(2, font.bold ? 0x02BC : 0x0190);
11087	var sss = 0;
11088	if(font.vertAlign == "superscript") sss = 1;
11089	else if(font.vertAlign == "subscript") sss = 2;
11090	o.write_shift(2, sss);
11091	o.write_shift(1, font.underline || 0);
11092	o.write_shift(1, font.family || 0);
11093	o.write_shift(1, font.charset || 0);
11094	o.write_shift(1, 0);
11095	write_BrtColor(font.color, o);
11096	var scheme = 0;
11097	if(font.scheme == "major") scheme = 1;
11098	if(font.scheme == "minor") scheme = 2;
11099	o.write_shift(1, scheme);
11100	write_XLWideString(font.name, o);
11101	return o.length > o.l ? o.slice(0, o.l) : o;
11102}
11103
11104/* [MS-XLSB] 2.4.650 BrtFill */
11105var XLSBFillPTNames = [
11106	"none",
11107	"solid",
11108	"mediumGray",
11109	"darkGray",
11110	"lightGray",
11111	"darkHorizontal",
11112	"darkVertical",
11113	"darkDown",
11114	"darkUp",
11115	"darkGrid",
11116	"darkTrellis",
11117	"lightHorizontal",
11118	"lightVertical",
11119	"lightDown",
11120	"lightUp",
11121	"lightGrid",
11122	"lightTrellis",
11123	"gray125",
11124	"gray0625"
11125];
11126var rev_XLSBFillPTNames/*:EvertNumType*/;
11127/* TODO: gradient fill representation */
11128var parse_BrtFill = parsenoop;
11129function write_BrtFill(fill, o) {
11130	if(!o) o = new_buf(4*3 + 8*7 + 16*1);
11131	if(!rev_XLSBFillPTNames) rev_XLSBFillPTNames = (evert(XLSBFillPTNames)/*:any*/);
11132	var fls/*:number*/ = rev_XLSBFillPTNames[fill.patternType];
11133	if(fls == null) fls = 0x28;
11134	o.write_shift(4, fls);
11135	var j = 0;
11136	if(fls != 0x28) {
11137		/* TODO: custom FG Color */
11138		write_BrtColor({auto:1}, o);
11139		/* TODO: custom BG Color */
11140		write_BrtColor({auto:1}, o);
11141
11142		for(; j < 12; ++j) o.write_shift(4, 0);
11143	} else {
11144		for(; j < 4; ++j) o.write_shift(4, 0);
11145
11146		for(; j < 12; ++j) o.write_shift(4, 0); /* TODO */
11147		/* iGradientType */
11148		/* xnumDegree */
11149		/* xnumFillToLeft */
11150		/* xnumFillToRight */
11151		/* xnumFillToTop */
11152		/* xnumFillToBottom */
11153		/* cNumStop */
11154		/* xfillGradientStop */
11155	}
11156	return o.length > o.l ? o.slice(0, o.l) : o;
11157}
11158
11159/* [MS-XLSB] 2.4.824 BrtXF */
11160function parse_BrtXF(data, length/*:number*/) {
11161	var tgt = data.l + length;
11162	var ixfeParent = data.read_shift(2);
11163	var ifmt = data.read_shift(2);
11164	data.l = tgt;
11165	return {ixfe:ixfeParent, numFmtId:ifmt };
11166}
11167function write_BrtXF(data, ixfeP, o) {
11168	if(!o) o = new_buf(16);
11169	o.write_shift(2, ixfeP||0);
11170	o.write_shift(2, data.numFmtId||0);
11171	o.write_shift(2, 0); /* iFont */
11172	o.write_shift(2, 0); /* iFill */
11173	o.write_shift(2, 0); /* ixBorder */
11174	o.write_shift(1, 0); /* trot */
11175	o.write_shift(1, 0); /* indent */
11176	var flow = 0;
11177	o.write_shift(1, flow); /* flags */
11178	o.write_shift(1, 0); /* flags */
11179	o.write_shift(1, 0); /* xfGrbitAtr */
11180	o.write_shift(1, 0);
11181	return o;
11182}
11183
11184/* [MS-XLSB] 2.5.4 Blxf TODO */
11185function write_Blxf(data, o) {
11186	if(!o) o = new_buf(10);
11187	o.write_shift(1, 0); /* dg */
11188	o.write_shift(1, 0);
11189	o.write_shift(4, 0); /* color */
11190	o.write_shift(4, 0); /* color */
11191	return o;
11192}
11193/* [MS-XLSB] 2.4.302 BrtBorder TODO */
11194var parse_BrtBorder = parsenoop;
11195function write_BrtBorder(border, o) {
11196	if(!o) o = new_buf(51);
11197	o.write_shift(1, 0); /* diagonal */
11198	write_Blxf(null, o); /* top */
11199	write_Blxf(null, o); /* bottom */
11200	write_Blxf(null, o); /* left */
11201	write_Blxf(null, o); /* right */
11202	write_Blxf(null, o); /* diag */
11203	return o.length > o.l ? o.slice(0, o.l) : o;
11204}
11205
11206/* [MS-XLSB] 2.4.763 BrtStyle TODO */
11207function write_BrtStyle(style, o) {
11208	if(!o) o = new_buf(12+4*10);
11209	o.write_shift(4, style.xfId);
11210	o.write_shift(2, 1);
11211	o.write_shift(1, +style.builtinId);
11212	o.write_shift(1, 0); /* iLevel */
11213	write_XLNullableWideString(style.name || "", o);
11214	return o.length > o.l ? o.slice(0, o.l) : o;
11215}
11216
11217/* [MS-XLSB] 2.4.272 BrtBeginTableStyles */
11218function write_BrtBeginTableStyles(cnt, defTableStyle, defPivotStyle) {
11219	var o = new_buf(4+256*2*4);
11220	o.write_shift(4, cnt);
11221	write_XLNullableWideString(defTableStyle, o);
11222	write_XLNullableWideString(defPivotStyle, o);
11223	return o.length > o.l ? o.slice(0, o.l) : o;
11224}
11225
11226/* [MS-XLSB] 2.1.7.50 Styles */
11227function parse_sty_bin(data, themes, opts) {
11228	var styles = {};
11229	styles.NumberFmt = ([]/*:any*/);
11230	for(var y in table_fmt) styles.NumberFmt[y] = table_fmt[y];
11231
11232	styles.CellXf = [];
11233	styles.Fonts = [];
11234	var state/*:Array<string>*/ = [];
11235	var pass = false;
11236	recordhopper(data, function hopper_sty(val, R, RT) {
11237		switch(RT) {
11238			case 0x002C: /* BrtFmt */
11239				styles.NumberFmt[val[0]] = val[1]; SSF__load(val[1], val[0]);
11240				break;
11241			case 0x002B: /* BrtFont */
11242				styles.Fonts.push(val);
11243				if(val.color.theme != null && themes && themes.themeElements && themes.themeElements.clrScheme) {
11244					val.color.rgb = rgb_tint(themes.themeElements.clrScheme[val.color.theme].rgb, val.color.tint || 0);
11245				}
11246				break;
11247			case 0x0401: /* BrtKnownFonts */ break;
11248			case 0x002D: /* BrtFill */
11249				break;
11250			case 0x002E: /* BrtBorder */
11251				break;
11252			case 0x002F: /* BrtXF */
11253				if(state[state.length - 1] == 0x0269 /* BrtBeginCellXFs */) {
11254					styles.CellXf.push(val);
11255				}
11256				break;
11257			case 0x0030: /* BrtStyle */
11258			case 0x01FB: /* BrtDXF */
11259			case 0x023C: /* BrtMRUColor */
11260			case 0x01DB: /* BrtIndexedColor */
11261				break;
11262
11263			case 0x0493: /* BrtDXF14 */
11264			case 0x0836: /* BrtDXF15 */
11265			case 0x046A: /* BrtSlicerStyleElement */
11266			case 0x0200: /* BrtTableStyleElement */
11267			case 0x082F: /* BrtTimelineStyleElement */
11268			case 0x0C00: /* BrtUid */
11269				break;
11270
11271			case 0x0023: /* BrtFRTBegin */
11272				pass = true; break;
11273			case 0x0024: /* BrtFRTEnd */
11274				pass = false; break;
11275			case 0x0025: /* BrtACBegin */
11276				state.push(RT); pass = true; break;
11277			case 0x0026: /* BrtACEnd */
11278				state.pop(); pass = false; break;
11279
11280			default:
11281				if(R.T > 0) state.push(RT);
11282				else if(R.T < 0) state.pop();
11283				else if(!pass || (opts.WTF && state[state.length-1] != 0x0025 /* BrtACBegin */)) throw new Error("Unexpected record 0x" + RT.toString(16));
11284		}
11285	});
11286	return styles;
11287}
11288
11289function write_FMTS_bin(ba, NF/*:?SSFTable*/) {
11290	if(!NF) return;
11291	var cnt = 0;
11292	[[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) {
11293		/*:: if(!NF) return; */
11294		for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) ++cnt;
11295	});
11296
11297	if(cnt == 0) return;
11298	write_record(ba, 0x0267 /* BrtBeginFmts */, write_UInt32LE(cnt));
11299	[[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) {
11300		/*:: if(!NF) return; */
11301		for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) write_record(ba, 0x002C /* BrtFmt */, write_BrtFmt(i, NF[i]));
11302	});
11303	write_record(ba, 0x0268 /* BrtEndFmts */);
11304}
11305
11306function write_FONTS_bin(ba/*::, data*/) {
11307	var cnt = 1;
11308
11309	if(cnt == 0) return;
11310	write_record(ba, 0x0263 /* BrtBeginFonts */, write_UInt32LE(cnt));
11311	write_record(ba, 0x002B /* BrtFont */, write_BrtFont({
11312		sz:12,
11313		color: {theme:1},
11314		name: "Calibri",
11315		family: 2,
11316		scheme: "minor"
11317	}));
11318	/* 1*65491BrtFont [ACFONTS] */
11319	write_record(ba, 0x0264 /* BrtEndFonts */);
11320}
11321
11322function write_FILLS_bin(ba/*::, data*/) {
11323	var cnt = 2;
11324
11325	if(cnt == 0) return;
11326	write_record(ba, 0x025B /* BrtBeginFills */, write_UInt32LE(cnt));
11327	write_record(ba, 0x002D /* BrtFill */, write_BrtFill({patternType:"none"}));
11328	write_record(ba, 0x002D /* BrtFill */, write_BrtFill({patternType:"gray125"}));
11329	/* 1*65431BrtFill */
11330	write_record(ba, 0x025C /* BrtEndFills */);
11331}
11332
11333function write_BORDERS_bin(ba/*::, data*/) {
11334	var cnt = 1;
11335
11336	if(cnt == 0) return;
11337	write_record(ba, 0x0265 /* BrtBeginBorders */, write_UInt32LE(cnt));
11338	write_record(ba, 0x002E /* BrtBorder */, write_BrtBorder({}));
11339	/* 1*65430BrtBorder */
11340	write_record(ba, 0x0266 /* BrtEndBorders */);
11341}
11342
11343function write_CELLSTYLEXFS_bin(ba/*::, data*/) {
11344	var cnt = 1;
11345	write_record(ba, 0x0272 /* BrtBeginCellStyleXFs */, write_UInt32LE(cnt));
11346	write_record(ba, 0x002F /* BrtXF */, write_BrtXF({
11347		numFmtId: 0,
11348		fontId:   0,
11349		fillId:   0,
11350		borderId: 0
11351	}, 0xFFFF));
11352	/* 1*65430(BrtXF *FRT) */
11353	write_record(ba, 0x0273 /* BrtEndCellStyleXFs */);
11354}
11355
11356function write_CELLXFS_bin(ba, data) {
11357	write_record(ba, 0x0269 /* BrtBeginCellXFs */, write_UInt32LE(data.length));
11358	data.forEach(function(c) { write_record(ba, 0x002F /* BrtXF */, write_BrtXF(c,0)); });
11359	/* 1*65430(BrtXF *FRT) */
11360	write_record(ba, 0x026A /* BrtEndCellXFs */);
11361}
11362
11363function write_STYLES_bin(ba/*::, data*/) {
11364	var cnt = 1;
11365
11366	write_record(ba, 0x026B /* BrtBeginStyles */, write_UInt32LE(cnt));
11367	write_record(ba, 0x0030 /* BrtStyle */, write_BrtStyle({
11368		xfId:0,
11369		builtinId:0,
11370		name:"Normal"
11371	}));
11372	/* 1*65430(BrtStyle *FRT) */
11373	write_record(ba, 0x026C /* BrtEndStyles */);
11374}
11375
11376function write_DXFS_bin(ba/*::, data*/) {
11377	var cnt = 0;
11378
11379	write_record(ba, 0x01F9 /* BrtBeginDXFs */, write_UInt32LE(cnt));
11380	/* *2147483647(BrtDXF *FRT) */
11381	write_record(ba, 0x01FA /* BrtEndDXFs */);
11382}
11383
11384function write_TABLESTYLES_bin(ba/*::, data*/) {
11385	var cnt = 0;
11386
11387	write_record(ba, 0x01FC /* BrtBeginTableStyles */, write_BrtBeginTableStyles(cnt, "TableStyleMedium9", "PivotStyleMedium4"));
11388	/* *TABLESTYLE */
11389	write_record(ba, 0x01FD /* BrtEndTableStyles */);
11390}
11391
11392function write_COLORPALETTE_bin(/*::ba, data*/) {
11393	return;
11394	/* BrtBeginColorPalette [INDEXEDCOLORS] [MRUCOLORS] BrtEndColorPalette */
11395}
11396
11397/* [MS-XLSB] 2.1.7.50 Styles */
11398function write_sty_bin(wb, opts) {
11399	var ba = buf_array();
11400	write_record(ba, 0x0116 /* BrtBeginStyleSheet */);
11401	write_FMTS_bin(ba, wb.SSF);
11402	write_FONTS_bin(ba, wb);
11403	write_FILLS_bin(ba, wb);
11404	write_BORDERS_bin(ba, wb);
11405	write_CELLSTYLEXFS_bin(ba, wb);
11406	write_CELLXFS_bin(ba, opts.cellXfs);
11407	write_STYLES_bin(ba, wb);
11408	write_DXFS_bin(ba, wb);
11409	write_TABLESTYLES_bin(ba, wb);
11410	write_COLORPALETTE_bin(ba, wb);
11411	/* FRTSTYLESHEET*/
11412	write_record(ba, 0x0117 /* BrtEndStyleSheet */);
11413	return ba.end();
11414}
11415/* Even though theme layout is dk1 lt1 dk2 lt2, true order is lt1 dk1 lt2 dk2 */
11416var XLSXThemeClrScheme = [
11417	'</a:lt1>', '</a:dk1>', '</a:lt2>', '</a:dk2>',
11418	'</a:accent1>', '</a:accent2>', '</a:accent3>',
11419	'</a:accent4>', '</a:accent5>', '</a:accent6>',
11420	'</a:hlink>', '</a:folHlink>'
11421];
11422/* 20.1.6.2 clrScheme CT_ColorScheme */
11423function parse_clrScheme(t, themes, opts) {
11424	themes.themeElements.clrScheme = [];
11425	var color = {};
11426	(t[0].match(tagregex)||[]).forEach(function(x) {
11427		var y = parsexmltag(x);
11428		switch(y[0]) {
11429			/* 20.1.6.2 clrScheme (Color Scheme) CT_ColorScheme */
11430			case '<a:clrScheme': case '</a:clrScheme>': break;
11431
11432			/* 20.1.2.3.32 srgbClr CT_SRgbColor */
11433			case '<a:srgbClr':
11434				color.rgb = y.val; break;
11435
11436			/* 20.1.2.3.33 sysClr CT_SystemColor */
11437			case '<a:sysClr':
11438				color.rgb = y.lastClr; break;
11439
11440			/* 20.1.4.1.1 accent1 (Accent 1) */
11441			/* 20.1.4.1.2 accent2 (Accent 2) */
11442			/* 20.1.4.1.3 accent3 (Accent 3) */
11443			/* 20.1.4.1.4 accent4 (Accent 4) */
11444			/* 20.1.4.1.5 accent5 (Accent 5) */
11445			/* 20.1.4.1.6 accent6 (Accent 6) */
11446			/* 20.1.4.1.9 dk1 (Dark 1) */
11447			/* 20.1.4.1.10 dk2 (Dark 2) */
11448			/* 20.1.4.1.15 folHlink (Followed Hyperlink) */
11449			/* 20.1.4.1.19 hlink (Hyperlink) */
11450			/* 20.1.4.1.22 lt1 (Light 1) */
11451			/* 20.1.4.1.23 lt2 (Light 2) */
11452			case '<a:dk1>': case '</a:dk1>':
11453			case '<a:lt1>': case '</a:lt1>':
11454			case '<a:dk2>': case '</a:dk2>':
11455			case '<a:lt2>': case '</a:lt2>':
11456			case '<a:accent1>': case '</a:accent1>':
11457			case '<a:accent2>': case '</a:accent2>':
11458			case '<a:accent3>': case '</a:accent3>':
11459			case '<a:accent4>': case '</a:accent4>':
11460			case '<a:accent5>': case '</a:accent5>':
11461			case '<a:accent6>': case '</a:accent6>':
11462			case '<a:hlink>': case '</a:hlink>':
11463			case '<a:folHlink>': case '</a:folHlink>':
11464				if (y[0].charAt(1) === '/') {
11465					themes.themeElements.clrScheme[XLSXThemeClrScheme.indexOf(y[0])] = color;
11466					color = {};
11467				} else {
11468					color.name = y[0].slice(3, y[0].length - 1);
11469				}
11470				break;
11471
11472			default: if(opts && opts.WTF) throw new Error('Unrecognized ' + y[0] + ' in clrScheme');
11473		}
11474	});
11475}
11476
11477/* 20.1.4.1.18 fontScheme CT_FontScheme */
11478function parse_fontScheme(/*::t, themes, opts*/) { }
11479
11480/* 20.1.4.1.15 fmtScheme CT_StyleMatrix */
11481function parse_fmtScheme(/*::t, themes, opts*/) { }
11482
11483var clrsregex = /<a:clrScheme([^>]*)>[\s\S]*<\/a:clrScheme>/;
11484var fntsregex = /<a:fontScheme([^>]*)>[\s\S]*<\/a:fontScheme>/;
11485var fmtsregex = /<a:fmtScheme([^>]*)>[\s\S]*<\/a:fmtScheme>/;
11486
11487/* 20.1.6.10 themeElements CT_BaseStyles */
11488function parse_themeElements(data, themes, opts) {
11489	themes.themeElements = {};
11490
11491	var t;
11492
11493	[
11494		/* clrScheme CT_ColorScheme */
11495		['clrScheme', clrsregex, parse_clrScheme],
11496		/* fontScheme CT_FontScheme */
11497		['fontScheme', fntsregex, parse_fontScheme],
11498		/* fmtScheme CT_StyleMatrix */
11499		['fmtScheme', fmtsregex, parse_fmtScheme]
11500	].forEach(function(m) {
11501		if(!(t=data.match(m[1]))) throw new Error(m[0] + ' not found in themeElements');
11502		m[2](t, themes, opts);
11503	});
11504}
11505
11506var themeltregex = /<a:themeElements([^>]*)>[\s\S]*<\/a:themeElements>/;
11507
11508/* 14.2.7 Theme Part */
11509function parse_theme_xml(data/*:string*/, opts) {
11510	/* 20.1.6.9 theme CT_OfficeStyleSheet */
11511	if(!data || data.length === 0) data = write_theme();
11512
11513	var t;
11514	var themes = {};
11515
11516	/* themeElements CT_BaseStyles */
11517	if(!(t=data.match(themeltregex))) throw new Error('themeElements not found in theme');
11518	parse_themeElements(t[0], themes, opts);
11519	themes.raw = data;
11520	return themes;
11521}
11522
11523function write_theme(Themes, opts)/*:string*/ {
11524	if(opts && opts.themeXLSX) return opts.themeXLSX;
11525	if(Themes && typeof Themes.raw == "string") return Themes.raw;
11526	var o = [XML_HEADER];
11527	o[o.length] = '<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">';
11528	o[o.length] =  '<a:themeElements>';
11529
11530	o[o.length] =   '<a:clrScheme name="Office">';
11531	o[o.length] =    '<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>';
11532	o[o.length] =    '<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>';
11533	o[o.length] =    '<a:dk2><a:srgbClr val="1F497D"/></a:dk2>';
11534	o[o.length] =    '<a:lt2><a:srgbClr val="EEECE1"/></a:lt2>';
11535	o[o.length] =    '<a:accent1><a:srgbClr val="4F81BD"/></a:accent1>';
11536	o[o.length] =    '<a:accent2><a:srgbClr val="C0504D"/></a:accent2>';
11537	o[o.length] =    '<a:accent3><a:srgbClr val="9BBB59"/></a:accent3>';
11538	o[o.length] =    '<a:accent4><a:srgbClr val="8064A2"/></a:accent4>';
11539	o[o.length] =    '<a:accent5><a:srgbClr val="4BACC6"/></a:accent5>';
11540	o[o.length] =    '<a:accent6><a:srgbClr val="F79646"/></a:accent6>';
11541	o[o.length] =    '<a:hlink><a:srgbClr val="0000FF"/></a:hlink>';
11542	o[o.length] =    '<a:folHlink><a:srgbClr val="800080"/></a:folHlink>';
11543	o[o.length] =   '</a:clrScheme>';
11544
11545	o[o.length] =   '<a:fontScheme name="Office">';
11546	o[o.length] =    '<a:majorFont>';
11547	o[o.length] =     '<a:latin typeface="Cambria"/>';
11548	o[o.length] =     '<a:ea typeface=""/>';
11549	o[o.length] =     '<a:cs typeface=""/>';
11550	o[o.length] =     '<a:font script="Jpan" typeface="MS Pゴシック"/>';
11551	o[o.length] =     '<a:font script="Hang" typeface="맑은 고딕"/>';
11552	o[o.length] =     '<a:font script="Hans" typeface="宋体"/>';
11553	o[o.length] =     '<a:font script="Hant" typeface="新細明體"/>';
11554	o[o.length] =     '<a:font script="Arab" typeface="Times New Roman"/>';
11555	o[o.length] =     '<a:font script="Hebr" typeface="Times New Roman"/>';
11556	o[o.length] =     '<a:font script="Thai" typeface="Tahoma"/>';
11557	o[o.length] =     '<a:font script="Ethi" typeface="Nyala"/>';
11558	o[o.length] =     '<a:font script="Beng" typeface="Vrinda"/>';
11559	o[o.length] =     '<a:font script="Gujr" typeface="Shruti"/>';
11560	o[o.length] =     '<a:font script="Khmr" typeface="MoolBoran"/>';
11561	o[o.length] =     '<a:font script="Knda" typeface="Tunga"/>';
11562	o[o.length] =     '<a:font script="Guru" typeface="Raavi"/>';
11563	o[o.length] =     '<a:font script="Cans" typeface="Euphemia"/>';
11564	o[o.length] =     '<a:font script="Cher" typeface="Plantagenet Cherokee"/>';
11565	o[o.length] =     '<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>';
11566	o[o.length] =     '<a:font script="Tibt" typeface="Microsoft Himalaya"/>';
11567	o[o.length] =     '<a:font script="Thaa" typeface="MV Boli"/>';
11568	o[o.length] =     '<a:font script="Deva" typeface="Mangal"/>';
11569	o[o.length] =     '<a:font script="Telu" typeface="Gautami"/>';
11570	o[o.length] =     '<a:font script="Taml" typeface="Latha"/>';
11571	o[o.length] =     '<a:font script="Syrc" typeface="Estrangelo Edessa"/>';
11572	o[o.length] =     '<a:font script="Orya" typeface="Kalinga"/>';
11573	o[o.length] =     '<a:font script="Mlym" typeface="Kartika"/>';
11574	o[o.length] =     '<a:font script="Laoo" typeface="DokChampa"/>';
11575	o[o.length] =     '<a:font script="Sinh" typeface="Iskoola Pota"/>';
11576	o[o.length] =     '<a:font script="Mong" typeface="Mongolian Baiti"/>';
11577	o[o.length] =     '<a:font script="Viet" typeface="Times New Roman"/>';
11578	o[o.length] =     '<a:font script="Uigh" typeface="Microsoft Uighur"/>';
11579	o[o.length] =     '<a:font script="Geor" typeface="Sylfaen"/>';
11580	o[o.length] =    '</a:majorFont>';
11581	o[o.length] =    '<a:minorFont>';
11582	o[o.length] =     '<a:latin typeface="Calibri"/>';
11583	o[o.length] =     '<a:ea typeface=""/>';
11584	o[o.length] =     '<a:cs typeface=""/>';
11585	o[o.length] =     '<a:font script="Jpan" typeface="MS Pゴシック"/>';
11586	o[o.length] =     '<a:font script="Hang" typeface="맑은 고딕"/>';
11587	o[o.length] =     '<a:font script="Hans" typeface="宋体"/>';
11588	o[o.length] =     '<a:font script="Hant" typeface="新細明體"/>';
11589	o[o.length] =     '<a:font script="Arab" typeface="Arial"/>';
11590	o[o.length] =     '<a:font script="Hebr" typeface="Arial"/>';
11591	o[o.length] =     '<a:font script="Thai" typeface="Tahoma"/>';
11592	o[o.length] =     '<a:font script="Ethi" typeface="Nyala"/>';
11593	o[o.length] =     '<a:font script="Beng" typeface="Vrinda"/>';
11594	o[o.length] =     '<a:font script="Gujr" typeface="Shruti"/>';
11595	o[o.length] =     '<a:font script="Khmr" typeface="DaunPenh"/>';
11596	o[o.length] =     '<a:font script="Knda" typeface="Tunga"/>';
11597	o[o.length] =     '<a:font script="Guru" typeface="Raavi"/>';
11598	o[o.length] =     '<a:font script="Cans" typeface="Euphemia"/>';
11599	o[o.length] =     '<a:font script="Cher" typeface="Plantagenet Cherokee"/>';
11600	o[o.length] =     '<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>';
11601	o[o.length] =     '<a:font script="Tibt" typeface="Microsoft Himalaya"/>';
11602	o[o.length] =     '<a:font script="Thaa" typeface="MV Boli"/>';
11603	o[o.length] =     '<a:font script="Deva" typeface="Mangal"/>';
11604	o[o.length] =     '<a:font script="Telu" typeface="Gautami"/>';
11605	o[o.length] =     '<a:font script="Taml" typeface="Latha"/>';
11606	o[o.length] =     '<a:font script="Syrc" typeface="Estrangelo Edessa"/>';
11607	o[o.length] =     '<a:font script="Orya" typeface="Kalinga"/>';
11608	o[o.length] =     '<a:font script="Mlym" typeface="Kartika"/>';
11609	o[o.length] =     '<a:font script="Laoo" typeface="DokChampa"/>';
11610	o[o.length] =     '<a:font script="Sinh" typeface="Iskoola Pota"/>';
11611	o[o.length] =     '<a:font script="Mong" typeface="Mongolian Baiti"/>';
11612	o[o.length] =     '<a:font script="Viet" typeface="Arial"/>';
11613	o[o.length] =     '<a:font script="Uigh" typeface="Microsoft Uighur"/>';
11614	o[o.length] =     '<a:font script="Geor" typeface="Sylfaen"/>';
11615	o[o.length] =    '</a:minorFont>';
11616	o[o.length] =   '</a:fontScheme>';
11617
11618	o[o.length] =   '<a:fmtScheme name="Office">';
11619	o[o.length] =    '<a:fillStyleLst>';
11620	o[o.length] =     '<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>';
11621	o[o.length] =     '<a:gradFill rotWithShape="1">';
11622	o[o.length] =      '<a:gsLst>';
11623	o[o.length] =       '<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/><a:satMod val="300000"/></a:schemeClr></a:gs>';
11624	o[o.length] =       '<a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/><a:satMod val="300000"/></a:schemeClr></a:gs>';
11625	o[o.length] =       '<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/><a:satMod val="350000"/></a:schemeClr></a:gs>';
11626	o[o.length] =      '</a:gsLst>';
11627	o[o.length] =      '<a:lin ang="16200000" scaled="1"/>';
11628	o[o.length] =     '</a:gradFill>';
11629	o[o.length] =     '<a:gradFill rotWithShape="1">';
11630	o[o.length] =      '<a:gsLst>';
11631	o[o.length] =       '<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="100000"/><a:shade val="100000"/><a:satMod val="130000"/></a:schemeClr></a:gs>';
11632	o[o.length] =       '<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="50000"/><a:shade val="100000"/><a:satMod val="350000"/></a:schemeClr></a:gs>';
11633	o[o.length] =      '</a:gsLst>';
11634	o[o.length] =      '<a:lin ang="16200000" scaled="0"/>';
11635	o[o.length] =     '</a:gradFill>';
11636	o[o.length] =    '</a:fillStyleLst>';
11637	o[o.length] =    '<a:lnStyleLst>';
11638	o[o.length] =     '<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/><a:satMod val="105000"/></a:schemeClr></a:solidFill><a:prstDash val="solid"/></a:ln>';
11639	o[o.length] =     '<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>';
11640	o[o.length] =     '<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>';
11641	o[o.length] =    '</a:lnStyleLst>';
11642	o[o.length] =    '<a:effectStyleLst>';
11643	o[o.length] =     '<a:effectStyle>';
11644	o[o.length] =      '<a:effectLst>';
11645	o[o.length] =       '<a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="38000"/></a:srgbClr></a:outerShdw>';
11646	o[o.length] =      '</a:effectLst>';
11647	o[o.length] =     '</a:effectStyle>';
11648	o[o.length] =     '<a:effectStyle>';
11649	o[o.length] =      '<a:effectLst>';
11650	o[o.length] =       '<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw>';
11651	o[o.length] =      '</a:effectLst>';
11652	o[o.length] =     '</a:effectStyle>';
11653	o[o.length] =     '<a:effectStyle>';
11654	o[o.length] =      '<a:effectLst>';
11655	o[o.length] =       '<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw>';
11656	o[o.length] =      '</a:effectLst>';
11657	o[o.length] =      '<a:scene3d><a:camera prst="orthographicFront"><a:rot lat="0" lon="0" rev="0"/></a:camera><a:lightRig rig="threePt" dir="t"><a:rot lat="0" lon="0" rev="1200000"/></a:lightRig></a:scene3d>';
11658	o[o.length] =      '<a:sp3d><a:bevelT w="63500" h="25400"/></a:sp3d>';
11659	o[o.length] =     '</a:effectStyle>';
11660	o[o.length] =    '</a:effectStyleLst>';
11661	o[o.length] =    '<a:bgFillStyleLst>';
11662	o[o.length] =     '<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>';
11663	o[o.length] =     '<a:gradFill rotWithShape="1">';
11664	o[o.length] =      '<a:gsLst>';
11665	o[o.length] =       '<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/><a:satMod val="350000"/></a:schemeClr></a:gs>';
11666	o[o.length] =       '<a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/><a:shade val="99000"/><a:satMod val="350000"/></a:schemeClr></a:gs>';
11667	o[o.length] =       '<a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="20000"/><a:satMod val="255000"/></a:schemeClr></a:gs>';
11668	o[o.length] =      '</a:gsLst>';
11669	o[o.length] =      '<a:path path="circle"><a:fillToRect l="50000" t="-80000" r="50000" b="180000"/></a:path>';
11670	o[o.length] =     '</a:gradFill>';
11671	o[o.length] =     '<a:gradFill rotWithShape="1">';
11672	o[o.length] =      '<a:gsLst>';
11673	o[o.length] =       '<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/><a:satMod val="300000"/></a:schemeClr></a:gs>';
11674	o[o.length] =       '<a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="30000"/><a:satMod val="200000"/></a:schemeClr></a:gs>';
11675	o[o.length] =      '</a:gsLst>';
11676	o[o.length] =      '<a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>';
11677	o[o.length] =     '</a:gradFill>';
11678	o[o.length] =    '</a:bgFillStyleLst>';
11679	o[o.length] =   '</a:fmtScheme>';
11680	o[o.length] =  '</a:themeElements>';
11681
11682	o[o.length] =  '<a:objectDefaults>';
11683	o[o.length] =   '<a:spDef>';
11684	o[o.length] =    '<a:spPr/><a:bodyPr/><a:lstStyle/><a:style><a:lnRef idx="1"><a:schemeClr val="accent1"/></a:lnRef><a:fillRef idx="3"><a:schemeClr val="accent1"/></a:fillRef><a:effectRef idx="2"><a:schemeClr val="accent1"/></a:effectRef><a:fontRef idx="minor"><a:schemeClr val="lt1"/></a:fontRef></a:style>';
11685	o[o.length] =   '</a:spDef>';
11686	o[o.length] =   '<a:lnDef>';
11687	o[o.length] =    '<a:spPr/><a:bodyPr/><a:lstStyle/><a:style><a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef><a:fillRef idx="0"><a:schemeClr val="accent1"/></a:fillRef><a:effectRef idx="1"><a:schemeClr val="accent1"/></a:effectRef><a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef></a:style>';
11688	o[o.length] =   '</a:lnDef>';
11689	o[o.length] =  '</a:objectDefaults>';
11690	o[o.length] =  '<a:extraClrSchemeLst/>';
11691	o[o.length] = '</a:theme>';
11692	return o.join("");
11693}
11694/* [MS-XLS] 2.4.326 TODO: payload is a zip file */
11695function parse_Theme(blob, length, opts) {
11696	var end = blob.l + length;
11697	var dwThemeVersion = blob.read_shift(4);
11698	if(dwThemeVersion === 124226) return;
11699	if(!opts.cellStyles) { blob.l = end; return; }
11700	var data = blob.slice(blob.l);
11701	blob.l = end;
11702	var zip; try { zip = zip_read(data, {type: "array"}); } catch(e) { return; }
11703	var themeXML = getzipstr(zip, "theme/theme/theme1.xml", true);
11704	if(!themeXML) return;
11705	return parse_theme_xml(themeXML, opts);
11706}
11707
11708/* 2.5.49 */
11709function parse_ColorTheme(blob/*::, length*/) { return blob.read_shift(4); }
11710
11711/* 2.5.155 */
11712function parse_FullColorExt(blob/*::, length*/) {
11713	var o = {};
11714	o.xclrType = blob.read_shift(2);
11715	o.nTintShade = blob.read_shift(2);
11716	switch(o.xclrType) {
11717		case 0: blob.l += 4; break;
11718		case 1: o.xclrValue = parse_IcvXF(blob, 4); break;
11719		case 2: o.xclrValue = parse_LongRGBA(blob, 4); break;
11720		case 3: o.xclrValue = parse_ColorTheme(blob, 4); break;
11721		case 4: blob.l += 4; break;
11722	}
11723	blob.l += 8;
11724	return o;
11725}
11726
11727/* 2.5.164 TODO: read 7 bits*/
11728function parse_IcvXF(blob, length) {
11729	return parsenoop(blob, length);
11730}
11731
11732/* 2.5.280 */
11733function parse_XFExtGradient(blob, length) {
11734	return parsenoop(blob, length);
11735}
11736
11737/* [MS-XLS] 2.5.108 */
11738function parse_ExtProp(blob/*::, length*/)/*:Array<any>*/ {
11739	var extType = blob.read_shift(2);
11740	var cb = blob.read_shift(2) - 4;
11741	var o = [extType];
11742	switch(extType) {
11743		case 0x04: case 0x05: case 0x07: case 0x08:
11744		case 0x09: case 0x0A: case 0x0B: case 0x0D:
11745			o[1] = parse_FullColorExt(blob, cb); break;
11746		case 0x06: o[1] = parse_XFExtGradient(blob, cb); break;
11747		case 0x0E: case 0x0F: o[1] = blob.read_shift(cb === 1 ? 1 : 2); break;
11748		default: throw new Error("Unrecognized ExtProp type: " + extType + " " + cb);
11749	}
11750	return o;
11751}
11752
11753/* 2.4.355 */
11754function parse_XFExt(blob, length) {
11755	var end = blob.l + length;
11756	blob.l += 2;
11757	var ixfe = blob.read_shift(2);
11758	blob.l += 2;
11759	var cexts = blob.read_shift(2);
11760	var ext/*:AOA*/ = [];
11761	while(cexts-- > 0) ext.push(parse_ExtProp(blob, end-blob.l));
11762	return {ixfe:ixfe, ext:ext};
11763}
11764
11765/* xf is an XF, see parse_XFExt for xfext */
11766function update_xfext(xf, xfext) {
11767	xfext.forEach(function(xfe) {
11768		switch(xfe[0]) { /* 2.5.108 extPropData */
11769			case 0x04: break; /* foreground color */
11770			case 0x05: break; /* background color */
11771			case 0x06: break; /* gradient fill */
11772			case 0x07: break; /* top cell border color */
11773			case 0x08: break; /* bottom cell border color */
11774			case 0x09: break; /* left cell border color */
11775			case 0x0a: break; /* right cell border color */
11776			case 0x0b: break; /* diagonal cell border color */
11777			case 0x0d: /* text color */
11778				break;
11779			case 0x0e: break; /* font scheme */
11780			case 0x0f: break; /* indentation level */
11781		}
11782	});
11783}
11784
11785function parse_BrtMdtinfo(data, length) {
11786  return {
11787    flags: data.read_shift(4),
11788    version: data.read_shift(4),
11789    name: parse_XLWideString(data, length - 8)
11790  };
11791}
11792function write_BrtMdtinfo(data) {
11793  var o = new_buf(12 + 2 * data.name.length);
11794  o.write_shift(4, data.flags);
11795  o.write_shift(4, data.version);
11796  write_XLWideString(data.name, o);
11797  return o.slice(0, o.l);
11798}
11799function parse_BrtMdb(data) {
11800  var out = [];
11801  var cnt = data.read_shift(4);
11802  while (cnt-- > 0)
11803    out.push([data.read_shift(4), data.read_shift(4)]);
11804  return out;
11805}
11806function write_BrtMdb(mdb) {
11807  var o = new_buf(4 + 8 * mdb.length);
11808  o.write_shift(4, mdb.length);
11809  for (var i = 0; i < mdb.length; ++i) {
11810    o.write_shift(4, mdb[i][0]);
11811    o.write_shift(4, mdb[i][1]);
11812  }
11813  return o;
11814}
11815function write_BrtBeginEsfmd(cnt, name) {
11816  var o = new_buf(8 + 2 * name.length);
11817  o.write_shift(4, cnt);
11818  write_XLWideString(name, o);
11819  return o.slice(0, o.l);
11820}
11821function parse_BrtBeginEsmdb(data) {
11822  data.l += 4;
11823  return data.read_shift(4) != 0;
11824}
11825function write_BrtBeginEsmdb(cnt, cm) {
11826  var o = new_buf(8);
11827  o.write_shift(4, cnt);
11828  o.write_shift(4, cm ? 1 : 0);
11829  return o;
11830}
11831function parse_xlmeta_bin(data, name, _opts) {
11832  var out = { Types: [], Cell: [], Value: [] };
11833  var opts = _opts || {};
11834  var state = [];
11835  var pass = false;
11836  var metatype = 2;
11837  recordhopper(data, function(val, R, RT) {
11838    switch (RT) {
11839      case 335:
11840        out.Types.push({ name: val.name });
11841        break;
11842      case 51:
11843        val.forEach(function(r) {
11844          if (metatype == 1)
11845            out.Cell.push({ type: out.Types[r[0] - 1].name, index: r[1] });
11846          else if (metatype == 0)
11847            out.Value.push({ type: out.Types[r[0] - 1].name, index: r[1] });
11848        });
11849        break;
11850      case 337:
11851        metatype = val ? 1 : 0;
11852        break;
11853      case 338:
11854        metatype = 2;
11855        break;
11856      case 35:
11857        state.push(RT);
11858        pass = true;
11859        break;
11860      case 36:
11861        state.pop();
11862        pass = false;
11863        break;
11864      default:
11865        if (R.T) {
11866        } else if (!pass || opts.WTF && state[state.length - 1] != 35)
11867          throw new Error("Unexpected record 0x" + RT.toString(16));
11868    }
11869  });
11870  return out;
11871}
11872function write_xlmeta_bin() {
11873  var ba = buf_array();
11874  write_record(ba, 332);
11875  write_record(ba, 334, write_UInt32LE(1));
11876  write_record(ba, 335, write_BrtMdtinfo({
11877    name: "XLDAPR",
11878    version: 12e4,
11879    flags: 3496657072
11880  }));
11881  write_record(ba, 336);
11882  write_record(ba, 339, write_BrtBeginEsfmd(1, "XLDAPR"));
11883  write_record(ba, 52);
11884  write_record(ba, 35, write_UInt32LE(514));
11885  write_record(ba, 4096, write_UInt32LE(0));
11886  write_record(ba, 4097, writeuint16(1));
11887  write_record(ba, 36);
11888  write_record(ba, 53);
11889  write_record(ba, 340);
11890  write_record(ba, 337, write_BrtBeginEsmdb(1, true));
11891  write_record(ba, 51, write_BrtMdb([[1, 0]]));
11892  write_record(ba, 338);
11893  write_record(ba, 333);
11894  return ba.end();
11895}
11896function parse_xlmeta_xml(data, name, opts) {
11897  var out = { Types: [], Cell: [], Value: [] };
11898  if (!data)
11899    return out;
11900  var pass = false;
11901  var metatype = 2;
11902  var lastmeta;
11903  data.replace(tagregex, function(x) {
11904    var y = parsexmltag(x);
11905    switch (strip_ns(y[0])) {
11906      case "<?xml":
11907        break;
11908      case "<metadata":
11909      case "</metadata>":
11910        break;
11911      case "<metadataTypes":
11912      case "</metadataTypes>":
11913        break;
11914      case "<metadataType":
11915        out.Types.push({ name: y.name });
11916        break;
11917      case "</metadataType>":
11918        break;
11919      case "<futureMetadata":
11920        for (var j = 0; j < out.Types.length; ++j)
11921          if (out.Types[j].name == y.name)
11922            lastmeta = out.Types[j];
11923        break;
11924      case "</futureMetadata>":
11925        break;
11926      case "<bk>":
11927        break;
11928      case "</bk>":
11929        break;
11930      case "<rc":
11931        if (metatype == 1)
11932          out.Cell.push({ type: out.Types[y.t - 1].name, index: +y.v });
11933        else if (metatype == 0)
11934          out.Value.push({ type: out.Types[y.t - 1].name, index: +y.v });
11935        break;
11936      case "</rc>":
11937        break;
11938      case "<cellMetadata":
11939        metatype = 1;
11940        break;
11941      case "</cellMetadata>":
11942        metatype = 2;
11943        break;
11944      case "<valueMetadata":
11945        metatype = 0;
11946        break;
11947      case "</valueMetadata>":
11948        metatype = 2;
11949        break;
11950      case "<extLst":
11951      case "<extLst>":
11952      case "</extLst>":
11953      case "<extLst/>":
11954        break;
11955      case "<ext":
11956        pass = true;
11957        break;
11958      case "</ext>":
11959        pass = false;
11960        break;
11961      case "<rvb":
11962        if (!lastmeta)
11963          break;
11964        if (!lastmeta.offsets)
11965          lastmeta.offsets = [];
11966        lastmeta.offsets.push(+y.i);
11967        break;
11968      default:
11969        if (!pass && (opts == null ? void 0 : opts.WTF))
11970          throw new Error("unrecognized " + y[0] + " in metadata");
11971    }
11972    return x;
11973  });
11974  return out;
11975}
11976function write_xlmeta_xml() {
11977  var o = [XML_HEADER];
11978  o.push('<metadata xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xlrd="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" xmlns:xda="http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray">\n  <metadataTypes count="1">\n    <metadataType name="XLDAPR" minSupportedVersion="120000" copy="1" pasteAll="1" pasteValues="1" merge="1" splitFirst="1" rowColShift="1" clearFormats="1" clearComments="1" assign="1" coerce="1" cellMeta="1"/>\n  </metadataTypes>\n  <futureMetadata name="XLDAPR" count="1">\n    <bk>\n      <extLst>\n        <ext uri="{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}">\n          <xda:dynamicArrayProperties fDynamic="1" fCollapsed="0"/>\n        </ext>\n      </extLst>\n    </bk>\n  </futureMetadata>\n  <cellMetadata count="1">\n    <bk>\n      <rc t="1" v="0"/>\n    </bk>\n  </cellMetadata>\n</metadata>');
11979  return o.join("");
11980}
11981/* 18.6 Calculation Chain */
11982function parse_cc_xml(data/*::, name, opts*/)/*:Array<any>*/ {
11983	var d = [];
11984	if(!data) return d;
11985	var i = 1;
11986	(data.match(tagregex)||[]).forEach(function(x) {
11987		var y = parsexmltag(x);
11988		switch(y[0]) {
11989			case '<?xml': break;
11990			/* 18.6.2  calcChain CT_CalcChain 1 */
11991			case '<calcChain': case '<calcChain>': case '</calcChain>': break;
11992			/* 18.6.1  c CT_CalcCell 1 */
11993			case '<c': delete y[0]; if(y.i) i = y.i; else y.i = i; d.push(y); break;
11994		}
11995	});
11996	return d;
11997}
11998
11999//function write_cc_xml(data, opts) { }
12000
12001/* [MS-XLSB] 2.6.4.1 */
12002function parse_BrtCalcChainItem$(data) {
12003	var out = {};
12004	out.i = data.read_shift(4);
12005	var cell = {};
12006	cell.r = data.read_shift(4);
12007	cell.c = data.read_shift(4);
12008	out.r = encode_cell(cell);
12009	var flags = data.read_shift(1);
12010	if(flags & 0x2) out.l = '1';
12011	if(flags & 0x8) out.a = '1';
12012	return out;
12013}
12014
12015/* 18.6 Calculation Chain */
12016function parse_cc_bin(data, name, opts) {
12017	var out = [];
12018	var pass = false;
12019	recordhopper(data, function hopper_cc(val, R, RT) {
12020		switch(RT) {
12021			case 0x003F: /* 'BrtCalcChainItem$' */
12022				out.push(val); break;
12023
12024			default:
12025				if(R.T){/* empty */}
12026				else if(!pass || opts.WTF) throw new Error("Unexpected record 0x" + RT.toString(16));
12027		}
12028	});
12029	return out;
12030}
12031
12032//function write_cc_bin(data, opts) { }
12033/* 18.14 Supplementary Workbook Data */
12034function parse_xlink_xml(/*::data, rel, name:string, _opts*/) {
12035	//var opts = _opts || {};
12036	//if(opts.WTF) throw "XLSX External Link";
12037}
12038
12039/* [MS-XLSB] 2.1.7.25 External Link */
12040function parse_xlink_bin(data, rel, name/*:string*/, _opts) {
12041	if(!data) return data;
12042	var opts = _opts || {};
12043
12044	var pass = false, end = false;
12045
12046	recordhopper(data, function xlink_parse(val, R, RT) {
12047		if(end) return;
12048		switch(RT) {
12049			case 0x0167: /* 'BrtSupTabs' */
12050			case 0x016B: /* 'BrtExternTableStart' */
12051			case 0x016C: /* 'BrtExternTableEnd' */
12052			case 0x016E: /* 'BrtExternRowHdr' */
12053			case 0x016F: /* 'BrtExternCellBlank' */
12054			case 0x0170: /* 'BrtExternCellReal' */
12055			case 0x0171: /* 'BrtExternCellBool' */
12056			case 0x0172: /* 'BrtExternCellError' */
12057			case 0x0173: /* 'BrtExternCellString' */
12058			case 0x01D8: /* 'BrtExternValueMeta' */
12059			case 0x0241: /* 'BrtSupNameStart' */
12060			case 0x0242: /* 'BrtSupNameValueStart' */
12061			case 0x0243: /* 'BrtSupNameValueEnd' */
12062			case 0x0244: /* 'BrtSupNameNum' */
12063			case 0x0245: /* 'BrtSupNameErr' */
12064			case 0x0246: /* 'BrtSupNameSt' */
12065			case 0x0247: /* 'BrtSupNameNil' */
12066			case 0x0248: /* 'BrtSupNameBool' */
12067			case 0x0249: /* 'BrtSupNameFmla' */
12068			case 0x024A: /* 'BrtSupNameBits' */
12069			case 0x024B: /* 'BrtSupNameEnd' */
12070				break;
12071
12072			case 0x0023: /* 'BrtFRTBegin' */
12073				pass = true; break;
12074			case 0x0024: /* 'BrtFRTEnd' */
12075				pass = false; break;
12076
12077			default:
12078				if(R.T){/* empty */}
12079				else if(!pass || opts.WTF) throw new Error("Unexpected record 0x" + RT.toString(16));
12080		}
12081	}, opts);
12082}
12083/* 20.5 DrawingML - SpreadsheetML Drawing */
12084/* 20.5.2.35 wsDr CT_Drawing */
12085function parse_drawing(data, rels/*:any*/) {
12086	if(!data) return "??";
12087	/*
12088	  Chartsheet Drawing:
12089	   - 20.5.2.35 wsDr CT_Drawing
12090	    - 20.5.2.1  absoluteAnchor CT_AbsoluteAnchor
12091	     - 20.5.2.16 graphicFrame CT_GraphicalObjectFrame
12092	      - 20.1.2.2.16 graphic CT_GraphicalObject
12093	       - 20.1.2.2.17 graphicData CT_GraphicalObjectData
12094          - chart reference
12095	   the actual type is based on the URI of the graphicData
12096		TODO: handle embedded charts and other types of graphics
12097	*/
12098	var id = (data.match(/<c:chart [^>]*r:id="([^"]*)"/)||["",""])[1];
12099
12100	return rels['!id'][id].Target;
12101}
12102
12103/* L.5.5.2 SpreadsheetML Comments + VML Schema */
12104function write_vml(rId/*:number*/, comments) {
12105	var csize = [21600, 21600];
12106	/* L.5.2.1.2 Path Attribute */
12107	var bbox = ["m0,0l0",csize[1],csize[0],csize[1],csize[0],"0xe"].join(",");
12108	var o = [
12109		writextag("xml", null, { 'xmlns:v': XLMLNS.v, 'xmlns:o': XLMLNS.o, 'xmlns:x': XLMLNS.x, 'xmlns:mv': XLMLNS.mv }).replace(/\/>/,">"),
12110		writextag("o:shapelayout", writextag("o:idmap", null, {'v:ext':"edit", 'data':rId}), {'v:ext':"edit"})
12111	];
12112
12113	var _shapeid = 65536 * rId;
12114
12115	var _comments = comments || [];
12116	if(_comments.length > 0) o.push(writextag("v:shapetype", [
12117		writextag("v:stroke", null, {joinstyle:"miter"}),
12118		writextag("v:path", null, {gradientshapeok:"t", 'o:connecttype':"rect"})
12119	].join(""), {id:"_x0000_t202", coordsize:csize.join(","), 'o:spt':202, path:bbox}));
12120
12121	_comments.forEach(function(x) { ++_shapeid; o.push(write_vml_comment(x, _shapeid)); });
12122	o.push('</xml>');
12123	return o.join("");
12124}
12125
12126function write_vml_comment(x, _shapeid)/*:string*/ {
12127	var c = decode_cell(x[0]);
12128	var fillopts = /*::(*/{'color2':"#BEFF82", 'type':"gradient"}/*:: :any)*/;
12129	if(fillopts.type == "gradient") fillopts.angle = "-180";
12130	var fillparm = fillopts.type == "gradient" ? writextag("o:fill", null, {type:"gradientUnscaled", 'v:ext':"view"}) : null;
12131	var fillxml = writextag('v:fill', fillparm, fillopts);
12132
12133	var shadata = ({on:"t", 'obscured':"t"}/*:any*/);
12134
12135	return [
12136	'<v:shape' + wxt_helper({
12137		id:'_x0000_s' + _shapeid,
12138		type:"#_x0000_t202",
12139		style:"position:absolute; margin-left:80pt;margin-top:5pt;width:104pt;height:64pt;z-index:10" + (x[1].hidden ? ";visibility:hidden" : "") ,
12140		fillcolor:"#ECFAD4",
12141		strokecolor:"#edeaa1"
12142	}) + '>',
12143		fillxml,
12144		writextag("v:shadow", null, shadata),
12145		writextag("v:path", null, {'o:connecttype':"none"}),
12146		'<v:textbox><div style="text-align:left"></div></v:textbox>',
12147		'<x:ClientData ObjectType="Note">',
12148			'<x:MoveWithCells/>',
12149			'<x:SizeWithCells/>',
12150			/* Part 4 19.4.2.3 Anchor (Anchor) */
12151			writetag('x:Anchor', [c.c+1, 0, c.r+1, 0, c.c+3, 20, c.r+5, 20].join(",")),
12152			writetag('x:AutoFill', "False"),
12153			writetag('x:Row', String(c.r)),
12154			writetag('x:Column', String(c.c)),
12155			x[1].hidden ? '' : '<x:Visible/>',
12156		'</x:ClientData>',
12157	'</v:shape>'
12158	].join("");
12159}
12160function sheet_insert_comments(sheet, comments/*:Array<RawComment>*/, threaded/*:boolean*/, people/*:?Array<any>*/) {
12161	var dense = Array.isArray(sheet);
12162	var cell/*:Cell*/;
12163	comments.forEach(function(comment) {
12164		var r = decode_cell(comment.ref);
12165		if(dense) {
12166			if(!sheet[r.r]) sheet[r.r] = [];
12167			cell = sheet[r.r][r.c];
12168		} else cell = sheet[comment.ref];
12169		if (!cell) {
12170			cell = ({t:"z"}/*:any*/);
12171			if(dense) sheet[r.r][r.c] = cell;
12172			else sheet[comment.ref] = cell;
12173			var range = safe_decode_range(sheet["!ref"]||"BDWGO1000001:A1");
12174			if(range.s.r > r.r) range.s.r = r.r;
12175			if(range.e.r < r.r) range.e.r = r.r;
12176			if(range.s.c > r.c) range.s.c = r.c;
12177			if(range.e.c < r.c) range.e.c = r.c;
12178			var encoded = encode_range(range);
12179			if (encoded !== sheet["!ref"]) sheet["!ref"] = encoded;
12180		}
12181
12182		if (!cell.c) cell.c = [];
12183		var o/*:Comment*/ = ({a: comment.author, t: comment.t, r: comment.r, T: threaded});
12184		if(comment.h) o.h = comment.h;
12185
12186		/* threaded comments always override */
12187		for(var i = cell.c.length - 1; i >= 0; --i) {
12188			if(!threaded && cell.c[i].T) return;
12189			if(threaded && !cell.c[i].T) cell.c.splice(i, 1);
12190		}
12191		if(threaded && people) for(i = 0; i < people.length; ++i) {
12192			if(o.a == people[i].id) { o.a = people[i].name || o.a; break; }
12193		}
12194		cell.c.push(o);
12195	});
12196}
12197
12198/* 18.7 Comments */
12199function parse_comments_xml(data/*:string*/, opts)/*:Array<RawComment>*/ {
12200	/* 18.7.6 CT_Comments */
12201	if(data.match(/<(?:\w+:)?comments *\/>/)) return [];
12202	var authors/*:Array<string>*/ = [];
12203	var commentList/*:Array<RawComment>*/ = [];
12204	var authtag = data.match(/<(?:\w+:)?authors>([\s\S]*)<\/(?:\w+:)?authors>/);
12205	if(authtag && authtag[1]) authtag[1].split(/<\/\w*:?author>/).forEach(function(x) {
12206		if(x === "" || x.trim() === "") return;
12207		var a = x.match(/<(?:\w+:)?author[^>]*>(.*)/);
12208		if(a) authors.push(a[1]);
12209	});
12210	var cmnttag = data.match(/<(?:\w+:)?commentList>([\s\S]*)<\/(?:\w+:)?commentList>/);
12211	if(cmnttag && cmnttag[1]) cmnttag[1].split(/<\/\w*:?comment>/).forEach(function(x) {
12212		if(x === "" || x.trim() === "") return;
12213		var cm = x.match(/<(?:\w+:)?comment[^>]*>/);
12214		if(!cm) return;
12215		var y = parsexmltag(cm[0]);
12216		var comment/*:RawComment*/ = ({ author: y.authorId && authors[y.authorId] || "sheetjsghost", ref: y.ref, guid: y.guid }/*:any*/);
12217		var cell = decode_cell(y.ref);
12218		if(opts.sheetRows && opts.sheetRows <= cell.r) return;
12219		var textMatch = x.match(/<(?:\w+:)?text>([\s\S]*)<\/(?:\w+:)?text>/);
12220		var rt = !!textMatch && !!textMatch[1] && parse_si(textMatch[1]) || {r:"",t:"",h:""};
12221		comment.r = rt.r;
12222		if(rt.r == "<t></t>") rt.t = rt.h = "";
12223		comment.t = (rt.t||"").replace(/\r\n/g,"\n").replace(/\r/g,"\n");
12224		if(opts.cellHTML) comment.h = rt.h;
12225		commentList.push(comment);
12226	});
12227	return commentList;
12228}
12229
12230function write_comments_xml(data/*::, opts*/) {
12231	var o = [XML_HEADER, writextag('comments', null, { 'xmlns': XMLNS_main[0] })];
12232
12233	var iauthor/*:Array<string>*/ = [];
12234	o.push("<authors>");
12235	data.forEach(function(x) { x[1].forEach(function(w) { var a = escapexml(w.a);
12236		if(iauthor.indexOf(a) == -1) {
12237			iauthor.push(a);
12238			o.push("<author>" + a + "</author>");
12239		}
12240		if(w.T && w.ID && iauthor.indexOf("tc=" + w.ID) == -1) {
12241			iauthor.push("tc=" + w.ID);
12242			o.push("<author>" + "tc=" + w.ID + "</author>");
12243		}
12244	}); });
12245	if(iauthor.length == 0) { iauthor.push("SheetJ5"); o.push("<author>SheetJ5</author>"); }
12246	o.push("</authors>");
12247	o.push("<commentList>");
12248	data.forEach(function(d) {
12249		/* 18.7.3 CT_Comment */
12250		var lastauthor = 0, ts = [], tcnt = 0;
12251		if(d[1][0] && d[1][0].T && d[1][0].ID) lastauthor = iauthor.indexOf("tc=" + d[1][0].ID);
12252		d[1].forEach(function(c) {
12253			if(c.a) lastauthor = iauthor.indexOf(escapexml(c.a));
12254			if(c.T) ++tcnt;
12255			ts.push(c.t == null ? "" : escapexml(c.t));
12256		});
12257		if(tcnt === 0) {
12258			d[1].forEach(function(c) {
12259				o.push('<comment ref="' + d[0] + '" authorId="' + iauthor.indexOf(escapexml(c.a)) + '"><text>');
12260				o.push(writetag("t", c.t == null ? "" : escapexml(c.t)));
12261				o.push('</text></comment>');
12262			});
12263		} else {
12264			/* based on Threaded Comments -> Comments projection */
12265			o.push('<comment ref="' + d[0] + '" authorId="' + lastauthor + '"><text>');
12266			var t = "Comment:\n    " + (ts[0]) + "\n";
12267			for(var i = 1; i < ts.length; ++i) t += "Reply:\n    " + ts[i] + "\n";
12268			o.push(writetag("t", escapexml(t)));
12269			o.push('</text></comment>');
12270		}
12271	});
12272	o.push("</commentList>");
12273	if(o.length>2) { o[o.length] = ('</comments>'); o[1]=o[1].replace("/>",">"); }
12274	return o.join("");
12275}
12276
12277/* [MS-XLSX] 2.1.17 */
12278function parse_tcmnt_xml(data/*:string*/, opts)/*:Array<RawComment>*/ {
12279	var out = [];
12280	var pass = false, comment = {}, tidx = 0;
12281	data.replace(tagregex, function xml_tcmnt(x, idx) {
12282		var y/*:any*/ = parsexmltag(x);
12283		switch(strip_ns(y[0])) {
12284			case '<?xml': break;
12285
12286			/* 2.6.207 ThreadedComments CT_ThreadedComments */
12287			case '<ThreadedComments': break;
12288			case '</ThreadedComments>': break;
12289
12290			/* 2.6.205 threadedComment CT_ThreadedComment */
12291			case '<threadedComment': comment = {author: y.personId, guid: y.id, ref: y.ref, T: 1}; break;
12292			case '</threadedComment>': if(comment.t != null) out.push(comment); break;
12293
12294			case '<text>': case '<text': tidx = idx + x.length; break;
12295			case '</text>': comment.t = data.slice(tidx, idx).replace(/\r\n/g, "\n").replace(/\r/g, "\n"); break;
12296
12297			/* 2.6.206 mentions CT_ThreadedCommentMentions TODO */
12298			case '<mentions': case '<mentions>': pass = true; break;
12299			case '</mentions>': pass = false; break;
12300
12301			/* 2.6.202 mention CT_Mention TODO */
12302
12303			/* 18.2.10 extLst CT_ExtensionList ? */
12304			case '<extLst': case '<extLst>': case '</extLst>': case '<extLst/>': break;
12305			/* 18.2.7  ext CT_Extension + */
12306			case '<ext': pass=true; break;
12307			case '</ext>': pass=false; break;
12308
12309			default: if(!pass && opts.WTF) throw new Error('unrecognized ' + y[0] + ' in threaded comments');
12310		}
12311		return x;
12312	});
12313	return out;
12314}
12315
12316function write_tcmnt_xml(comments, people, opts) {
12317	var o = [XML_HEADER, writextag('ThreadedComments', null, { 'xmlns': XMLNS.TCMNT }).replace(/[\/]>/, ">")];
12318	comments.forEach(function(carr) {
12319		var rootid = "";
12320		(carr[1] || []).forEach(function(c, idx) {
12321			if(!c.T) { delete c.ID; return; }
12322			if(c.a && people.indexOf(c.a) == -1) people.push(c.a);
12323			var tcopts = {
12324				ref: carr[0],
12325				id: "{54EE7951-7262-4200-6969-" + ("000000000000" + opts.tcid++).slice(-12) + "}"
12326			};
12327			if(idx == 0) rootid = tcopts.id;
12328			else tcopts.parentId = rootid;
12329			c.ID = tcopts.id;
12330			if(c.a) tcopts.personId = "{54EE7950-7262-4200-6969-" + ("000000000000" + people.indexOf(c.a)).slice(-12) + "}";
12331			o.push(writextag('threadedComment', writetag('text', c.t||""), tcopts));
12332		});
12333	});
12334	o.push('</ThreadedComments>');
12335	return o.join("");
12336}
12337
12338/* [MS-XLSX] 2.1.18 */
12339function parse_people_xml(data/*:string*/, opts) {
12340	var out = [];
12341	var pass = false;
12342	data.replace(tagregex, function xml_tcmnt(x) {
12343		var y/*:any*/ = parsexmltag(x);
12344		switch(strip_ns(y[0])) {
12345			case '<?xml': break;
12346
12347			/* 2.4.85 personList CT_PersonList */
12348			case '<personList': break;
12349			case '</personList>': break;
12350
12351			/* 2.6.203 person CT_Person TODO: providers */
12352			case '<person': out.push({name: y.displayname, id: y.id }); break;
12353			case '</person>': break;
12354
12355			/* 18.2.10 extLst CT_ExtensionList ? */
12356			case '<extLst': case '<extLst>': case '</extLst>': case '<extLst/>': break;
12357			/* 18.2.7  ext CT_Extension + */
12358			case '<ext': pass=true; break;
12359			case '</ext>': pass=false; break;
12360
12361			default: if(!pass && opts.WTF) throw new Error('unrecognized ' + y[0] + ' in threaded comments');
12362		}
12363		return x;
12364	});
12365	return out;
12366}
12367function write_people_xml(people/*, opts*/) {
12368	var o = [XML_HEADER, writextag('personList', null, {
12369		'xmlns': XMLNS.TCMNT,
12370		'xmlns:x': XMLNS_main[0]
12371	}).replace(/[\/]>/, ">")];
12372	people.forEach(function(person, idx) {
12373		o.push(writextag('person', null, {
12374			displayName: person,
12375			id: "{54EE7950-7262-4200-6969-" + ("000000000000" + idx).slice(-12) + "}",
12376			userId: person,
12377			providerId: "None"
12378		}));
12379	});
12380	o.push("</personList>");
12381	return o.join("");
12382}
12383/* [MS-XLSB] 2.4.28 BrtBeginComment */
12384function parse_BrtBeginComment(data) {
12385	var out = {};
12386	out.iauthor = data.read_shift(4);
12387	var rfx = parse_UncheckedRfX(data, 16);
12388	out.rfx = rfx.s;
12389	out.ref = encode_cell(rfx.s);
12390	data.l += 16; /*var guid = parse_GUID(data); */
12391	return out;
12392}
12393function write_BrtBeginComment(data, o) {
12394	if(o == null) o = new_buf(36);
12395	o.write_shift(4, data[1].iauthor);
12396	write_UncheckedRfX((data[0]/*:any*/), o);
12397	o.write_shift(4, 0);
12398	o.write_shift(4, 0);
12399	o.write_shift(4, 0);
12400	o.write_shift(4, 0);
12401	return o;
12402}
12403
12404/* [MS-XLSB] 2.4.327 BrtCommentAuthor */
12405var parse_BrtCommentAuthor = parse_XLWideString;
12406function write_BrtCommentAuthor(data) { return write_XLWideString(data.slice(0, 54)); }
12407
12408/* [MS-XLSB] 2.1.7.8 Comments */
12409function parse_comments_bin(data, opts)/*:Array<RawComment>*/ {
12410	var out/*:Array<RawComment>*/ = [];
12411	var authors/*:Array<string>*/ = [];
12412	var c = {};
12413	var pass = false;
12414	recordhopper(data, function hopper_cmnt(val, R, RT) {
12415		switch(RT) {
12416			case 0x0278: /* 'BrtCommentAuthor' */
12417				authors.push(val); break;
12418			case 0x027B: /* 'BrtBeginComment' */
12419				c = val; break;
12420			case 0x027D: /* 'BrtCommentText' */
12421				c.t = val.t; c.h = val.h; c.r = val.r; break;
12422			case 0x027C: /* 'BrtEndComment' */
12423				c.author = authors[c.iauthor];
12424				delete (c/*:any*/).iauthor;
12425				if(opts.sheetRows && c.rfx && opts.sheetRows <= c.rfx.r) break;
12426				if(!c.t) c.t = "";
12427				delete c.rfx; out.push(c); break;
12428
12429			case 0x0C00: /* 'BrtUid' */
12430				break;
12431
12432			case 0x0023: /* 'BrtFRTBegin' */
12433				pass = true; break;
12434			case 0x0024: /* 'BrtFRTEnd' */
12435				pass = false; break;
12436			case 0x0025: /* 'BrtACBegin' */ break;
12437			case 0x0026: /* 'BrtACEnd' */ break;
12438
12439
12440			default:
12441				if(R.T){/* empty */}
12442				else if(!pass || opts.WTF) throw new Error("Unexpected record 0x" + RT.toString(16));
12443		}
12444	});
12445	return out;
12446}
12447
12448function write_comments_bin(data/*::, opts*/) {
12449	var ba = buf_array();
12450	var iauthor/*:Array<string>*/ = [];
12451	write_record(ba, 0x0274 /* BrtBeginComments */);
12452
12453	write_record(ba, 0x0276 /* BrtBeginCommentAuthors */);
12454	data.forEach(function(comment) {
12455		comment[1].forEach(function(c) {
12456			if(iauthor.indexOf(c.a) > -1) return;
12457			iauthor.push(c.a.slice(0,54));
12458			write_record(ba, 0x0278 /* BrtCommentAuthor */, write_BrtCommentAuthor(c.a));
12459		});
12460	});
12461	write_record(ba, 0x0277 /* BrtEndCommentAuthors */);
12462
12463	write_record(ba, 0x0279 /* BrtBeginCommentList */);
12464	data.forEach(function(comment) {
12465		comment[1].forEach(function(c) {
12466			c.iauthor = iauthor.indexOf(c.a);
12467			var range = {s:decode_cell(comment[0]),e:decode_cell(comment[0])};
12468			write_record(ba, 0x027B /* BrtBeginComment */, write_BrtBeginComment([range, c]));
12469			if(c.t && c.t.length > 0) write_record(ba, 0x027D /* BrtCommentText */, write_BrtCommentText(c));
12470			write_record(ba, 0x027C /* BrtEndComment */);
12471			delete c.iauthor;
12472		});
12473	});
12474	write_record(ba, 0x027A /* BrtEndCommentList */);
12475
12476	write_record(ba, 0x0275 /* BrtEndComments */);
12477	return ba.end();
12478}
12479var CT_VBA = "application/vnd.ms-office.vbaProject";
12480function make_vba_xls(cfb) {
12481  var newcfb = CFB.utils.cfb_new({ root: "R" });
12482  cfb.FullPaths.forEach(function(p, i) {
12483    if (p.slice(-1) === "/" || !p.match(/_VBA_PROJECT_CUR/))
12484      return;
12485    var newpath = p.replace(/^[^\/]*/, "R").replace(/\/_VBA_PROJECT_CUR\u0000*/, "");
12486    CFB.utils.cfb_add(newcfb, newpath, cfb.FileIndex[i].content);
12487  });
12488  return CFB.write(newcfb);
12489}
12490function fill_vba_xls(cfb, vba) {
12491  vba.FullPaths.forEach(function(p, i) {
12492    if (i == 0)
12493      return;
12494    var newpath = p.replace(/[^\/]*[\/]/, "/_VBA_PROJECT_CUR/");
12495    if (newpath.slice(-1) !== "/")
12496      CFB.utils.cfb_add(cfb, newpath, vba.FileIndex[i].content);
12497  });
12498}
12499var VBAFMTS = ["xlsb", "xlsm", "xlam", "biff8", "xla"];
12500/* macro and dialog sheet stubs */
12501function parse_ds_bin(/*::data:any, opts, idx:number, rels, wb, themes, styles*/)/*:Worksheet*/ { return {'!type':'dialog'}; }
12502function parse_ds_xml(/*::data:any, opts, idx:number, rels, wb, themes, styles*/)/*:Worksheet*/ { return {'!type':'dialog'}; }
12503function parse_ms_bin(/*::data:any, opts, idx:number, rels, wb, themes, styles*/)/*:Worksheet*/ { return {'!type':'macro'}; }
12504function parse_ms_xml(/*::data:any, opts, idx:number, rels, wb, themes, styles*/)/*:Worksheet*/ { return {'!type':'macro'}; }
12505/* TODO: it will be useful to parse the function str */
12506var rc_to_a1 = /*#__PURE__*/(function(){
12507	var rcregex = /(^|[^A-Za-z_])R(\[?-?\d+\]|[1-9]\d*|)C(\[?-?\d+\]|[1-9]\d*|)(?![A-Za-z0-9_])/g;
12508	var rcbase/*:Cell*/ = ({r:0,c:0}/*:any*/);
12509	function rcfunc($$,$1,$2,$3) {
12510		var cRel = false, rRel = false;
12511
12512		if($2.length == 0) rRel = true;
12513		else if($2.charAt(0) == "[") { rRel = true; $2 = $2.slice(1, -1); }
12514
12515		if($3.length == 0) cRel = true;
12516		else if($3.charAt(0) == "[") { cRel = true; $3 = $3.slice(1, -1); }
12517
12518		var R = $2.length>0?parseInt($2,10)|0:0, C = $3.length>0?parseInt($3,10)|0:0;
12519
12520		if(cRel) C += rcbase.c; else --C;
12521		if(rRel) R += rcbase.r; else --R;
12522		return $1 + (cRel ? "" : "$") + encode_col(C) + (rRel ? "" : "$") + encode_row(R);
12523	}
12524	return function rc_to_a1(fstr/*:string*/, base/*:Cell*/)/*:string*/ {
12525		rcbase = base;
12526		return fstr.replace(rcregex, rcfunc);
12527	};
12528})();
12529
12530var crefregex = /(^|[^._A-Z0-9])([$]?)([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])([$]?)(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})(?![_.\(A-Za-z0-9])/g;
12531var a1_to_rc = /*#__PURE__*/(function(){
12532	return function a1_to_rc(fstr/*:string*/, base/*:CellAddress*/) {
12533		return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5) {
12534			var c = decode_col($3) - ($2 ? 0 : base.c);
12535			var r = decode_row($5) - ($4 ? 0 : base.r);
12536			var R = $4 == "$" ? (r+1) : (r == 0 ? "" : "[" + r + "]");
12537			var C = $2 == "$" ? (c+1) : (c == 0 ? "" : "[" + c + "]");
12538			return $1 + "R" + R + "C" + C;
12539		});
12540	};
12541})();
12542
12543/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */
12544function shift_formula_str(f/*:string*/, delta/*:Cell*/)/*:string*/ {
12545	return f.replace(crefregex, function($0, $1, $2, $3, $4, $5) {
12546		return $1+($2=="$" ? $2+$3 : encode_col(decode_col($3)+delta.c))+($4=="$" ? $4+$5 : encode_row(decode_row($5) + delta.r));
12547	});
12548}
12549
12550function shift_formula_xlsx(f/*:string*/, range/*:string*/, cell/*:string*/)/*:string*/ {
12551	var r = decode_range(range), s = r.s, c = decode_cell(cell);
12552	var delta = {r:c.r - s.r, c:c.c - s.c};
12553	return shift_formula_str(f, delta);
12554}
12555
12556/* TODO: parse formula */
12557function fuzzyfmla(f/*:string*/)/*:boolean*/ {
12558	if(f.length == 1) return false;
12559	return true;
12560}
12561
12562function _xlfn(f/*:string*/)/*:string*/ {
12563	return f.replace(/_xlfn\./g,"");
12564}
12565function parseread1(blob) { blob.l+=1; return; }
12566
12567/* [MS-XLS] 2.5.51 */
12568function parse_ColRelU(blob, length) {
12569	var c = blob.read_shift(length == 1 ? 1 : 2);
12570	return [c & 0x3FFF, (c >> 14) & 1, (c >> 15) & 1];
12571}
12572
12573/* [MS-XLS] 2.5.198.105 ; [MS-XLSB] 2.5.97.89 */
12574function parse_RgceArea(blob, length, opts) {
12575	var w = 2;
12576	if(opts) {
12577		if(opts.biff >= 2 && opts.biff <= 5) return parse_RgceArea_BIFF2(blob, length, opts);
12578		else if(opts.biff == 12) w = 4;
12579	}
12580	var r=blob.read_shift(w), R=blob.read_shift(w);
12581	var c=parse_ColRelU(blob, 2);
12582	var C=parse_ColRelU(blob, 2);
12583	return { s:{r:r, c:c[0], cRel:c[1], rRel:c[2]}, e:{r:R, c:C[0], cRel:C[1], rRel:C[2]} };
12584}
12585/* BIFF 2-5 encodes flags in the row field */
12586function parse_RgceArea_BIFF2(blob/*::, length, opts*/) {
12587	var r=parse_ColRelU(blob, 2), R=parse_ColRelU(blob, 2);
12588	var c=blob.read_shift(1);
12589	var C=blob.read_shift(1);
12590	return { s:{r:r[0], c:c, cRel:r[1], rRel:r[2]}, e:{r:R[0], c:C, cRel:R[1], rRel:R[2]} };
12591}
12592
12593/* [MS-XLS] 2.5.198.105 ; [MS-XLSB] 2.5.97.90 */
12594function parse_RgceAreaRel(blob, length, opts) {
12595	if(opts.biff < 8) return parse_RgceArea_BIFF2(blob, length, opts);
12596	var r=blob.read_shift(opts.biff == 12 ? 4 : 2), R=blob.read_shift(opts.biff == 12 ? 4 : 2);
12597	var c=parse_ColRelU(blob, 2);
12598	var C=parse_ColRelU(blob, 2);
12599	return { s:{r:r, c:c[0], cRel:c[1], rRel:c[2]}, e:{r:R, c:C[0], cRel:C[1], rRel:C[2]} };
12600}
12601
12602/* [MS-XLS] 2.5.198.109 ; [MS-XLSB] 2.5.97.91 */
12603function parse_RgceLoc(blob, length, opts) {
12604	if(opts && opts.biff >= 2 && opts.biff <= 5) return parse_RgceLoc_BIFF2(blob, length, opts);
12605	var r = blob.read_shift(opts && opts.biff == 12 ? 4 : 2);
12606	var c = parse_ColRelU(blob, 2);
12607	return {r:r, c:c[0], cRel:c[1], rRel:c[2]};
12608}
12609function parse_RgceLoc_BIFF2(blob/*::, length, opts*/) {
12610	var r = parse_ColRelU(blob, 2);
12611	var c = blob.read_shift(1);
12612	return {r:r[0], c:c, cRel:r[1], rRel:r[2]};
12613}
12614
12615/* [MS-XLS] 2.5.198.107, 2.5.47 */
12616function parse_RgceElfLoc(blob/*::, length, opts*/) {
12617	var r = blob.read_shift(2);
12618	var c = blob.read_shift(2);
12619	return {r:r, c:c & 0xFF, fQuoted:!!(c & 0x4000), cRel:c>>15, rRel:c>>15 };
12620}
12621
12622/* [MS-XLS] 2.5.198.111 ; [MS-XLSB] 2.5.97.92 TODO */
12623function parse_RgceLocRel(blob, length, opts) {
12624	var biff = opts && opts.biff ? opts.biff : 8;
12625	if(biff >= 2 && biff <= 5) return parse_RgceLocRel_BIFF2(blob, length, opts);
12626	var r = blob.read_shift(biff >= 12 ? 4 : 2);
12627	var cl = blob.read_shift(2);
12628	var cRel = (cl & 0x4000) >> 14, rRel = (cl & 0x8000) >> 15;
12629	cl &= 0x3FFF;
12630	if(rRel == 1) while(r > 0x7FFFF) r -= 0x100000;
12631	if(cRel == 1) while(cl > 0x1FFF) cl = cl - 0x4000;
12632	return {r:r,c:cl,cRel:cRel,rRel:rRel};
12633}
12634function parse_RgceLocRel_BIFF2(blob/*::, length:number, opts*/) {
12635	var rl = blob.read_shift(2);
12636	var c = blob.read_shift(1);
12637	var rRel = (rl & 0x8000) >> 15, cRel = (rl & 0x4000) >> 14;
12638	rl &= 0x3FFF;
12639	if(rRel == 1 && rl >= 0x2000) rl = rl - 0x4000;
12640	if(cRel == 1 && c >= 0x80) c = c - 0x100;
12641	return {r:rl,c:c,cRel:cRel,rRel:rRel};
12642}
12643
12644/* [MS-XLS] 2.5.198.27 ; [MS-XLSB] 2.5.97.18 */
12645function parse_PtgArea(blob, length, opts) {
12646	var type = (blob[blob.l++] & 0x60) >> 5;
12647	var area = parse_RgceArea(blob, opts.biff >= 2 && opts.biff <= 5 ? 6 : 8, opts);
12648	return [type, area];
12649}
12650
12651/* [MS-XLS] 2.5.198.28 ; [MS-XLSB] 2.5.97.19 */
12652function parse_PtgArea3d(blob, length, opts) {
12653	var type = (blob[blob.l++] & 0x60) >> 5;
12654	var ixti = blob.read_shift(2, 'i');
12655	var w = 8;
12656	if(opts) switch(opts.biff) {
12657		case 5: blob.l += 12; w = 6; break;
12658		case 12: w = 12; break;
12659	}
12660	var area = parse_RgceArea(blob, w, opts);
12661	return [type, ixti, area];
12662}
12663
12664/* [MS-XLS] 2.5.198.29 ; [MS-XLSB] 2.5.97.20 */
12665function parse_PtgAreaErr(blob, length, opts) {
12666	var type = (blob[blob.l++] & 0x60) >> 5;
12667	blob.l += opts && (opts.biff > 8) ? 12 : (opts.biff < 8 ? 6 : 8);
12668	return [type];
12669}
12670/* [MS-XLS] 2.5.198.30 ; [MS-XLSB] 2.5.97.21 */
12671function parse_PtgAreaErr3d(blob, length, opts) {
12672	var type = (blob[blob.l++] & 0x60) >> 5;
12673	var ixti = blob.read_shift(2);
12674	var w = 8;
12675	if(opts) switch(opts.biff) {
12676		case 5: blob.l += 12; w = 6; break;
12677		case 12: w = 12; break;
12678	}
12679	blob.l += w;
12680	return [type, ixti];
12681}
12682
12683/* [MS-XLS] 2.5.198.31 ; [MS-XLSB] 2.5.97.22 */
12684function parse_PtgAreaN(blob, length, opts) {
12685	var type = (blob[blob.l++] & 0x60) >> 5;
12686	var area = parse_RgceAreaRel(blob, length - 1, opts);
12687	return [type, area];
12688}
12689
12690/* [MS-XLS] 2.5.198.32 ; [MS-XLSB] 2.5.97.23 */
12691function parse_PtgArray(blob, length, opts) {
12692	var type = (blob[blob.l++] & 0x60) >> 5;
12693	blob.l += opts.biff == 2 ? 6 : opts.biff == 12 ? 14 : 7;
12694	return [type];
12695}
12696
12697/* [MS-XLS] 2.5.198.33 ; [MS-XLSB] 2.5.97.24 */
12698function parse_PtgAttrBaxcel(blob) {
12699	var bitSemi = blob[blob.l+1] & 0x01; /* 1 = volatile */
12700	var bitBaxcel = 1;
12701	blob.l += 4;
12702	return [bitSemi, bitBaxcel];
12703}
12704
12705/* [MS-XLS] 2.5.198.34 ; [MS-XLSB] 2.5.97.25 */
12706function parse_PtgAttrChoose(blob, length, opts)/*:Array<number>*/ {
12707	blob.l +=2;
12708	var offset = blob.read_shift(opts && opts.biff == 2 ? 1 : 2);
12709	var o/*:Array<number>*/ = [];
12710	/* offset is 1 less than the number of elements */
12711	for(var i = 0; i <= offset; ++i) o.push(blob.read_shift(opts && opts.biff == 2 ? 1 : 2));
12712	return o;
12713}
12714
12715/* [MS-XLS] 2.5.198.35 ; [MS-XLSB] 2.5.97.26 */
12716function parse_PtgAttrGoto(blob, length, opts) {
12717	var bitGoto = (blob[blob.l+1] & 0xFF) ? 1 : 0;
12718	blob.l += 2;
12719	return [bitGoto, blob.read_shift(opts && opts.biff == 2 ? 1 : 2)];
12720}
12721
12722/* [MS-XLS] 2.5.198.36 ; [MS-XLSB] 2.5.97.27 */
12723function parse_PtgAttrIf(blob, length, opts) {
12724	var bitIf = (blob[blob.l+1] & 0xFF) ? 1 : 0;
12725	blob.l += 2;
12726	return [bitIf, blob.read_shift(opts && opts.biff == 2 ? 1 : 2)];
12727}
12728
12729/* [MS-XLSB] 2.5.97.28 */
12730function parse_PtgAttrIfError(blob) {
12731	var bitIf = (blob[blob.l+1] & 0xFF) ? 1 : 0;
12732	blob.l += 2;
12733	return [bitIf, blob.read_shift(2)];
12734}
12735
12736/* [MS-XLS] 2.5.198.37 ; [MS-XLSB] 2.5.97.29 */
12737function parse_PtgAttrSemi(blob, length, opts) {
12738	var bitSemi = (blob[blob.l+1] & 0xFF) ? 1 : 0;
12739	blob.l += opts && opts.biff == 2 ? 3 : 4;
12740	return [bitSemi];
12741}
12742
12743/* [MS-XLS] 2.5.198.40 ; [MS-XLSB] 2.5.97.32 */
12744function parse_PtgAttrSpaceType(blob/*::, length*/) {
12745	var type = blob.read_shift(1), cch = blob.read_shift(1);
12746	return [type, cch];
12747}
12748
12749/* [MS-XLS] 2.5.198.38 ; [MS-XLSB] 2.5.97.30 */
12750function parse_PtgAttrSpace(blob) {
12751	blob.read_shift(2);
12752	return parse_PtgAttrSpaceType(blob, 2);
12753}
12754
12755/* [MS-XLS] 2.5.198.39 ; [MS-XLSB] 2.5.97.31 */
12756function parse_PtgAttrSpaceSemi(blob) {
12757	blob.read_shift(2);
12758	return parse_PtgAttrSpaceType(blob, 2);
12759}
12760
12761/* [MS-XLS] 2.5.198.84 ; [MS-XLSB] 2.5.97.68 TODO */
12762function parse_PtgRef(blob, length, opts) {
12763	//var ptg = blob[blob.l] & 0x1F;
12764	var type = (blob[blob.l] & 0x60)>>5;
12765	blob.l += 1;
12766	var loc = parse_RgceLoc(blob, 0, opts);
12767	return [type, loc];
12768}
12769
12770/* [MS-XLS] 2.5.198.88 ; [MS-XLSB] 2.5.97.72 TODO */
12771function parse_PtgRefN(blob, length, opts) {
12772	var type = (blob[blob.l] & 0x60)>>5;
12773	blob.l += 1;
12774	var loc = parse_RgceLocRel(blob, 0, opts);
12775	return [type, loc];
12776}
12777
12778/* [MS-XLS] 2.5.198.85 ; [MS-XLSB] 2.5.97.69 TODO */
12779function parse_PtgRef3d(blob, length, opts) {
12780	var type = (blob[blob.l] & 0x60)>>5;
12781	blob.l += 1;
12782	var ixti = blob.read_shift(2); // XtiIndex
12783	if(opts && opts.biff == 5) blob.l += 12;
12784	var loc = parse_RgceLoc(blob, 0, opts); // TODO: or RgceLocRel
12785	return [type, ixti, loc];
12786}
12787
12788
12789/* [MS-XLS] 2.5.198.62 ; [MS-XLSB] 2.5.97.45 TODO */
12790function parse_PtgFunc(blob, length, opts) {
12791	//var ptg = blob[blob.l] & 0x1F;
12792	var type = (blob[blob.l] & 0x60)>>5;
12793	blob.l += 1;
12794	var iftab = blob.read_shift(opts && opts.biff <= 3 ? 1 : 2);
12795	return [FtabArgc[iftab], Ftab[iftab], type];
12796}
12797/* [MS-XLS] 2.5.198.63 ; [MS-XLSB] 2.5.97.46 TODO */
12798function parse_PtgFuncVar(blob, length, opts) {
12799	var type = blob[blob.l++];
12800	var cparams = blob.read_shift(1), tab = opts && opts.biff <= 3 ? [(type == 0x58 ? -1 : 0), blob.read_shift(1)]: parsetab(blob);
12801	return [cparams, (tab[0] === 0 ? Ftab : Cetab)[tab[1]]];
12802}
12803
12804function parsetab(blob) {
12805	return [blob[blob.l+1]>>7, blob.read_shift(2) & 0x7FFF];
12806}
12807
12808/* [MS-XLS] 2.5.198.41 ; [MS-XLSB] 2.5.97.33 */
12809function parse_PtgAttrSum(blob, length, opts) {
12810	blob.l += opts && opts.biff == 2 ? 3 : 4; return;
12811}
12812
12813/* [MS-XLS] 2.5.198.58 ; [MS-XLSB] 2.5.97.40 */
12814function parse_PtgExp(blob, length, opts) {
12815	blob.l++;
12816	if(opts && opts.biff == 12) return [blob.read_shift(4, 'i'), 0];
12817	var row = blob.read_shift(2);
12818	var col = blob.read_shift(opts && opts.biff == 2 ? 1 : 2);
12819	return [row, col];
12820}
12821
12822/* [MS-XLS] 2.5.198.57 ; [MS-XLSB] 2.5.97.39 */
12823function parse_PtgErr(blob) { blob.l++; return BErr[blob.read_shift(1)]; }
12824
12825/* [MS-XLS] 2.5.198.66 ; [MS-XLSB] 2.5.97.49 */
12826function parse_PtgInt(blob) { blob.l++; return blob.read_shift(2); }
12827
12828/* [MS-XLS] 2.5.198.42 ; [MS-XLSB] 2.5.97.34 */
12829function parse_PtgBool(blob) { blob.l++; return blob.read_shift(1)!==0;}
12830
12831/* [MS-XLS] 2.5.198.79 ; [MS-XLSB] 2.5.97.63 */
12832function parse_PtgNum(blob) { blob.l++; return parse_Xnum(blob, 8); }
12833
12834/* [MS-XLS] 2.5.198.89 ; [MS-XLSB] 2.5.97.74 */
12835function parse_PtgStr(blob, length, opts) { blob.l++; return parse_ShortXLUnicodeString(blob, length-1, opts); }
12836
12837/* [MS-XLS] 2.5.192.112 + 2.5.192.11{3,4,5,6,7} */
12838/* [MS-XLSB] 2.5.97.93 + 2.5.97.9{4,5,6,7} */
12839function parse_SerAr(blob, biff/*:number*/) {
12840	var val = [blob.read_shift(1)];
12841	if(biff == 12) switch(val[0]) {
12842		case 0x02: val[0] = 0x04; break; /* SerBool */
12843		case 0x04: val[0] = 0x10; break; /* SerErr */
12844		case 0x00: val[0] = 0x01; break; /* SerNum */
12845		case 0x01: val[0] = 0x02; break; /* SerStr */
12846	}
12847	switch(val[0]) {
12848		case 0x04: /* SerBool -- boolean */
12849			val[1] = parsebool(blob, 1) ? 'TRUE' : 'FALSE';
12850			if(biff != 12) blob.l += 7; break;
12851		case 0x25: /* appears to be an alias */
12852		case 0x10: /* SerErr -- error */
12853			val[1] = BErr[blob[blob.l]];
12854			blob.l += ((biff == 12) ? 4 : 8); break;
12855		case 0x00: /* SerNil -- honestly, I'm not sure how to reproduce this */
12856			blob.l += 8; break;
12857		case 0x01: /* SerNum -- Xnum */
12858			val[1] = parse_Xnum(blob, 8); break;
12859		case 0x02: /* SerStr -- XLUnicodeString (<256 chars) */
12860			val[1] = parse_XLUnicodeString2(blob, 0, {biff:biff > 0 && biff < 8 ? 2 : biff}); break;
12861		default: throw new Error("Bad SerAr: " + val[0]); /* Unreachable */
12862	}
12863	return val;
12864}
12865
12866/* [MS-XLS] 2.5.198.61 ; [MS-XLSB] 2.5.97.44 */
12867function parse_PtgExtraMem(blob, cce, opts) {
12868	var count = blob.read_shift((opts.biff == 12) ? 4 : 2);
12869	var out/*:Array<Range>*/ = [];
12870	for(var i = 0; i != count; ++i) out.push(((opts.biff == 12) ? parse_UncheckedRfX : parse_Ref8U)(blob, 8));
12871	return out;
12872}
12873
12874/* [MS-XLS] 2.5.198.59 ; [MS-XLSB] 2.5.97.41 */
12875function parse_PtgExtraArray(blob, length, opts) {
12876	var rows = 0, cols = 0;
12877	if(opts.biff == 12) {
12878		rows = blob.read_shift(4); // DRw
12879		cols = blob.read_shift(4); // DCol
12880	} else {
12881		cols = 1 + blob.read_shift(1); //DColByteU
12882		rows = 1 + blob.read_shift(2); //DRw
12883	}
12884	if(opts.biff >= 2 && opts.biff < 8) { --rows; if(--cols == 0) cols = 0x100; }
12885	// $FlowIgnore
12886	for(var i = 0, o/*:Array<Array<any>>*/ = []; i != rows && (o[i] = []); ++i)
12887		for(var j = 0; j != cols; ++j) o[i][j] = parse_SerAr(blob, opts.biff);
12888	return o;
12889}
12890
12891/* [MS-XLS] 2.5.198.76 ; [MS-XLSB] 2.5.97.60 */
12892function parse_PtgName(blob, length, opts) {
12893	var type = (blob.read_shift(1) >>> 5) & 0x03;
12894	var w = (!opts || (opts.biff >= 8)) ? 4 : 2;
12895	var nameindex = blob.read_shift(w);
12896	switch(opts.biff) {
12897		case 2: blob.l += 5; break;
12898		case 3: case 4: blob.l += 8; break;
12899		case 5: blob.l += 12; break;
12900	}
12901	return [type, 0, nameindex];
12902}
12903
12904/* [MS-XLS] 2.5.198.77 ; [MS-XLSB] 2.5.97.61 */
12905function parse_PtgNameX(blob, length, opts) {
12906	if(opts.biff == 5) return parse_PtgNameX_BIFF5(blob, length, opts);
12907	var type = (blob.read_shift(1) >>> 5) & 0x03;
12908	var ixti = blob.read_shift(2); // XtiIndex
12909	var nameindex = blob.read_shift(4);
12910	return [type, ixti, nameindex];
12911}
12912function parse_PtgNameX_BIFF5(blob/*::, length, opts*/) {
12913	var type = (blob.read_shift(1) >>> 5) & 0x03;
12914	var ixti = blob.read_shift(2, 'i'); // XtiIndex
12915	blob.l += 8;
12916	var nameindex = blob.read_shift(2);
12917	blob.l += 12;
12918	return [type, ixti, nameindex];
12919}
12920
12921/* [MS-XLS] 2.5.198.70 ; [MS-XLSB] 2.5.97.54 */
12922function parse_PtgMemArea(blob, length, opts) {
12923	var type = (blob.read_shift(1) >>> 5) & 0x03;
12924	blob.l += (opts && opts.biff == 2 ? 3 : 4);
12925	var cce = blob.read_shift(opts && opts.biff == 2 ? 1 : 2);
12926	return [type, cce];
12927}
12928
12929/* [MS-XLS] 2.5.198.72 ; [MS-XLSB] 2.5.97.56 */
12930function parse_PtgMemFunc(blob, length, opts) {
12931	var type = (blob.read_shift(1) >>> 5) & 0x03;
12932	var cce = blob.read_shift(opts && opts.biff == 2 ? 1 : 2);
12933	return [type, cce];
12934}
12935
12936
12937/* [MS-XLS] 2.5.198.86 ; [MS-XLSB] 2.5.97.69 */
12938function parse_PtgRefErr(blob, length, opts) {
12939	var type = (blob.read_shift(1) >>> 5) & 0x03;
12940	blob.l += 4;
12941	if(opts.biff < 8) blob.l--;
12942	if(opts.biff == 12) blob.l += 2;
12943	return [type];
12944}
12945
12946/* [MS-XLS] 2.5.198.87 ; [MS-XLSB] 2.5.97.71 */
12947function parse_PtgRefErr3d(blob, length, opts) {
12948	var type = (blob[blob.l++] & 0x60) >> 5;
12949	var ixti = blob.read_shift(2);
12950	var w = 4;
12951	if(opts) switch(opts.biff) {
12952		case 5: w = 15; break;
12953		case 12: w = 6; break;
12954	}
12955	blob.l += w;
12956	return [type, ixti];
12957}
12958
12959/* [MS-XLS] 2.5.198.71 ; [MS-XLSB] 2.5.97.55 */
12960var parse_PtgMemErr = parsenoop;
12961/* [MS-XLS] 2.5.198.73  ; [MS-XLSB] 2.5.97.57 */
12962var parse_PtgMemNoMem = parsenoop;
12963/* [MS-XLS] 2.5.198.92 */
12964var parse_PtgTbl = parsenoop;
12965
12966function parse_PtgElfLoc(blob, length, opts) {
12967	blob.l += 2;
12968	return [parse_RgceElfLoc(blob, 4, opts)];
12969}
12970function parse_PtgElfNoop(blob/*::, length, opts*/) {
12971	blob.l += 6;
12972	return [];
12973}
12974/* [MS-XLS] 2.5.198.46 */
12975var parse_PtgElfCol = parse_PtgElfLoc;
12976/* [MS-XLS] 2.5.198.47 */
12977var parse_PtgElfColS = parse_PtgElfNoop;
12978/* [MS-XLS] 2.5.198.48 */
12979var parse_PtgElfColSV = parse_PtgElfNoop;
12980/* [MS-XLS] 2.5.198.49 */
12981var parse_PtgElfColV = parse_PtgElfLoc;
12982/* [MS-XLS] 2.5.198.50 */
12983function parse_PtgElfLel(blob/*::, length, opts*/) {
12984	blob.l += 2;
12985	return [parseuint16(blob), blob.read_shift(2) & 0x01];
12986}
12987/* [MS-XLS] 2.5.198.51 */
12988var parse_PtgElfRadical = parse_PtgElfLoc;
12989/* [MS-XLS] 2.5.198.52 */
12990var parse_PtgElfRadicalLel = parse_PtgElfLel;
12991/* [MS-XLS] 2.5.198.53 */
12992var parse_PtgElfRadicalS = parse_PtgElfNoop;
12993/* [MS-XLS] 2.5.198.54 */
12994var parse_PtgElfRw = parse_PtgElfLoc;
12995/* [MS-XLS] 2.5.198.55 */
12996var parse_PtgElfRwV = parse_PtgElfLoc;
12997
12998/* [MS-XLSB] 2.5.97.52 TODO */
12999var PtgListRT = [
13000	"Data",
13001	"All",
13002	"Headers",
13003	"??",
13004	"?Data2",
13005	"??",
13006	"?DataHeaders",
13007	"??",
13008	"Totals",
13009	"??",
13010	"??",
13011	"??",
13012	"?DataTotals",
13013	"??",
13014	"??",
13015	"??",
13016	"?Current"
13017];
13018function parse_PtgList(blob/*::, length, opts*/) {
13019	blob.l += 2;
13020	var ixti = blob.read_shift(2);
13021	var flags = blob.read_shift(2);
13022	var idx = blob.read_shift(4);
13023	var c = blob.read_shift(2);
13024	var C = blob.read_shift(2);
13025	var rt = PtgListRT[(flags >> 2) & 0x1F];
13026	return {ixti: ixti, coltype:(flags&0x3), rt:rt, idx:idx, c:c, C:C};
13027}
13028/* [MS-XLS] 2.5.198.91 ; [MS-XLSB] 2.5.97.76 */
13029function parse_PtgSxName(blob/*::, length, opts*/) {
13030	blob.l += 2;
13031	return [blob.read_shift(4)];
13032}
13033
13034/* [XLS] old spec */
13035function parse_PtgSheet(blob, length, opts) {
13036	blob.l += 5;
13037	blob.l += 2;
13038	blob.l += (opts.biff == 2 ? 1 : 4);
13039	return ["PTGSHEET"];
13040}
13041function parse_PtgEndSheet(blob, length, opts) {
13042	blob.l += (opts.biff == 2 ? 4 : 5);
13043	return ["PTGENDSHEET"];
13044}
13045function parse_PtgMemAreaN(blob/*::, length, opts*/) {
13046	var type = (blob.read_shift(1) >>> 5) & 0x03;
13047	var cce = blob.read_shift(2);
13048	return [type, cce];
13049}
13050function parse_PtgMemNoMemN(blob/*::, length, opts*/) {
13051	var type = (blob.read_shift(1) >>> 5) & 0x03;
13052	var cce = blob.read_shift(2);
13053	return [type, cce];
13054}
13055function parse_PtgAttrNoop(blob/*::, length, opts*/) {
13056	blob.l += 4;
13057	return [0, 0];
13058}
13059
13060/* [MS-XLS] 2.5.198.25 ; [MS-XLSB] 2.5.97.16 */
13061var PtgTypes = {
13062	/*::[*/0x01/*::]*/: { n:'PtgExp', f:parse_PtgExp },
13063	/*::[*/0x02/*::]*/: { n:'PtgTbl', f:parse_PtgTbl },
13064	/*::[*/0x03/*::]*/: { n:'PtgAdd', f:parseread1 },
13065	/*::[*/0x04/*::]*/: { n:'PtgSub', f:parseread1 },
13066	/*::[*/0x05/*::]*/: { n:'PtgMul', f:parseread1 },
13067	/*::[*/0x06/*::]*/: { n:'PtgDiv', f:parseread1 },
13068	/*::[*/0x07/*::]*/: { n:'PtgPower', f:parseread1 },
13069	/*::[*/0x08/*::]*/: { n:'PtgConcat', f:parseread1 },
13070	/*::[*/0x09/*::]*/: { n:'PtgLt', f:parseread1 },
13071	/*::[*/0x0A/*::]*/: { n:'PtgLe', f:parseread1 },
13072	/*::[*/0x0B/*::]*/: { n:'PtgEq', f:parseread1 },
13073	/*::[*/0x0C/*::]*/: { n:'PtgGe', f:parseread1 },
13074	/*::[*/0x0D/*::]*/: { n:'PtgGt', f:parseread1 },
13075	/*::[*/0x0E/*::]*/: { n:'PtgNe', f:parseread1 },
13076	/*::[*/0x0F/*::]*/: { n:'PtgIsect', f:parseread1 },
13077	/*::[*/0x10/*::]*/: { n:'PtgUnion', f:parseread1 },
13078	/*::[*/0x11/*::]*/: { n:'PtgRange', f:parseread1 },
13079	/*::[*/0x12/*::]*/: { n:'PtgUplus', f:parseread1 },
13080	/*::[*/0x13/*::]*/: { n:'PtgUminus', f:parseread1 },
13081	/*::[*/0x14/*::]*/: { n:'PtgPercent', f:parseread1 },
13082	/*::[*/0x15/*::]*/: { n:'PtgParen', f:parseread1 },
13083	/*::[*/0x16/*::]*/: { n:'PtgMissArg', f:parseread1 },
13084	/*::[*/0x17/*::]*/: { n:'PtgStr', f:parse_PtgStr },
13085	/*::[*/0x1A/*::]*/: { n:'PtgSheet', f:parse_PtgSheet },
13086	/*::[*/0x1B/*::]*/: { n:'PtgEndSheet', f:parse_PtgEndSheet },
13087	/*::[*/0x1C/*::]*/: { n:'PtgErr', f:parse_PtgErr },
13088	/*::[*/0x1D/*::]*/: { n:'PtgBool', f:parse_PtgBool },
13089	/*::[*/0x1E/*::]*/: { n:'PtgInt', f:parse_PtgInt },
13090	/*::[*/0x1F/*::]*/: { n:'PtgNum', f:parse_PtgNum },
13091	/*::[*/0x20/*::]*/: { n:'PtgArray', f:parse_PtgArray },
13092	/*::[*/0x21/*::]*/: { n:'PtgFunc', f:parse_PtgFunc },
13093	/*::[*/0x22/*::]*/: { n:'PtgFuncVar', f:parse_PtgFuncVar },
13094	/*::[*/0x23/*::]*/: { n:'PtgName', f:parse_PtgName },
13095	/*::[*/0x24/*::]*/: { n:'PtgRef', f:parse_PtgRef },
13096	/*::[*/0x25/*::]*/: { n:'PtgArea', f:parse_PtgArea },
13097	/*::[*/0x26/*::]*/: { n:'PtgMemArea', f:parse_PtgMemArea },
13098	/*::[*/0x27/*::]*/: { n:'PtgMemErr', f:parse_PtgMemErr },
13099	/*::[*/0x28/*::]*/: { n:'PtgMemNoMem', f:parse_PtgMemNoMem },
13100	/*::[*/0x29/*::]*/: { n:'PtgMemFunc', f:parse_PtgMemFunc },
13101	/*::[*/0x2A/*::]*/: { n:'PtgRefErr', f:parse_PtgRefErr },
13102	/*::[*/0x2B/*::]*/: { n:'PtgAreaErr', f:parse_PtgAreaErr },
13103	/*::[*/0x2C/*::]*/: { n:'PtgRefN', f:parse_PtgRefN },
13104	/*::[*/0x2D/*::]*/: { n:'PtgAreaN', f:parse_PtgAreaN },
13105	/*::[*/0x2E/*::]*/: { n:'PtgMemAreaN', f:parse_PtgMemAreaN },
13106	/*::[*/0x2F/*::]*/: { n:'PtgMemNoMemN', f:parse_PtgMemNoMemN },
13107	/*::[*/0x39/*::]*/: { n:'PtgNameX', f:parse_PtgNameX },
13108	/*::[*/0x3A/*::]*/: { n:'PtgRef3d', f:parse_PtgRef3d },
13109	/*::[*/0x3B/*::]*/: { n:'PtgArea3d', f:parse_PtgArea3d },
13110	/*::[*/0x3C/*::]*/: { n:'PtgRefErr3d', f:parse_PtgRefErr3d },
13111	/*::[*/0x3D/*::]*/: { n:'PtgAreaErr3d', f:parse_PtgAreaErr3d },
13112	/*::[*/0xFF/*::]*/: {}
13113};
13114/* These are duplicated in the PtgTypes table */
13115var PtgDupes = {
13116	/*::[*/0x40/*::]*/: 0x20, /*::[*/0x60/*::]*/: 0x20,
13117	/*::[*/0x41/*::]*/: 0x21, /*::[*/0x61/*::]*/: 0x21,
13118	/*::[*/0x42/*::]*/: 0x22, /*::[*/0x62/*::]*/: 0x22,
13119	/*::[*/0x43/*::]*/: 0x23, /*::[*/0x63/*::]*/: 0x23,
13120	/*::[*/0x44/*::]*/: 0x24, /*::[*/0x64/*::]*/: 0x24,
13121	/*::[*/0x45/*::]*/: 0x25, /*::[*/0x65/*::]*/: 0x25,
13122	/*::[*/0x46/*::]*/: 0x26, /*::[*/0x66/*::]*/: 0x26,
13123	/*::[*/0x47/*::]*/: 0x27, /*::[*/0x67/*::]*/: 0x27,
13124	/*::[*/0x48/*::]*/: 0x28, /*::[*/0x68/*::]*/: 0x28,
13125	/*::[*/0x49/*::]*/: 0x29, /*::[*/0x69/*::]*/: 0x29,
13126	/*::[*/0x4A/*::]*/: 0x2A, /*::[*/0x6A/*::]*/: 0x2A,
13127	/*::[*/0x4B/*::]*/: 0x2B, /*::[*/0x6B/*::]*/: 0x2B,
13128	/*::[*/0x4C/*::]*/: 0x2C, /*::[*/0x6C/*::]*/: 0x2C,
13129	/*::[*/0x4D/*::]*/: 0x2D, /*::[*/0x6D/*::]*/: 0x2D,
13130	/*::[*/0x4E/*::]*/: 0x2E, /*::[*/0x6E/*::]*/: 0x2E,
13131	/*::[*/0x4F/*::]*/: 0x2F, /*::[*/0x6F/*::]*/: 0x2F,
13132	/*::[*/0x58/*::]*/: 0x22, /*::[*/0x78/*::]*/: 0x22,
13133	/*::[*/0x59/*::]*/: 0x39, /*::[*/0x79/*::]*/: 0x39,
13134	/*::[*/0x5A/*::]*/: 0x3A, /*::[*/0x7A/*::]*/: 0x3A,
13135	/*::[*/0x5B/*::]*/: 0x3B, /*::[*/0x7B/*::]*/: 0x3B,
13136	/*::[*/0x5C/*::]*/: 0x3C, /*::[*/0x7C/*::]*/: 0x3C,
13137	/*::[*/0x5D/*::]*/: 0x3D, /*::[*/0x7D/*::]*/: 0x3D
13138};
13139
13140var Ptg18 = {
13141	/*::[*/0x01/*::]*/: { n:'PtgElfLel', f:parse_PtgElfLel },
13142	/*::[*/0x02/*::]*/: { n:'PtgElfRw', f:parse_PtgElfRw },
13143	/*::[*/0x03/*::]*/: { n:'PtgElfCol', f:parse_PtgElfCol },
13144	/*::[*/0x06/*::]*/: { n:'PtgElfRwV', f:parse_PtgElfRwV },
13145	/*::[*/0x07/*::]*/: { n:'PtgElfColV', f:parse_PtgElfColV },
13146	/*::[*/0x0A/*::]*/: { n:'PtgElfRadical', f:parse_PtgElfRadical },
13147	/*::[*/0x0B/*::]*/: { n:'PtgElfRadicalS', f:parse_PtgElfRadicalS },
13148	/*::[*/0x0D/*::]*/: { n:'PtgElfColS', f:parse_PtgElfColS },
13149	/*::[*/0x0F/*::]*/: { n:'PtgElfColSV', f:parse_PtgElfColSV },
13150	/*::[*/0x10/*::]*/: { n:'PtgElfRadicalLel', f:parse_PtgElfRadicalLel },
13151	/*::[*/0x19/*::]*/: { n:'PtgList', f:parse_PtgList },
13152	/*::[*/0x1D/*::]*/: { n:'PtgSxName', f:parse_PtgSxName },
13153	/*::[*/0xFF/*::]*/: {}
13154};
13155var Ptg19 = {
13156	/*::[*/0x00/*::]*/: { n:'PtgAttrNoop', f:parse_PtgAttrNoop },
13157	/*::[*/0x01/*::]*/: { n:'PtgAttrSemi', f:parse_PtgAttrSemi },
13158	/*::[*/0x02/*::]*/: { n:'PtgAttrIf', f:parse_PtgAttrIf },
13159	/*::[*/0x04/*::]*/: { n:'PtgAttrChoose', f:parse_PtgAttrChoose },
13160	/*::[*/0x08/*::]*/: { n:'PtgAttrGoto', f:parse_PtgAttrGoto },
13161	/*::[*/0x10/*::]*/: { n:'PtgAttrSum', f:parse_PtgAttrSum },
13162	/*::[*/0x20/*::]*/: { n:'PtgAttrBaxcel', f:parse_PtgAttrBaxcel },
13163	/*::[*/0x21/*::]*/: { n:'PtgAttrBaxcel', f:parse_PtgAttrBaxcel },
13164	/*::[*/0x40/*::]*/: { n:'PtgAttrSpace', f:parse_PtgAttrSpace },
13165	/*::[*/0x41/*::]*/: { n:'PtgAttrSpaceSemi', f:parse_PtgAttrSpaceSemi },
13166	/*::[*/0x80/*::]*/: { n:'PtgAttrIfError', f:parse_PtgAttrIfError },
13167	/*::[*/0xFF/*::]*/: {}
13168};
13169
13170/* [MS-XLS] 2.5.198.103 ; [MS-XLSB] 2.5.97.87 */
13171function parse_RgbExtra(blob, length, rgce, opts) {
13172	if(opts.biff < 8) return parsenoop(blob, length);
13173	var target = blob.l + length;
13174	var o = [];
13175	for(var i = 0; i !== rgce.length; ++i) {
13176		switch(rgce[i][0]) {
13177			case 'PtgArray': /* PtgArray -> PtgExtraArray */
13178				rgce[i][1] = parse_PtgExtraArray(blob, 0, opts);
13179				o.push(rgce[i][1]);
13180				break;
13181			case 'PtgMemArea': /* PtgMemArea -> PtgExtraMem */
13182				rgce[i][2] = parse_PtgExtraMem(blob, rgce[i][1], opts);
13183				o.push(rgce[i][2]);
13184				break;
13185			case 'PtgExp': /* PtgExp -> PtgExtraCol */
13186				if(opts && opts.biff == 12) {
13187					rgce[i][1][1] = blob.read_shift(4);
13188					o.push(rgce[i][1]);
13189				} break;
13190			case 'PtgList': /* TODO: PtgList -> PtgExtraList */
13191			case 'PtgElfRadicalS': /* TODO: PtgElfRadicalS -> PtgExtraElf */
13192			case 'PtgElfColS': /* TODO: PtgElfColS -> PtgExtraElf */
13193			case 'PtgElfColSV': /* TODO: PtgElfColSV -> PtgExtraElf */
13194				throw "Unsupported " + rgce[i][0];
13195			default: break;
13196		}
13197	}
13198	length = target - blob.l;
13199	/* note: this is technically an error but Excel disregards */
13200	//if(target !== blob.l && blob.l !== target - length) throw new Error(target + " != " + blob.l);
13201	if(length !== 0) o.push(parsenoop(blob, length));
13202	return o;
13203}
13204
13205/* [MS-XLS] 2.5.198.104 ; [MS-XLSB] 2.5.97.88 */
13206function parse_Rgce(blob, length, opts) {
13207	var target = blob.l + length;
13208	var R, id, ptgs = [];
13209	while(target != blob.l) {
13210		length = target - blob.l;
13211		id = blob[blob.l];
13212		R = PtgTypes[id] || PtgTypes[PtgDupes[id]];
13213		if(id === 0x18 || id === 0x19) R = (id === 0x18 ? Ptg18 : Ptg19)[blob[blob.l + 1]];
13214		if(!R || !R.f) { /*ptgs.push*/(parsenoop(blob, length)); }
13215		else { ptgs.push([R.n, R.f(blob, length, opts)]); }
13216	}
13217	return ptgs;
13218}
13219
13220function stringify_array(f/*:Array<Array<string>>*/)/*:string*/ {
13221	var o/*:Array<string>*/ = [];
13222	for(var i = 0; i < f.length; ++i) {
13223		var x = f[i], r/*:Array<string>*/ = [];
13224		for(var j = 0; j < x.length; ++j) {
13225			var y = x[j];
13226			if(y) switch(y[0]) {
13227				// TODO: handle embedded quotes
13228				case 0x02:
13229					/*:: if(typeof y[1] != 'string') throw "unreachable"; */
13230					r.push('"' + y[1].replace(/"/g,'""') + '"'); break;
13231				default: r.push(y[1]);
13232			} else r.push("");
13233		}
13234		o.push(r.join(","));
13235	}
13236	return o.join(";");
13237}
13238
13239/* [MS-XLS] 2.2.2 ; [MS-XLSB] 2.2.2 TODO */
13240var PtgBinOp = {
13241	PtgAdd: "+",
13242	PtgConcat: "&",
13243	PtgDiv: "/",
13244	PtgEq: "=",
13245	PtgGe: ">=",
13246	PtgGt: ">",
13247	PtgLe: "<=",
13248	PtgLt: "<",
13249	PtgMul: "*",
13250	PtgNe: "<>",
13251	PtgPower: "^",
13252	PtgSub: "-"
13253};
13254
13255// TODO: explore space
13256function make_3d_range(start, end) {
13257	var s = start.lastIndexOf("!"), e = end.lastIndexOf("!");
13258	if(s == -1 && e == -1) return start + ":" + end;
13259	if(s > 0 && e > 0 && start.slice(0, s).toLowerCase() == end.slice(0, e).toLowerCase()) return start + ":" + end.slice(e+1);
13260	console.error("Cannot hydrate range", start, end);
13261	return start + ":" + end;
13262}
13263
13264function get_ixti_raw(supbooks, ixti/*:number*/, opts)/*:string*/ {
13265	if(!supbooks) return "SH33TJSERR0";
13266	if(opts.biff > 8 && (!supbooks.XTI || !supbooks.XTI[ixti])) return supbooks.SheetNames[ixti];
13267	if(!supbooks.XTI) return "SH33TJSERR6";
13268	var XTI = supbooks.XTI[ixti];
13269	if(opts.biff < 8) {
13270		if(ixti > 10000) ixti-= 65536;
13271		if(ixti < 0) ixti = -ixti;
13272		return ixti == 0 ? "" : supbooks.XTI[ixti - 1];
13273	}
13274	if(!XTI) return "SH33TJSERR1";
13275	var o = "";
13276	if(opts.biff > 8) switch(supbooks[XTI[0]][0]) {
13277		case 0x0165: /* 'BrtSupSelf' */
13278			o = XTI[1] == -1 ? "#REF" : supbooks.SheetNames[XTI[1]];
13279			return XTI[1] == XTI[2] ? o : o + ":" + supbooks.SheetNames[XTI[2]];
13280		case 0x0166: /* 'BrtSupSame' */
13281			if(opts.SID != null) return supbooks.SheetNames[opts.SID];
13282			return "SH33TJSSAME" + supbooks[XTI[0]][0];
13283		case 0x0163: /* 'BrtSupBookSrc' */
13284			/* falls through */
13285		default: return "SH33TJSSRC" + supbooks[XTI[0]][0];
13286	}
13287	switch(supbooks[XTI[0]][0][0]) {
13288		case 0x0401:
13289			o = XTI[1] == -1 ? "#REF" : (supbooks.SheetNames[XTI[1]] || "SH33TJSERR3");
13290			return XTI[1] == XTI[2] ? o : o + ":" + supbooks.SheetNames[XTI[2]];
13291		case 0x3A01: return supbooks[XTI[0]].slice(1).map(function(name) { return name.Name; }).join(";;"); //return "SH33TJSERR8";
13292		default:
13293			if(!supbooks[XTI[0]][0][3]) return "SH33TJSERR2";
13294			o = XTI[1] == -1 ? "#REF" : (supbooks[XTI[0]][0][3][XTI[1]] || "SH33TJSERR4");
13295			return XTI[1] == XTI[2] ? o : o + ":" + supbooks[XTI[0]][0][3][XTI[2]];
13296	}
13297}
13298function get_ixti(supbooks, ixti/*:number*/, opts)/*:string*/ {
13299	var ixtiraw = get_ixti_raw(supbooks, ixti, opts);
13300	return ixtiraw == "#REF" ? ixtiraw : formula_quote_sheet_name(ixtiraw, opts);
13301}
13302function stringify_formula(formula/*Array<any>*/, range, cell/*:any*/, supbooks, opts)/*:string*/ {
13303	var biff = (opts && opts.biff) || 8;
13304	var _range = /*range != null ? range :*/ {s:{c:0, r:0},e:{c:0, r:0}};
13305	var stack/*:Array<string>*/ = [], e1, e2, /*::type,*/ c/*:CellAddress*/, ixti=0, nameidx=0, r, sname="";
13306	if(!formula[0] || !formula[0][0]) return "";
13307	var last_sp = -1, sp = "";
13308	for(var ff = 0, fflen = formula[0].length; ff < fflen; ++ff) {
13309		var f = formula[0][ff];
13310		switch(f[0]) {
13311			case 'PtgUminus': /* [MS-XLS] 2.5.198.93 */
13312				stack.push("-" + stack.pop()); break;
13313			case 'PtgUplus': /* [MS-XLS] 2.5.198.95 */
13314				stack.push("+" + stack.pop()); break;
13315			case 'PtgPercent': /* [MS-XLS] 2.5.198.81 */
13316				stack.push(stack.pop() + "%"); break;
13317
13318			case 'PtgAdd':    /* [MS-XLS] 2.5.198.26 */
13319			case 'PtgConcat': /* [MS-XLS] 2.5.198.43 */
13320			case 'PtgDiv':    /* [MS-XLS] 2.5.198.45 */
13321			case 'PtgEq':     /* [MS-XLS] 2.5.198.56 */
13322			case 'PtgGe':     /* [MS-XLS] 2.5.198.64 */
13323			case 'PtgGt':     /* [MS-XLS] 2.5.198.65 */
13324			case 'PtgLe':     /* [MS-XLS] 2.5.198.68 */
13325			case 'PtgLt':     /* [MS-XLS] 2.5.198.69 */
13326			case 'PtgMul':    /* [MS-XLS] 2.5.198.75 */
13327			case 'PtgNe':     /* [MS-XLS] 2.5.198.78 */
13328			case 'PtgPower':  /* [MS-XLS] 2.5.198.82 */
13329			case 'PtgSub':    /* [MS-XLS] 2.5.198.90 */
13330				e1 = stack.pop(); e2 = stack.pop();
13331				if(last_sp >= 0) {
13332					switch(formula[0][last_sp][1][0]) {
13333						case 0:
13334							// $FlowIgnore
13335							sp = fill(" ", formula[0][last_sp][1][1]); break;
13336						case 1:
13337							// $FlowIgnore
13338							sp = fill("\r", formula[0][last_sp][1][1]); break;
13339						default:
13340							sp = "";
13341							// $FlowIgnore
13342							if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + formula[0][last_sp][1][0]);
13343					}
13344					e2 = e2 + sp;
13345					last_sp = -1;
13346				}
13347				stack.push(e2+PtgBinOp[f[0]]+e1);
13348				break;
13349
13350			case 'PtgIsect': /* [MS-XLS] 2.5.198.67 */
13351				e1 = stack.pop(); e2 = stack.pop();
13352				stack.push(e2+" "+e1);
13353				break;
13354			case 'PtgUnion': /* [MS-XLS] 2.5.198.94 */
13355				e1 = stack.pop(); e2 = stack.pop();
13356				stack.push(e2+","+e1);
13357				break;
13358			case 'PtgRange': /* [MS-XLS] 2.5.198.83 */
13359				e1 = stack.pop(); e2 = stack.pop();
13360				stack.push(make_3d_range(e2,e1));
13361				break;
13362
13363			case 'PtgAttrChoose': /* [MS-XLS] 2.5.198.34 */
13364				break;
13365			case 'PtgAttrGoto': /* [MS-XLS] 2.5.198.35 */
13366				break;
13367			case 'PtgAttrIf': /* [MS-XLS] 2.5.198.36 */
13368				break;
13369			case 'PtgAttrIfError': /* [MS-XLSB] 2.5.97.28 */
13370				break;
13371
13372
13373			case 'PtgRef': /* [MS-XLS] 2.5.198.84 */
13374				/*::type = f[1][0]; */c = shift_cell_xls((f[1][1]/*:any*/), _range, opts);
13375				stack.push(encode_cell_xls(c, biff));
13376				break;
13377			case 'PtgRefN': /* [MS-XLS] 2.5.198.88 */
13378				/*::type = f[1][0]; */c = cell ? shift_cell_xls((f[1][1]/*:any*/), cell, opts) : (f[1][1]/*:any*/);
13379				stack.push(encode_cell_xls(c, biff));
13380				break;
13381			case 'PtgRef3d': /* [MS-XLS] 2.5.198.85 */
13382				/*::type = f[1][0]; */ixti = /*::Number(*/f[1][1]/*::)*/; c = shift_cell_xls((f[1][2]/*:any*/), _range, opts);
13383				sname = get_ixti(supbooks, ixti, opts);
13384				var w = sname; /* IE9 fails on defined names */ // eslint-disable-line no-unused-vars
13385				stack.push(sname + "!" + encode_cell_xls(c, biff));
13386				break;
13387
13388			case 'PtgFunc': /* [MS-XLS] 2.5.198.62 */
13389			case 'PtgFuncVar': /* [MS-XLS] 2.5.198.63 */
13390				/* f[1] = [argc, func, type] */
13391				var argc/*:number*/ = (f[1][0]/*:any*/), func/*:string*/ = (f[1][1]/*:any*/);
13392				if(!argc) argc = 0;
13393				argc &= 0x7F;
13394				var args = argc == 0 ? [] : stack.slice(-argc);
13395				stack.length -= argc;
13396				if(func === 'User') func = args.shift();
13397				stack.push(func + "(" + args.join(",") + ")");
13398				break;
13399
13400			case 'PtgBool': /* [MS-XLS] 2.5.198.42 */
13401				stack.push(f[1] ? "TRUE" : "FALSE"); break;
13402			case 'PtgInt': /* [MS-XLS] 2.5.198.66 */
13403				stack.push(/*::String(*/f[1]/*::)*/); break;
13404			case 'PtgNum': /* [MS-XLS] 2.5.198.79 TODO: precision? */
13405				stack.push(String(f[1])); break;
13406			case 'PtgStr': /* [MS-XLS] 2.5.198.89 */
13407				// $FlowIgnore
13408				stack.push('"' + f[1].replace(/"/g, '""') + '"'); break;
13409			case 'PtgErr': /* [MS-XLS] 2.5.198.57 */
13410				stack.push(/*::String(*/f[1]/*::)*/); break;
13411			case 'PtgAreaN': /* [MS-XLS] 2.5.198.31 TODO */
13412				/*::type = f[1][0]; */r = shift_range_xls(f[1][1], cell ? {s:cell} : _range, opts);
13413				stack.push(encode_range_xls((r/*:any*/), opts));
13414				break;
13415			case 'PtgArea': /* [MS-XLS] 2.5.198.27 TODO: fixed points */
13416				/*::type = f[1][0]; */r = shift_range_xls(f[1][1], _range, opts);
13417				stack.push(encode_range_xls((r/*:any*/), opts));
13418				break;
13419			case 'PtgArea3d': /* [MS-XLS] 2.5.198.28 TODO */
13420				/*::type = f[1][0]; */ixti = /*::Number(*/f[1][1]/*::)*/; r = f[1][2];
13421				sname = get_ixti(supbooks, ixti, opts);
13422				stack.push(sname + "!" + encode_range_xls((r/*:any*/), opts));
13423				break;
13424			case 'PtgAttrSum': /* [MS-XLS] 2.5.198.41 */
13425				stack.push("SUM(" + stack.pop() + ")");
13426				break;
13427
13428			case 'PtgAttrBaxcel': /* [MS-XLS] 2.5.198.33 */
13429			case 'PtgAttrSemi': /* [MS-XLS] 2.5.198.37 */
13430				break;
13431
13432			case 'PtgName': /* [MS-XLS] 2.5.198.76 ; [MS-XLSB] 2.5.97.60 TODO: revisions */
13433				/* f[1] = type, 0, nameindex */
13434				nameidx = (f[1][2]/*:any*/);
13435				var lbl = (supbooks.names||[])[nameidx-1] || (supbooks[0]||[])[nameidx];
13436				var name = lbl ? lbl.Name : "SH33TJSNAME" + String(nameidx);
13437				/* [MS-XLSB] 2.5.97.10 Ftab -- last verified 20220204 */
13438				if(name && name.slice(0,6) == "_xlfn." && !opts.xlfn) name = name.slice(6);
13439				stack.push(name);
13440				break;
13441
13442			case 'PtgNameX': /* [MS-XLS] 2.5.198.77 ; [MS-XLSB] 2.5.97.61 TODO: revisions */
13443				/* f[1] = type, ixti, nameindex */
13444				var bookidx/*:number*/ = (f[1][1]/*:any*/); nameidx = (f[1][2]/*:any*/); var externbook;
13445				/* TODO: Properly handle missing values -- this should be using get_ixti_raw primarily */
13446				if(opts.biff <= 5) {
13447					if(bookidx < 0) bookidx = -bookidx;
13448					if(supbooks[bookidx]) externbook = supbooks[bookidx][nameidx];
13449				} else {
13450					var o = "";
13451					if(((supbooks[bookidx]||[])[0]||[])[0] == 0x3A01){/* empty */}
13452					else if(((supbooks[bookidx]||[])[0]||[])[0] == 0x0401){
13453						if(supbooks[bookidx][nameidx] && supbooks[bookidx][nameidx].itab > 0) {
13454							o = supbooks.SheetNames[supbooks[bookidx][nameidx].itab-1] + "!";
13455						}
13456					}
13457					else o = supbooks.SheetNames[nameidx-1]+ "!";
13458					if(supbooks[bookidx] && supbooks[bookidx][nameidx]) o += supbooks[bookidx][nameidx].Name;
13459					else if(supbooks[0] && supbooks[0][nameidx]) o += supbooks[0][nameidx].Name;
13460					else {
13461						var ixtidata = (get_ixti_raw(supbooks, bookidx, opts)||"").split(";;");
13462						if(ixtidata[nameidx - 1]) o = ixtidata[nameidx - 1]; // TODO: confirm this is correct
13463						else o += "SH33TJSERRX";
13464					}
13465					stack.push(o);
13466					break;
13467				}
13468				if(!externbook) externbook = {Name: "SH33TJSERRY"};
13469				stack.push(externbook.Name);
13470				break;
13471
13472			case 'PtgParen': /* [MS-XLS] 2.5.198.80 */
13473				var lp = '(', rp = ')';
13474				if(last_sp >= 0) {
13475					sp = "";
13476					switch(formula[0][last_sp][1][0]) {
13477						// $FlowIgnore
13478						case 2: lp = fill(" ", formula[0][last_sp][1][1]) + lp; break;
13479						// $FlowIgnore
13480						case 3: lp = fill("\r", formula[0][last_sp][1][1]) + lp; break;
13481						// $FlowIgnore
13482						case 4: rp = fill(" ", formula[0][last_sp][1][1]) + rp; break;
13483						// $FlowIgnore
13484						case 5: rp = fill("\r", formula[0][last_sp][1][1]) + rp; break;
13485						default:
13486							// $FlowIgnore
13487							if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + formula[0][last_sp][1][0]);
13488					}
13489					last_sp = -1;
13490				}
13491				stack.push(lp + stack.pop() + rp); break;
13492
13493			case 'PtgRefErr': /* [MS-XLS] 2.5.198.86 */
13494				stack.push('#REF!'); break;
13495
13496			case 'PtgRefErr3d': /* [MS-XLS] 2.5.198.87 */
13497				stack.push('#REF!'); break;
13498
13499			case 'PtgExp': /* [MS-XLS] 2.5.198.58 TODO */
13500				c = {c:(f[1][1]/*:any*/),r:(f[1][0]/*:any*/)};
13501				var q = ({c: cell.c, r:cell.r}/*:any*/);
13502				if(supbooks.sharedf[encode_cell(c)]) {
13503					var parsedf = (supbooks.sharedf[encode_cell(c)]);
13504					stack.push(stringify_formula(parsedf, _range, q, supbooks, opts));
13505				} else {
13506					var fnd = false;
13507					for(e1=0;e1!=supbooks.arrayf.length; ++e1) {
13508						/* TODO: should be something like range_has */
13509						e2 = supbooks.arrayf[e1];
13510						if(c.c < e2[0].s.c || c.c > e2[0].e.c) continue;
13511						if(c.r < e2[0].s.r || c.r > e2[0].e.r) continue;
13512						stack.push(stringify_formula(e2[1], _range, q, supbooks, opts));
13513						fnd = true;
13514						break;
13515					}
13516					if(!fnd) stack.push(/*::String(*/f[1]/*::)*/);
13517				}
13518				break;
13519
13520			case 'PtgArray': /* [MS-XLS] 2.5.198.32 TODO */
13521				stack.push("{" + stringify_array(/*::(*/f[1]/*:: :any)*/) + "}");
13522				break;
13523
13524			case 'PtgMemArea': /* [MS-XLS] 2.5.198.70 TODO: confirm this is a non-display */
13525				//stack.push("(" + f[2].map(encode_range).join(",") + ")");
13526				break;
13527
13528			case 'PtgAttrSpace': /* [MS-XLS] 2.5.198.38 */
13529			case 'PtgAttrSpaceSemi': /* [MS-XLS] 2.5.198.39 */
13530				last_sp = ff;
13531				break;
13532
13533			case 'PtgTbl': /* [MS-XLS] 2.5.198.92 TODO */
13534				break;
13535
13536			case 'PtgMemErr': /* [MS-XLS] 2.5.198.71 */
13537				break;
13538
13539			case 'PtgMissArg': /* [MS-XLS] 2.5.198.74 */
13540				stack.push("");
13541				break;
13542
13543			case 'PtgAreaErr': /* [MS-XLS] 2.5.198.29 */
13544				stack.push("#REF!"); break;
13545
13546			case 'PtgAreaErr3d': /* [MS-XLS] 2.5.198.30 */
13547				stack.push("#REF!"); break;
13548
13549			case 'PtgList': /* [MS-XLSB] 2.5.97.52 */
13550				// $FlowIgnore
13551				stack.push("Table" + f[1].idx + "[#" + f[1].rt + "]");
13552				break;
13553
13554			case 'PtgMemAreaN':
13555			case 'PtgMemNoMemN':
13556			case 'PtgAttrNoop':
13557			case 'PtgSheet':
13558			case 'PtgEndSheet':
13559				break;
13560
13561			case 'PtgMemFunc': /* [MS-XLS] 2.5.198.72 TODO */
13562				break;
13563			case 'PtgMemNoMem': /* [MS-XLS] 2.5.198.73 TODO */
13564				break;
13565
13566			case 'PtgElfCol': /* [MS-XLS] 2.5.198.46 */
13567			case 'PtgElfColS': /* [MS-XLS] 2.5.198.47 */
13568			case 'PtgElfColSV': /* [MS-XLS] 2.5.198.48 */
13569			case 'PtgElfColV': /* [MS-XLS] 2.5.198.49 */
13570			case 'PtgElfLel': /* [MS-XLS] 2.5.198.50 */
13571			case 'PtgElfRadical': /* [MS-XLS] 2.5.198.51 */
13572			case 'PtgElfRadicalLel': /* [MS-XLS] 2.5.198.52 */
13573			case 'PtgElfRadicalS': /* [MS-XLS] 2.5.198.53 */
13574			case 'PtgElfRw': /* [MS-XLS] 2.5.198.54 */
13575			case 'PtgElfRwV': /* [MS-XLS] 2.5.198.55 */
13576				throw new Error("Unsupported ELFs");
13577
13578			case 'PtgSxName': /* [MS-XLS] 2.5.198.91 TODO -- find a test case */
13579				throw new Error('Unrecognized Formula Token: ' + String(f));
13580			default: throw new Error('Unrecognized Formula Token: ' + String(f));
13581		}
13582		var PtgNonDisp = ['PtgAttrSpace', 'PtgAttrSpaceSemi', 'PtgAttrGoto'];
13583		if(opts.biff != 3) if(last_sp >= 0 && PtgNonDisp.indexOf(formula[0][ff][0]) == -1) {
13584			f = formula[0][last_sp];
13585			var _left = true;
13586			switch(f[1][0]) {
13587				/* note: some bad XLSB files omit the PtgParen */
13588				case 4: _left = false;
13589				/* falls through */
13590				case 0:
13591					// $FlowIgnore
13592					sp = fill(" ", f[1][1]); break;
13593				case 5: _left = false;
13594				/* falls through */
13595				case 1:
13596					// $FlowIgnore
13597					sp = fill("\r", f[1][1]); break;
13598				default:
13599					sp = "";
13600					// $FlowIgnore
13601					if(opts.WTF) throw new Error("Unexpected PtgAttrSpaceType " + f[1][0]);
13602			}
13603			stack.push((_left ? sp : "") + stack.pop() + (_left ? "" : sp));
13604			last_sp = -1;
13605		}
13606	}
13607	if(stack.length > 1 && opts.WTF) throw new Error("bad formula stack");
13608	if(stack[0] == "TRUE") return true; if(stack[0] == "FALSE") return false;
13609	return stack[0];
13610}
13611
13612/* [MS-XLS] 2.5.198.1 TODO */
13613function parse_ArrayParsedFormula(blob, length, opts/*::, ref*/) {
13614	var target = blob.l + length, len = opts.biff == 2 ? 1 : 2;
13615	var rgcb, cce = blob.read_shift(len); // length of rgce
13616	if(cce == 0xFFFF) return [[],parsenoop(blob, length-2)];
13617	var rgce = parse_Rgce(blob, cce, opts);
13618	if(length !== cce + len) rgcb = parse_RgbExtra(blob, length - cce - len, rgce, opts);
13619	blob.l = target;
13620	return [rgce, rgcb];
13621}
13622
13623/* [MS-XLS] 2.5.198.3 TODO */
13624function parse_XLSCellParsedFormula(blob, length, opts) {
13625	var target = blob.l + length, len = opts.biff == 2 ? 1 : 2;
13626	var rgcb, cce = blob.read_shift(len); // length of rgce
13627	if(cce == 0xFFFF) return [[],parsenoop(blob, length-2)];
13628	var rgce = parse_Rgce(blob, cce, opts);
13629	if(length !== cce + len) rgcb = parse_RgbExtra(blob, length - cce - len, rgce, opts);
13630	blob.l = target;
13631	return [rgce, rgcb];
13632}
13633
13634/* [MS-XLS] 2.5.198.21 */
13635function parse_NameParsedFormula(blob, length, opts, cce) {
13636	var target = blob.l + length;
13637	var rgce = parse_Rgce(blob, cce, opts);
13638	var rgcb;
13639	if(target !== blob.l) rgcb = parse_RgbExtra(blob, target - blob.l, rgce, opts);
13640	return [rgce, rgcb];
13641}
13642
13643/* [MS-XLS] 2.5.198.118 TODO */
13644function parse_SharedParsedFormula(blob, length, opts) {
13645	var target = blob.l + length;
13646	var rgcb, cce = blob.read_shift(2); // length of rgce
13647	var rgce = parse_Rgce(blob, cce, opts);
13648	if(cce == 0xFFFF) return [[],parsenoop(blob, length-2)];
13649	if(length !== cce + 2) rgcb = parse_RgbExtra(blob, target - cce - 2, rgce, opts);
13650	return [rgce, rgcb];
13651}
13652
13653/* [MS-XLS] 2.5.133 TODO: how to emit empty strings? */
13654function parse_FormulaValue(blob/*::, length*/) {
13655	var b;
13656	if(__readUInt16LE(blob,blob.l + 6) !== 0xFFFF) return [parse_Xnum(blob),'n'];
13657	switch(blob[blob.l]) {
13658		case 0x00: blob.l += 8; return ["String", 's'];
13659		case 0x01: b = blob[blob.l+2] === 0x1; blob.l += 8; return [b,'b'];
13660		case 0x02: b = blob[blob.l+2]; blob.l += 8; return [b,'e'];
13661		case 0x03: blob.l += 8; return ["",'s'];
13662	}
13663	return [];
13664}
13665function write_FormulaValue(value) {
13666	if(value == null) {
13667		// Blank String Value
13668		var o = new_buf(8);
13669		o.write_shift(1, 0x03);
13670		o.write_shift(1, 0);
13671		o.write_shift(2, 0);
13672		o.write_shift(2, 0);
13673		o.write_shift(2, 0xFFFF);
13674		return o;
13675	} else if(typeof value == "number") return write_Xnum(value);
13676	return write_Xnum(0);
13677}
13678
13679/* [MS-XLS] 2.4.127 TODO */
13680function parse_Formula(blob, length, opts) {
13681	var end = blob.l + length;
13682	var cell = parse_XLSCell(blob, 6);
13683	if(opts.biff == 2) ++blob.l;
13684	var val = parse_FormulaValue(blob,8);
13685	var flags = blob.read_shift(1);
13686	if(opts.biff != 2) {
13687		blob.read_shift(1);
13688		if(opts.biff >= 5) {
13689			/*var chn = */blob.read_shift(4);
13690		}
13691	}
13692	var cbf = parse_XLSCellParsedFormula(blob, end - blob.l, opts);
13693	return {cell:cell, val:val[0], formula:cbf, shared: (flags >> 3) & 1, tt:val[1]};
13694}
13695function write_Formula(cell/*:Cell*/, R/*:number*/, C/*:number*/, opts, os/*:number*/) {
13696	// Cell
13697	var o1 = write_XLSCell(R, C, os);
13698
13699	// FormulaValue
13700	var o2 = write_FormulaValue(cell.v);
13701
13702	// flags + cache
13703	var o3 = new_buf(6);
13704	var flags = 0x01 | 0x20;
13705	o3.write_shift(2, flags);
13706	o3.write_shift(4, 0);
13707
13708	// CellParsedFormula
13709	var bf = new_buf(cell.bf.length);
13710	for(var i = 0; i < cell.bf.length; ++i) bf[i] = cell.bf[i];
13711
13712	var out = bconcat([o1, o2, o3, bf]);
13713	return out;
13714}
13715
13716
13717/* XLSB Parsed Formula records have the same shape */
13718function parse_XLSBParsedFormula(data, length, opts) {
13719	var cce = data.read_shift(4);
13720	var rgce = parse_Rgce(data, cce, opts);
13721	var cb = data.read_shift(4);
13722	var rgcb = cb > 0 ? parse_RgbExtra(data, cb, rgce, opts) : null;
13723	return [rgce, rgcb];
13724}
13725
13726/* [MS-XLSB] 2.5.97.1 ArrayParsedFormula */
13727var parse_XLSBArrayParsedFormula = parse_XLSBParsedFormula;
13728/* [MS-XLSB] 2.5.97.4 CellParsedFormula */
13729var parse_XLSBCellParsedFormula = parse_XLSBParsedFormula;
13730/* [MS-XLSB] 2.5.97.8 DVParsedFormula */
13731//var parse_XLSBDVParsedFormula = parse_XLSBParsedFormula;
13732/* [MS-XLSB] 2.5.97.9 FRTParsedFormula */
13733//var parse_XLSBFRTParsedFormula = parse_XLSBParsedFormula2;
13734/* [MS-XLSB] 2.5.97.12 NameParsedFormula */
13735var parse_XLSBNameParsedFormula = parse_XLSBParsedFormula;
13736/* [MS-XLSB] 2.5.97.98 SharedParsedFormula */
13737var parse_XLSBSharedParsedFormula = parse_XLSBParsedFormula;
13738
13739/* Writes a PtgNum or PtgInt */
13740function write_XLSBFormulaNum(val/*:number*/) {
13741	if((val | 0) == val && val < Math.pow(2,16) && val >= 0) {
13742		var oint = new_buf(11);
13743		oint.write_shift(4, 3);
13744		oint.write_shift(1, 0x1e);
13745		oint.write_shift(2, val);
13746		oint.write_shift(4, 0);
13747		return oint;
13748	}
13749
13750	var num = new_buf(17);
13751	num.write_shift(4, 11);
13752	num.write_shift(1, 0x1f);
13753	num.write_shift(8, val);
13754	num.write_shift(4, 0);
13755	return num;
13756}
13757/* Writes a PtgErr */
13758function write_XLSBFormulaErr(val/*:number*/) {
13759	var oint = new_buf(10);
13760	oint.write_shift(4, 2);
13761	oint.write_shift(1, 0x1C);
13762	oint.write_shift(1, val);
13763	oint.write_shift(4, 0);
13764	return oint;
13765}
13766/* Writes a PtgBool */
13767function write_XLSBFormulaBool(val/*:boolean*/) {
13768	var oint = new_buf(10);
13769	oint.write_shift(4, 2);
13770	oint.write_shift(1, 0x1D);
13771	oint.write_shift(1, val?1:0);
13772	oint.write_shift(4, 0);
13773	return oint;
13774}
13775
13776/* Writes a PtgStr */
13777function write_XLSBFormulaStr(val/*:string*/) {
13778	var preamble = new_buf(7);
13779	preamble.write_shift(4, 3 + 2 * val.length);
13780	preamble.write_shift(1, 0x17);
13781	preamble.write_shift(2, val.length);
13782
13783	var body = new_buf(2 * val.length);
13784	body.write_shift(2 * val.length, val, "utf16le");
13785
13786	var postamble = new_buf(4);
13787	postamble.write_shift(4, 0);
13788
13789	return bconcat([preamble, body, postamble]);
13790}
13791
13792/* Writes a PtgRef */
13793function write_XLSBFormulaRef(str) {
13794	var cell = decode_cell(str);
13795	var out = new_buf(15);
13796	out.write_shift(4, 7);
13797	out.write_shift(1, 0x04 | ((1)<<5));
13798	out.write_shift(4, cell.r);
13799	out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
13800	out.write_shift(4, 0);
13801
13802	return out;
13803}
13804
13805/* Writes a PtgRef3d */
13806function write_XLSBFormulaRef3D(str, wb) {
13807	var lastbang = str.lastIndexOf("!");
13808	var sname = str.slice(0, lastbang);
13809	str = str.slice(lastbang+1);
13810	var cell = decode_cell(str);
13811	if(sname.charAt(0) == "'") sname = sname.slice(1, -1).replace(/''/g, "'");
13812
13813	var out = new_buf(17);
13814	out.write_shift(4, 9);
13815	out.write_shift(1, 0x1A | ((1)<<5));
13816	out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
13817	out.write_shift(4, cell.r);
13818	out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
13819	out.write_shift(4, 0);
13820
13821	return out;
13822}
13823
13824/* Writes a PtgRefErr3d */
13825function write_XLSBFormulaRefErr3D(str, wb) {
13826	var lastbang = str.lastIndexOf("!");
13827	var sname = str.slice(0, lastbang);
13828	str = str.slice(lastbang+1);
13829	if(sname.charAt(0) == "'") sname = sname.slice(1, -1).replace(/''/g, "'");
13830
13831	var out = new_buf(17);
13832	out.write_shift(4, 9);
13833	out.write_shift(1, 0x1C | ((1)<<5));
13834	out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
13835	out.write_shift(4, 0);
13836	out.write_shift(2, 0); // <== ColRelShort
13837	out.write_shift(4, 0);
13838
13839	return out;
13840}
13841
13842/* Writes a single sheet range [PtgRef PtgRef PtgRange] */
13843function write_XLSBFormulaRange(_str) {
13844	var parts = _str.split(":"), str = parts[0];
13845
13846	var out = new_buf(23);
13847	out.write_shift(4, 15);
13848
13849	/* start cell */
13850	str = parts[0]; var cell = decode_cell(str);
13851	out.write_shift(1, 0x04 | ((1)<<5));
13852	out.write_shift(4, cell.r);
13853	out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
13854	out.write_shift(4, 0);
13855
13856	/* end cell */
13857	str = parts[1]; cell = decode_cell(str);
13858	out.write_shift(1, 0x04 | ((1)<<5));
13859	out.write_shift(4, cell.r);
13860	out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
13861	out.write_shift(4, 0);
13862
13863	/* PtgRange */
13864	out.write_shift(1, 0x11);
13865
13866	out.write_shift(4, 0);
13867
13868	return out;
13869}
13870
13871/* Writes a range with explicit sheet name [PtgRef3D PtgRef3D PtgRange] */
13872function write_XLSBFormulaRangeWS(_str, wb) {
13873	var lastbang = _str.lastIndexOf("!");
13874	var sname = _str.slice(0, lastbang);
13875	_str = _str.slice(lastbang+1);
13876	if(sname.charAt(0) == "'") sname = sname.slice(1, -1).replace(/''/g, "'");
13877	var parts = _str.split(":"); str = parts[0];
13878
13879	var out = new_buf(27);
13880	out.write_shift(4, 19);
13881
13882	/* start cell */
13883	var str = parts[0], cell = decode_cell(str);
13884	out.write_shift(1, 0x1A | ((1)<<5));
13885	out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
13886	out.write_shift(4, cell.r);
13887	out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
13888
13889	/* end cell */
13890	str = parts[1]; cell = decode_cell(str);
13891	out.write_shift(1, 0x1A | ((1)<<5));
13892	out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
13893	out.write_shift(4, cell.r);
13894	out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
13895
13896	/* PtgRange */
13897	out.write_shift(1, 0x11);
13898
13899	out.write_shift(4, 0);
13900
13901	return out;
13902}
13903
13904/* Writes a range with explicit sheet name [PtgArea3d] */
13905function write_XLSBFormulaArea3D(_str, wb) {
13906	var lastbang = _str.lastIndexOf("!");
13907	var sname = _str.slice(0, lastbang);
13908	_str = _str.slice(lastbang+1);
13909	if(sname.charAt(0) == "'") sname = sname.slice(1, -1).replace(/''/g, "'");
13910	var range = decode_range(_str);
13911
13912	var out = new_buf(23);
13913	out.write_shift(4, 15);
13914
13915	out.write_shift(1, 0x1B | ((1)<<5));
13916	out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
13917	out.write_shift(4, range.s.r);
13918	out.write_shift(4, range.e.r);
13919	out.write_shift(2, range.s.c);
13920	out.write_shift(2, range.e.c);
13921
13922	out.write_shift(4, 0);
13923
13924	return out;
13925}
13926
13927
13928/* General Formula */
13929function write_XLSBFormula(val/*:string|number*/, wb) {
13930	if(typeof val == "number") return write_XLSBFormulaNum(val);
13931	if(typeof val == "boolean") return write_XLSBFormulaBool(val);
13932	if(/^#(DIV\/0!|GETTING_DATA|N\/A|NAME\?|NULL!|NUM!|REF!|VALUE!)$/.test(val)) return write_XLSBFormulaErr(+RBErr[val]);
13933	if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef(val);
13934	if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRange(val);
13935	if(val.match(/^#REF!\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaArea3D(val, wb);
13936	if(val.match(/^(?:'[^\\\/?*\[\]:]*'|[^'][^\\\/?*\[\]:'`~!@#$%^()\-=+{}|;,<.>]*)!\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef3D(val, wb);
13937	if(val.match(/^(?:'[^\\\/?*\[\]:]*'|[^'][^\\\/?*\[\]:'`~!@#$%^()\-=+{}|;,<.>]*)!\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRangeWS(val, wb);
13938	if(/^(?:'[^\\\/?*\[\]:]*'|[^'][^\\\/?*\[\]:'`~!@#$%^()\-=+{}|;,<.>]*)!#REF!$/.test(val)) return write_XLSBFormulaRefErr3D(val, wb);
13939	if(/^".*"$/.test(val)) return write_XLSBFormulaStr(val);
13940	if(/^[+-]\d+$/.test(val)) return write_XLSBFormulaNum(parseInt(val, 10));
13941	throw "Formula |" + val + "| not supported for XLSB";
13942}
13943var write_XLSBNameParsedFormula = write_XLSBFormula;
13944var Cetab = {
13945  0: "BEEP",
13946  1: "OPEN",
13947  2: "OPEN.LINKS",
13948  3: "CLOSE.ALL",
13949  4: "SAVE",
13950  5: "SAVE.AS",
13951  6: "FILE.DELETE",
13952  7: "PAGE.SETUP",
13953  8: "PRINT",
13954  9: "PRINTER.SETUP",
13955  10: "QUIT",
13956  11: "NEW.WINDOW",
13957  12: "ARRANGE.ALL",
13958  13: "WINDOW.SIZE",
13959  14: "WINDOW.MOVE",
13960  15: "FULL",
13961  16: "CLOSE",
13962  17: "RUN",
13963  22: "SET.PRINT.AREA",
13964  23: "SET.PRINT.TITLES",
13965  24: "SET.PAGE.BREAK",
13966  25: "REMOVE.PAGE.BREAK",
13967  26: "FONT",
13968  27: "DISPLAY",
13969  28: "PROTECT.DOCUMENT",
13970  29: "PRECISION",
13971  30: "A1.R1C1",
13972  31: "CALCULATE.NOW",
13973  32: "CALCULATION",
13974  34: "DATA.FIND",
13975  35: "EXTRACT",
13976  36: "DATA.DELETE",
13977  37: "SET.DATABASE",
13978  38: "SET.CRITERIA",
13979  39: "SORT",
13980  40: "DATA.SERIES",
13981  41: "TABLE",
13982  42: "FORMAT.NUMBER",
13983  43: "ALIGNMENT",
13984  44: "STYLE",
13985  45: "BORDER",
13986  46: "CELL.PROTECTION",
13987  47: "COLUMN.WIDTH",
13988  48: "UNDO",
13989  49: "CUT",
13990  50: "COPY",
13991  51: "PASTE",
13992  52: "CLEAR",
13993  53: "PASTE.SPECIAL",
13994  54: "EDIT.DELETE",
13995  55: "INSERT",
13996  56: "FILL.RIGHT",
13997  57: "FILL.DOWN",
13998  61: "DEFINE.NAME",
13999  62: "CREATE.NAMES",
14000  63: "FORMULA.GOTO",
14001  64: "FORMULA.FIND",
14002  65: "SELECT.LAST.CELL",
14003  66: "SHOW.ACTIVE.CELL",
14004  67: "GALLERY.AREA",
14005  68: "GALLERY.BAR",
14006  69: "GALLERY.COLUMN",
14007  70: "GALLERY.LINE",
14008  71: "GALLERY.PIE",
14009  72: "GALLERY.SCATTER",
14010  73: "COMBINATION",
14011  74: "PREFERRED",
14012  75: "ADD.OVERLAY",
14013  76: "GRIDLINES",
14014  77: "SET.PREFERRED",
14015  78: "AXES",
14016  79: "LEGEND",
14017  80: "ATTACH.TEXT",
14018  81: "ADD.ARROW",
14019  82: "SELECT.CHART",
14020  83: "SELECT.PLOT.AREA",
14021  84: "PATTERNS",
14022  85: "MAIN.CHART",
14023  86: "OVERLAY",
14024  87: "SCALE",
14025  88: "FORMAT.LEGEND",
14026  89: "FORMAT.TEXT",
14027  90: "EDIT.REPEAT",
14028  91: "PARSE",
14029  92: "JUSTIFY",
14030  93: "HIDE",
14031  94: "UNHIDE",
14032  95: "WORKSPACE",
14033  96: "FORMULA",
14034  97: "FORMULA.FILL",
14035  98: "FORMULA.ARRAY",
14036  99: "DATA.FIND.NEXT",
14037  100: "DATA.FIND.PREV",
14038  101: "FORMULA.FIND.NEXT",
14039  102: "FORMULA.FIND.PREV",
14040  103: "ACTIVATE",
14041  104: "ACTIVATE.NEXT",
14042  105: "ACTIVATE.PREV",
14043  106: "UNLOCKED.NEXT",
14044  107: "UNLOCKED.PREV",
14045  108: "COPY.PICTURE",
14046  109: "SELECT",
14047  110: "DELETE.NAME",
14048  111: "DELETE.FORMAT",
14049  112: "VLINE",
14050  113: "HLINE",
14051  114: "VPAGE",
14052  115: "HPAGE",
14053  116: "VSCROLL",
14054  117: "HSCROLL",
14055  118: "ALERT",
14056  119: "NEW",
14057  120: "CANCEL.COPY",
14058  121: "SHOW.CLIPBOARD",
14059  122: "MESSAGE",
14060  124: "PASTE.LINK",
14061  125: "APP.ACTIVATE",
14062  126: "DELETE.ARROW",
14063  127: "ROW.HEIGHT",
14064  128: "FORMAT.MOVE",
14065  129: "FORMAT.SIZE",
14066  130: "FORMULA.REPLACE",
14067  131: "SEND.KEYS",
14068  132: "SELECT.SPECIAL",
14069  133: "APPLY.NAMES",
14070  134: "REPLACE.FONT",
14071  135: "FREEZE.PANES",
14072  136: "SHOW.INFO",
14073  137: "SPLIT",
14074  138: "ON.WINDOW",
14075  139: "ON.DATA",
14076  140: "DISABLE.INPUT",
14077  142: "OUTLINE",
14078  143: "LIST.NAMES",
14079  144: "FILE.CLOSE",
14080  145: "SAVE.WORKBOOK",
14081  146: "DATA.FORM",
14082  147: "COPY.CHART",
14083  148: "ON.TIME",
14084  149: "WAIT",
14085  150: "FORMAT.FONT",
14086  151: "FILL.UP",
14087  152: "FILL.LEFT",
14088  153: "DELETE.OVERLAY",
14089  155: "SHORT.MENUS",
14090  159: "SET.UPDATE.STATUS",
14091  161: "COLOR.PALETTE",
14092  162: "DELETE.STYLE",
14093  163: "WINDOW.RESTORE",
14094  164: "WINDOW.MAXIMIZE",
14095  166: "CHANGE.LINK",
14096  167: "CALCULATE.DOCUMENT",
14097  168: "ON.KEY",
14098  169: "APP.RESTORE",
14099  170: "APP.MOVE",
14100  171: "APP.SIZE",
14101  172: "APP.MINIMIZE",
14102  173: "APP.MAXIMIZE",
14103  174: "BRING.TO.FRONT",
14104  175: "SEND.TO.BACK",
14105  185: "MAIN.CHART.TYPE",
14106  186: "OVERLAY.CHART.TYPE",
14107  187: "SELECT.END",
14108  188: "OPEN.MAIL",
14109  189: "SEND.MAIL",
14110  190: "STANDARD.FONT",
14111  191: "CONSOLIDATE",
14112  192: "SORT.SPECIAL",
14113  193: "GALLERY.3D.AREA",
14114  194: "GALLERY.3D.COLUMN",
14115  195: "GALLERY.3D.LINE",
14116  196: "GALLERY.3D.PIE",
14117  197: "VIEW.3D",
14118  198: "GOAL.SEEK",
14119  199: "WORKGROUP",
14120  200: "FILL.GROUP",
14121  201: "UPDATE.LINK",
14122  202: "PROMOTE",
14123  203: "DEMOTE",
14124  204: "SHOW.DETAIL",
14125  206: "UNGROUP",
14126  207: "OBJECT.PROPERTIES",
14127  208: "SAVE.NEW.OBJECT",
14128  209: "SHARE",
14129  210: "SHARE.NAME",
14130  211: "DUPLICATE",
14131  212: "APPLY.STYLE",
14132  213: "ASSIGN.TO.OBJECT",
14133  214: "OBJECT.PROTECTION",
14134  215: "HIDE.OBJECT",
14135  216: "SET.EXTRACT",
14136  217: "CREATE.PUBLISHER",
14137  218: "SUBSCRIBE.TO",
14138  219: "ATTRIBUTES",
14139  220: "SHOW.TOOLBAR",
14140  222: "PRINT.PREVIEW",
14141  223: "EDIT.COLOR",
14142  224: "SHOW.LEVELS",
14143  225: "FORMAT.MAIN",
14144  226: "FORMAT.OVERLAY",
14145  227: "ON.RECALC",
14146  228: "EDIT.SERIES",
14147  229: "DEFINE.STYLE",
14148  240: "LINE.PRINT",
14149  243: "ENTER.DATA",
14150  249: "GALLERY.RADAR",
14151  250: "MERGE.STYLES",
14152  251: "EDITION.OPTIONS",
14153  252: "PASTE.PICTURE",
14154  253: "PASTE.PICTURE.LINK",
14155  254: "SPELLING",
14156  256: "ZOOM",
14157  259: "INSERT.OBJECT",
14158  260: "WINDOW.MINIMIZE",
14159  265: "SOUND.NOTE",
14160  266: "SOUND.PLAY",
14161  267: "FORMAT.SHAPE",
14162  268: "EXTEND.POLYGON",
14163  269: "FORMAT.AUTO",
14164  272: "GALLERY.3D.BAR",
14165  273: "GALLERY.3D.SURFACE",
14166  274: "FILL.AUTO",
14167  276: "CUSTOMIZE.TOOLBAR",
14168  277: "ADD.TOOL",
14169  278: "EDIT.OBJECT",
14170  279: "ON.DOUBLECLICK",
14171  280: "ON.ENTRY",
14172  281: "WORKBOOK.ADD",
14173  282: "WORKBOOK.MOVE",
14174  283: "WORKBOOK.COPY",
14175  284: "WORKBOOK.OPTIONS",
14176  285: "SAVE.WORKSPACE",
14177  288: "CHART.WIZARD",
14178  289: "DELETE.TOOL",
14179  290: "MOVE.TOOL",
14180  291: "WORKBOOK.SELECT",
14181  292: "WORKBOOK.ACTIVATE",
14182  293: "ASSIGN.TO.TOOL",
14183  295: "COPY.TOOL",
14184  296: "RESET.TOOL",
14185  297: "CONSTRAIN.NUMERIC",
14186  298: "PASTE.TOOL",
14187  302: "WORKBOOK.NEW",
14188  305: "SCENARIO.CELLS",
14189  306: "SCENARIO.DELETE",
14190  307: "SCENARIO.ADD",
14191  308: "SCENARIO.EDIT",
14192  309: "SCENARIO.SHOW",
14193  310: "SCENARIO.SHOW.NEXT",
14194  311: "SCENARIO.SUMMARY",
14195  312: "PIVOT.TABLE.WIZARD",
14196  313: "PIVOT.FIELD.PROPERTIES",
14197  314: "PIVOT.FIELD",
14198  315: "PIVOT.ITEM",
14199  316: "PIVOT.ADD.FIELDS",
14200  318: "OPTIONS.CALCULATION",
14201  319: "OPTIONS.EDIT",
14202  320: "OPTIONS.VIEW",
14203  321: "ADDIN.MANAGER",
14204  322: "MENU.EDITOR",
14205  323: "ATTACH.TOOLBARS",
14206  324: "VBAActivate",
14207  325: "OPTIONS.CHART",
14208  328: "VBA.INSERT.FILE",
14209  330: "VBA.PROCEDURE.DEFINITION",
14210  336: "ROUTING.SLIP",
14211  338: "ROUTE.DOCUMENT",
14212  339: "MAIL.LOGON",
14213  342: "INSERT.PICTURE",
14214  343: "EDIT.TOOL",
14215  344: "GALLERY.DOUGHNUT",
14216  350: "CHART.TREND",
14217  352: "PIVOT.ITEM.PROPERTIES",
14218  354: "WORKBOOK.INSERT",
14219  355: "OPTIONS.TRANSITION",
14220  356: "OPTIONS.GENERAL",
14221  370: "FILTER.ADVANCED",
14222  373: "MAIL.ADD.MAILER",
14223  374: "MAIL.DELETE.MAILER",
14224  375: "MAIL.REPLY",
14225  376: "MAIL.REPLY.ALL",
14226  377: "MAIL.FORWARD",
14227  378: "MAIL.NEXT.LETTER",
14228  379: "DATA.LABEL",
14229  380: "INSERT.TITLE",
14230  381: "FONT.PROPERTIES",
14231  382: "MACRO.OPTIONS",
14232  383: "WORKBOOK.HIDE",
14233  384: "WORKBOOK.UNHIDE",
14234  385: "WORKBOOK.DELETE",
14235  386: "WORKBOOK.NAME",
14236  388: "GALLERY.CUSTOM",
14237  390: "ADD.CHART.AUTOFORMAT",
14238  391: "DELETE.CHART.AUTOFORMAT",
14239  392: "CHART.ADD.DATA",
14240  393: "AUTO.OUTLINE",
14241  394: "TAB.ORDER",
14242  395: "SHOW.DIALOG",
14243  396: "SELECT.ALL",
14244  397: "UNGROUP.SHEETS",
14245  398: "SUBTOTAL.CREATE",
14246  399: "SUBTOTAL.REMOVE",
14247  400: "RENAME.OBJECT",
14248  412: "WORKBOOK.SCROLL",
14249  413: "WORKBOOK.NEXT",
14250  414: "WORKBOOK.PREV",
14251  415: "WORKBOOK.TAB.SPLIT",
14252  416: "FULL.SCREEN",
14253  417: "WORKBOOK.PROTECT",
14254  420: "SCROLLBAR.PROPERTIES",
14255  421: "PIVOT.SHOW.PAGES",
14256  422: "TEXT.TO.COLUMNS",
14257  423: "FORMAT.CHARTTYPE",
14258  424: "LINK.FORMAT",
14259  425: "TRACER.DISPLAY",
14260  430: "TRACER.NAVIGATE",
14261  431: "TRACER.CLEAR",
14262  432: "TRACER.ERROR",
14263  433: "PIVOT.FIELD.GROUP",
14264  434: "PIVOT.FIELD.UNGROUP",
14265  435: "CHECKBOX.PROPERTIES",
14266  436: "LABEL.PROPERTIES",
14267  437: "LISTBOX.PROPERTIES",
14268  438: "EDITBOX.PROPERTIES",
14269  439: "PIVOT.REFRESH",
14270  440: "LINK.COMBO",
14271  441: "OPEN.TEXT",
14272  442: "HIDE.DIALOG",
14273  443: "SET.DIALOG.FOCUS",
14274  444: "ENABLE.OBJECT",
14275  445: "PUSHBUTTON.PROPERTIES",
14276  446: "SET.DIALOG.DEFAULT",
14277  447: "FILTER",
14278  448: "FILTER.SHOW.ALL",
14279  449: "CLEAR.OUTLINE",
14280  450: "FUNCTION.WIZARD",
14281  451: "ADD.LIST.ITEM",
14282  452: "SET.LIST.ITEM",
14283  453: "REMOVE.LIST.ITEM",
14284  454: "SELECT.LIST.ITEM",
14285  455: "SET.CONTROL.VALUE",
14286  456: "SAVE.COPY.AS",
14287  458: "OPTIONS.LISTS.ADD",
14288  459: "OPTIONS.LISTS.DELETE",
14289  460: "SERIES.AXES",
14290  461: "SERIES.X",
14291  462: "SERIES.Y",
14292  463: "ERRORBAR.X",
14293  464: "ERRORBAR.Y",
14294  465: "FORMAT.CHART",
14295  466: "SERIES.ORDER",
14296  467: "MAIL.LOGOFF",
14297  468: "CLEAR.ROUTING.SLIP",
14298  469: "APP.ACTIVATE.MICROSOFT",
14299  470: "MAIL.EDIT.MAILER",
14300  471: "ON.SHEET",
14301  472: "STANDARD.WIDTH",
14302  473: "SCENARIO.MERGE",
14303  474: "SUMMARY.INFO",
14304  475: "FIND.FILE",
14305  476: "ACTIVE.CELL.FONT",
14306  477: "ENABLE.TIPWIZARD",
14307  478: "VBA.MAKE.ADDIN",
14308  480: "INSERTDATATABLE",
14309  481: "WORKGROUP.OPTIONS",
14310  482: "MAIL.SEND.MAILER",
14311  485: "AUTOCORRECT",
14312  489: "POST.DOCUMENT",
14313  491: "PICKLIST",
14314  493: "VIEW.SHOW",
14315  494: "VIEW.DEFINE",
14316  495: "VIEW.DELETE",
14317  509: "SHEET.BACKGROUND",
14318  510: "INSERT.MAP.OBJECT",
14319  511: "OPTIONS.MENONO",
14320  517: "MSOCHECKS",
14321  518: "NORMAL",
14322  519: "LAYOUT",
14323  520: "RM.PRINT.AREA",
14324  521: "CLEAR.PRINT.AREA",
14325  522: "ADD.PRINT.AREA",
14326  523: "MOVE.BRK",
14327  545: "HIDECURR.NOTE",
14328  546: "HIDEALL.NOTES",
14329  547: "DELETE.NOTE",
14330  548: "TRAVERSE.NOTES",
14331  549: "ACTIVATE.NOTES",
14332  620: "PROTECT.REVISIONS",
14333  621: "UNPROTECT.REVISIONS",
14334  647: "OPTIONS.ME",
14335  653: "WEB.PUBLISH",
14336  667: "NEWWEBQUERY",
14337  673: "PIVOT.TABLE.CHART",
14338  753: "OPTIONS.SAVE",
14339  755: "OPTIONS.SPELL",
14340  808: "HIDEALL.INKANNOTS"
14341};
14342var Ftab = {
14343  0: "COUNT",
14344  1: "IF",
14345  2: "ISNA",
14346  3: "ISERROR",
14347  4: "SUM",
14348  5: "AVERAGE",
14349  6: "MIN",
14350  7: "MAX",
14351  8: "ROW",
14352  9: "COLUMN",
14353  10: "NA",
14354  11: "NPV",
14355  12: "STDEV",
14356  13: "DOLLAR",
14357  14: "FIXED",
14358  15: "SIN",
14359  16: "COS",
14360  17: "TAN",
14361  18: "ATAN",
14362  19: "PI",
14363  20: "SQRT",
14364  21: "EXP",
14365  22: "LN",
14366  23: "LOG10",
14367  24: "ABS",
14368  25: "INT",
14369  26: "SIGN",
14370  27: "ROUND",
14371  28: "LOOKUP",
14372  29: "INDEX",
14373  30: "REPT",
14374  31: "MID",
14375  32: "LEN",
14376  33: "VALUE",
14377  34: "TRUE",
14378  35: "FALSE",
14379  36: "AND",
14380  37: "OR",
14381  38: "NOT",
14382  39: "MOD",
14383  40: "DCOUNT",
14384  41: "DSUM",
14385  42: "DAVERAGE",
14386  43: "DMIN",
14387  44: "DMAX",
14388  45: "DSTDEV",
14389  46: "VAR",
14390  47: "DVAR",
14391  48: "TEXT",
14392  49: "LINEST",
14393  50: "TREND",
14394  51: "LOGEST",
14395  52: "GROWTH",
14396  53: "GOTO",
14397  54: "HALT",
14398  55: "RETURN",
14399  56: "PV",
14400  57: "FV",
14401  58: "NPER",
14402  59: "PMT",
14403  60: "RATE",
14404  61: "MIRR",
14405  62: "IRR",
14406  63: "RAND",
14407  64: "MATCH",
14408  65: "DATE",
14409  66: "TIME",
14410  67: "DAY",
14411  68: "MONTH",
14412  69: "YEAR",
14413  70: "WEEKDAY",
14414  71: "HOUR",
14415  72: "MINUTE",
14416  73: "SECOND",
14417  74: "NOW",
14418  75: "AREAS",
14419  76: "ROWS",
14420  77: "COLUMNS",
14421  78: "OFFSET",
14422  79: "ABSREF",
14423  80: "RELREF",
14424  81: "ARGUMENT",
14425  82: "SEARCH",
14426  83: "TRANSPOSE",
14427  84: "ERROR",
14428  85: "STEP",
14429  86: "TYPE",
14430  87: "ECHO",
14431  88: "SET.NAME",
14432  89: "CALLER",
14433  90: "DEREF",
14434  91: "WINDOWS",
14435  92: "SERIES",
14436  93: "DOCUMENTS",
14437  94: "ACTIVE.CELL",
14438  95: "SELECTION",
14439  96: "RESULT",
14440  97: "ATAN2",
14441  98: "ASIN",
14442  99: "ACOS",
14443  100: "CHOOSE",
14444  101: "HLOOKUP",
14445  102: "VLOOKUP",
14446  103: "LINKS",
14447  104: "INPUT",
14448  105: "ISREF",
14449  106: "GET.FORMULA",
14450  107: "GET.NAME",
14451  108: "SET.VALUE",
14452  109: "LOG",
14453  110: "EXEC",
14454  111: "CHAR",
14455  112: "LOWER",
14456  113: "UPPER",
14457  114: "PROPER",
14458  115: "LEFT",
14459  116: "RIGHT",
14460  117: "EXACT",
14461  118: "TRIM",
14462  119: "REPLACE",
14463  120: "SUBSTITUTE",
14464  121: "CODE",
14465  122: "NAMES",
14466  123: "DIRECTORY",
14467  124: "FIND",
14468  125: "CELL",
14469  126: "ISERR",
14470  127: "ISTEXT",
14471  128: "ISNUMBER",
14472  129: "ISBLANK",
14473  130: "T",
14474  131: "N",
14475  132: "FOPEN",
14476  133: "FCLOSE",
14477  134: "FSIZE",
14478  135: "FREADLN",
14479  136: "FREAD",
14480  137: "FWRITELN",
14481  138: "FWRITE",
14482  139: "FPOS",
14483  140: "DATEVALUE",
14484  141: "TIMEVALUE",
14485  142: "SLN",
14486  143: "SYD",
14487  144: "DDB",
14488  145: "GET.DEF",
14489  146: "REFTEXT",
14490  147: "TEXTREF",
14491  148: "INDIRECT",
14492  149: "REGISTER",
14493  150: "CALL",
14494  151: "ADD.BAR",
14495  152: "ADD.MENU",
14496  153: "ADD.COMMAND",
14497  154: "ENABLE.COMMAND",
14498  155: "CHECK.COMMAND",
14499  156: "RENAME.COMMAND",
14500  157: "SHOW.BAR",
14501  158: "DELETE.MENU",
14502  159: "DELETE.COMMAND",
14503  160: "GET.CHART.ITEM",
14504  161: "DIALOG.BOX",
14505  162: "CLEAN",
14506  163: "MDETERM",
14507  164: "MINVERSE",
14508  165: "MMULT",
14509  166: "FILES",
14510  167: "IPMT",
14511  168: "PPMT",
14512  169: "COUNTA",
14513  170: "CANCEL.KEY",
14514  171: "FOR",
14515  172: "WHILE",
14516  173: "BREAK",
14517  174: "NEXT",
14518  175: "INITIATE",
14519  176: "REQUEST",
14520  177: "POKE",
14521  178: "EXECUTE",
14522  179: "TERMINATE",
14523  180: "RESTART",
14524  181: "HELP",
14525  182: "GET.BAR",
14526  183: "PRODUCT",
14527  184: "FACT",
14528  185: "GET.CELL",
14529  186: "GET.WORKSPACE",
14530  187: "GET.WINDOW",
14531  188: "GET.DOCUMENT",
14532  189: "DPRODUCT",
14533  190: "ISNONTEXT",
14534  191: "GET.NOTE",
14535  192: "NOTE",
14536  193: "STDEVP",
14537  194: "VARP",
14538  195: "DSTDEVP",
14539  196: "DVARP",
14540  197: "TRUNC",
14541  198: "ISLOGICAL",
14542  199: "DCOUNTA",
14543  200: "DELETE.BAR",
14544  201: "UNREGISTER",
14545  204: "USDOLLAR",
14546  205: "FINDB",
14547  206: "SEARCHB",
14548  207: "REPLACEB",
14549  208: "LEFTB",
14550  209: "RIGHTB",
14551  210: "MIDB",
14552  211: "LENB",
14553  212: "ROUNDUP",
14554  213: "ROUNDDOWN",
14555  214: "ASC",
14556  215: "DBCS",
14557  216: "RANK",
14558  219: "ADDRESS",
14559  220: "DAYS360",
14560  221: "TODAY",
14561  222: "VDB",
14562  223: "ELSE",
14563  224: "ELSE.IF",
14564  225: "END.IF",
14565  226: "FOR.CELL",
14566  227: "MEDIAN",
14567  228: "SUMPRODUCT",
14568  229: "SINH",
14569  230: "COSH",
14570  231: "TANH",
14571  232: "ASINH",
14572  233: "ACOSH",
14573  234: "ATANH",
14574  235: "DGET",
14575  236: "CREATE.OBJECT",
14576  237: "VOLATILE",
14577  238: "LAST.ERROR",
14578  239: "CUSTOM.UNDO",
14579  240: "CUSTOM.REPEAT",
14580  241: "FORMULA.CONVERT",
14581  242: "GET.LINK.INFO",
14582  243: "TEXT.BOX",
14583  244: "INFO",
14584  245: "GROUP",
14585  246: "GET.OBJECT",
14586  247: "DB",
14587  248: "PAUSE",
14588  251: "RESUME",
14589  252: "FREQUENCY",
14590  253: "ADD.TOOLBAR",
14591  254: "DELETE.TOOLBAR",
14592  255: "User",
14593  256: "RESET.TOOLBAR",
14594  257: "EVALUATE",
14595  258: "GET.TOOLBAR",
14596  259: "GET.TOOL",
14597  260: "SPELLING.CHECK",
14598  261: "ERROR.TYPE",
14599  262: "APP.TITLE",
14600  263: "WINDOW.TITLE",
14601  264: "SAVE.TOOLBAR",
14602  265: "ENABLE.TOOL",
14603  266: "PRESS.TOOL",
14604  267: "REGISTER.ID",
14605  268: "GET.WORKBOOK",
14606  269: "AVEDEV",
14607  270: "BETADIST",
14608  271: "GAMMALN",
14609  272: "BETAINV",
14610  273: "BINOMDIST",
14611  274: "CHIDIST",
14612  275: "CHIINV",
14613  276: "COMBIN",
14614  277: "CONFIDENCE",
14615  278: "CRITBINOM",
14616  279: "EVEN",
14617  280: "EXPONDIST",
14618  281: "FDIST",
14619  282: "FINV",
14620  283: "FISHER",
14621  284: "FISHERINV",
14622  285: "FLOOR",
14623  286: "GAMMADIST",
14624  287: "GAMMAINV",
14625  288: "CEILING",
14626  289: "HYPGEOMDIST",
14627  290: "LOGNORMDIST",
14628  291: "LOGINV",
14629  292: "NEGBINOMDIST",
14630  293: "NORMDIST",
14631  294: "NORMSDIST",
14632  295: "NORMINV",
14633  296: "NORMSINV",
14634  297: "STANDARDIZE",
14635  298: "ODD",
14636  299: "PERMUT",
14637  300: "POISSON",
14638  301: "TDIST",
14639  302: "WEIBULL",
14640  303: "SUMXMY2",
14641  304: "SUMX2MY2",
14642  305: "SUMX2PY2",
14643  306: "CHITEST",
14644  307: "CORREL",
14645  308: "COVAR",
14646  309: "FORECAST",
14647  310: "FTEST",
14648  311: "INTERCEPT",
14649  312: "PEARSON",
14650  313: "RSQ",
14651  314: "STEYX",
14652  315: "SLOPE",
14653  316: "TTEST",
14654  317: "PROB",
14655  318: "DEVSQ",
14656  319: "GEOMEAN",
14657  320: "HARMEAN",
14658  321: "SUMSQ",
14659  322: "KURT",
14660  323: "SKEW",
14661  324: "ZTEST",
14662  325: "LARGE",
14663  326: "SMALL",
14664  327: "QUARTILE",
14665  328: "PERCENTILE",
14666  329: "PERCENTRANK",
14667  330: "MODE",
14668  331: "TRIMMEAN",
14669  332: "TINV",
14670  334: "MOVIE.COMMAND",
14671  335: "GET.MOVIE",
14672  336: "CONCATENATE",
14673  337: "POWER",
14674  338: "PIVOT.ADD.DATA",
14675  339: "GET.PIVOT.TABLE",
14676  340: "GET.PIVOT.FIELD",
14677  341: "GET.PIVOT.ITEM",
14678  342: "RADIANS",
14679  343: "DEGREES",
14680  344: "SUBTOTAL",
14681  345: "SUMIF",
14682  346: "COUNTIF",
14683  347: "COUNTBLANK",
14684  348: "SCENARIO.GET",
14685  349: "OPTIONS.LISTS.GET",
14686  350: "ISPMT",
14687  351: "DATEDIF",
14688  352: "DATESTRING",
14689  353: "NUMBERSTRING",
14690  354: "ROMAN",
14691  355: "OPEN.DIALOG",
14692  356: "SAVE.DIALOG",
14693  357: "VIEW.GET",
14694  358: "GETPIVOTDATA",
14695  359: "HYPERLINK",
14696  360: "PHONETIC",
14697  361: "AVERAGEA",
14698  362: "MAXA",
14699  363: "MINA",
14700  364: "STDEVPA",
14701  365: "VARPA",
14702  366: "STDEVA",
14703  367: "VARA",
14704  368: "BAHTTEXT",
14705  369: "THAIDAYOFWEEK",
14706  370: "THAIDIGIT",
14707  371: "THAIMONTHOFYEAR",
14708  372: "THAINUMSOUND",
14709  373: "THAINUMSTRING",
14710  374: "THAISTRINGLENGTH",
14711  375: "ISTHAIDIGIT",
14712  376: "ROUNDBAHTDOWN",
14713  377: "ROUNDBAHTUP",
14714  378: "THAIYEAR",
14715  379: "RTD",
14716  380: "CUBEVALUE",
14717  381: "CUBEMEMBER",
14718  382: "CUBEMEMBERPROPERTY",
14719  383: "CUBERANKEDMEMBER",
14720  384: "HEX2BIN",
14721  385: "HEX2DEC",
14722  386: "HEX2OCT",
14723  387: "DEC2BIN",
14724  388: "DEC2HEX",
14725  389: "DEC2OCT",
14726  390: "OCT2BIN",
14727  391: "OCT2HEX",
14728  392: "OCT2DEC",
14729  393: "BIN2DEC",
14730  394: "BIN2OCT",
14731  395: "BIN2HEX",
14732  396: "IMSUB",
14733  397: "IMDIV",
14734  398: "IMPOWER",
14735  399: "IMABS",
14736  400: "IMSQRT",
14737  401: "IMLN",
14738  402: "IMLOG2",
14739  403: "IMLOG10",
14740  404: "IMSIN",
14741  405: "IMCOS",
14742  406: "IMEXP",
14743  407: "IMARGUMENT",
14744  408: "IMCONJUGATE",
14745  409: "IMAGINARY",
14746  410: "IMREAL",
14747  411: "COMPLEX",
14748  412: "IMSUM",
14749  413: "IMPRODUCT",
14750  414: "SERIESSUM",
14751  415: "FACTDOUBLE",
14752  416: "SQRTPI",
14753  417: "QUOTIENT",
14754  418: "DELTA",
14755  419: "GESTEP",
14756  420: "ISEVEN",
14757  421: "ISODD",
14758  422: "MROUND",
14759  423: "ERF",
14760  424: "ERFC",
14761  425: "BESSELJ",
14762  426: "BESSELK",
14763  427: "BESSELY",
14764  428: "BESSELI",
14765  429: "XIRR",
14766  430: "XNPV",
14767  431: "PRICEMAT",
14768  432: "YIELDMAT",
14769  433: "INTRATE",
14770  434: "RECEIVED",
14771  435: "DISC",
14772  436: "PRICEDISC",
14773  437: "YIELDDISC",
14774  438: "TBILLEQ",
14775  439: "TBILLPRICE",
14776  440: "TBILLYIELD",
14777  441: "PRICE",
14778  442: "YIELD",
14779  443: "DOLLARDE",
14780  444: "DOLLARFR",
14781  445: "NOMINAL",
14782  446: "EFFECT",
14783  447: "CUMPRINC",
14784  448: "CUMIPMT",
14785  449: "EDATE",
14786  450: "EOMONTH",
14787  451: "YEARFRAC",
14788  452: "COUPDAYBS",
14789  453: "COUPDAYS",
14790  454: "COUPDAYSNC",
14791  455: "COUPNCD",
14792  456: "COUPNUM",
14793  457: "COUPPCD",
14794  458: "DURATION",
14795  459: "MDURATION",
14796  460: "ODDLPRICE",
14797  461: "ODDLYIELD",
14798  462: "ODDFPRICE",
14799  463: "ODDFYIELD",
14800  464: "RANDBETWEEN",
14801  465: "WEEKNUM",
14802  466: "AMORDEGRC",
14803  467: "AMORLINC",
14804  468: "CONVERT",
14805  724: "SHEETJS",
14806  469: "ACCRINT",
14807  470: "ACCRINTM",
14808  471: "WORKDAY",
14809  472: "NETWORKDAYS",
14810  473: "GCD",
14811  474: "MULTINOMIAL",
14812  475: "LCM",
14813  476: "FVSCHEDULE",
14814  477: "CUBEKPIMEMBER",
14815  478: "CUBESET",
14816  479: "CUBESETCOUNT",
14817  480: "IFERROR",
14818  481: "COUNTIFS",
14819  482: "SUMIFS",
14820  483: "AVERAGEIF",
14821  484: "AVERAGEIFS"
14822};
14823var FtabArgc = {
14824  2: 1,
14825  3: 1,
14826  10: 0,
14827  15: 1,
14828  16: 1,
14829  17: 1,
14830  18: 1,
14831  19: 0,
14832  20: 1,
14833  21: 1,
14834  22: 1,
14835  23: 1,
14836  24: 1,
14837  25: 1,
14838  26: 1,
14839  27: 2,
14840  30: 2,
14841  31: 3,
14842  32: 1,
14843  33: 1,
14844  34: 0,
14845  35: 0,
14846  38: 1,
14847  39: 2,
14848  40: 3,
14849  41: 3,
14850  42: 3,
14851  43: 3,
14852  44: 3,
14853  45: 3,
14854  47: 3,
14855  48: 2,
14856  53: 1,
14857  61: 3,
14858  63: 0,
14859  65: 3,
14860  66: 3,
14861  67: 1,
14862  68: 1,
14863  69: 1,
14864  70: 1,
14865  71: 1,
14866  72: 1,
14867  73: 1,
14868  74: 0,
14869  75: 1,
14870  76: 1,
14871  77: 1,
14872  79: 2,
14873  80: 2,
14874  83: 1,
14875  85: 0,
14876  86: 1,
14877  89: 0,
14878  90: 1,
14879  94: 0,
14880  95: 0,
14881  97: 2,
14882  98: 1,
14883  99: 1,
14884  101: 3,
14885  102: 3,
14886  105: 1,
14887  106: 1,
14888  108: 2,
14889  111: 1,
14890  112: 1,
14891  113: 1,
14892  114: 1,
14893  117: 2,
14894  118: 1,
14895  119: 4,
14896  121: 1,
14897  126: 1,
14898  127: 1,
14899  128: 1,
14900  129: 1,
14901  130: 1,
14902  131: 1,
14903  133: 1,
14904  134: 1,
14905  135: 1,
14906  136: 2,
14907  137: 2,
14908  138: 2,
14909  140: 1,
14910  141: 1,
14911  142: 3,
14912  143: 4,
14913  144: 4,
14914  161: 1,
14915  162: 1,
14916  163: 1,
14917  164: 1,
14918  165: 2,
14919  172: 1,
14920  175: 2,
14921  176: 2,
14922  177: 3,
14923  178: 2,
14924  179: 1,
14925  184: 1,
14926  186: 1,
14927  189: 3,
14928  190: 1,
14929  195: 3,
14930  196: 3,
14931  197: 1,
14932  198: 1,
14933  199: 3,
14934  201: 1,
14935  207: 4,
14936  210: 3,
14937  211: 1,
14938  212: 2,
14939  213: 2,
14940  214: 1,
14941  215: 1,
14942  225: 0,
14943  229: 1,
14944  230: 1,
14945  231: 1,
14946  232: 1,
14947  233: 1,
14948  234: 1,
14949  235: 3,
14950  244: 1,
14951  247: 4,
14952  252: 2,
14953  257: 1,
14954  261: 1,
14955  271: 1,
14956  273: 4,
14957  274: 2,
14958  275: 2,
14959  276: 2,
14960  277: 3,
14961  278: 3,
14962  279: 1,
14963  280: 3,
14964  281: 3,
14965  282: 3,
14966  283: 1,
14967  284: 1,
14968  285: 2,
14969  286: 4,
14970  287: 3,
14971  288: 2,
14972  289: 4,
14973  290: 3,
14974  291: 3,
14975  292: 3,
14976  293: 4,
14977  294: 1,
14978  295: 3,
14979  296: 1,
14980  297: 3,
14981  298: 1,
14982  299: 2,
14983  300: 3,
14984  301: 3,
14985  302: 4,
14986  303: 2,
14987  304: 2,
14988  305: 2,
14989  306: 2,
14990  307: 2,
14991  308: 2,
14992  309: 3,
14993  310: 2,
14994  311: 2,
14995  312: 2,
14996  313: 2,
14997  314: 2,
14998  315: 2,
14999  316: 4,
15000  325: 2,
15001  326: 2,
15002  327: 2,
15003  328: 2,
15004  331: 2,
15005  332: 2,
15006  337: 2,
15007  342: 1,
15008  343: 1,
15009  346: 2,
15010  347: 1,
15011  350: 4,
15012  351: 3,
15013  352: 1,
15014  353: 2,
15015  360: 1,
15016  368: 1,
15017  369: 1,
15018  370: 1,
15019  371: 1,
15020  372: 1,
15021  373: 1,
15022  374: 1,
15023  375: 1,
15024  376: 1,
15025  377: 1,
15026  378: 1,
15027  382: 3,
15028  385: 1,
15029  392: 1,
15030  393: 1,
15031  396: 2,
15032  397: 2,
15033  398: 2,
15034  399: 1,
15035  400: 1,
15036  401: 1,
15037  402: 1,
15038  403: 1,
15039  404: 1,
15040  405: 1,
15041  406: 1,
15042  407: 1,
15043  408: 1,
15044  409: 1,
15045  410: 1,
15046  414: 4,
15047  415: 1,
15048  416: 1,
15049  417: 2,
15050  420: 1,
15051  421: 1,
15052  422: 2,
15053  424: 1,
15054  425: 2,
15055  426: 2,
15056  427: 2,
15057  428: 2,
15058  430: 3,
15059  438: 3,
15060  439: 3,
15061  440: 3,
15062  443: 2,
15063  444: 2,
15064  445: 2,
15065  446: 2,
15066  447: 6,
15067  448: 6,
15068  449: 2,
15069  450: 2,
15070  464: 2,
15071  468: 3,
15072  476: 2,
15073  479: 1,
15074  480: 2,
15075  65535: 0
15076};
15077/* Part 3 TODO: actually parse formulae */
15078function ods_to_csf_formula(f/*:string*/)/*:string*/ {
15079	if(f.slice(0,3) == "of:") f = f.slice(3);
15080	/* 5.2 Basic Expressions */
15081	if(f.charCodeAt(0) == 61) {
15082		f = f.slice(1);
15083		if(f.charCodeAt(0) == 61) f = f.slice(1);
15084	}
15085	f = f.replace(/COM\.MICROSOFT\./g, "");
15086	/* Part 3 Section 5.8 References */
15087	f = f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, function($$, $1) { return $1.replace(/\./g,""); });
15088	f = f.replace(/\$'([^']|'')+'/g, function($$) { return $$.slice(1); });
15089	f = f.replace(/\$([^\]\. #$]+)/g, function($$, $1) { return ($1).match(/^([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])?(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})?$/) ? $$ : $1; });
15090	/* TODO: something other than this */
15091	f = f.replace(/\[.(#[A-Z]*[?!])\]/g, "$1");
15092	return f.replace(/[;~]/g,",").replace(/\|/g,";");
15093}
15094
15095function csf_to_ods_formula(f/*:string*/)/*:string*/ {
15096	var o = "of:=" + f.replace(crefregex, "$1[.$2$3$4$5]").replace(/\]:\[/g,":");
15097	/* TODO: something other than this */
15098	return o.replace(/;/g, "|").replace(/,/g,";");
15099}
15100
15101function ods_to_csf_3D(r/*:string*/)/*:[string, string]*/ {
15102	r = r.replace(/\$'([^']|'')+'/g, function($$) { return $$.slice(1); });
15103	r = r.replace(/\$([^\]\. #$]+)/g, function($$, $1) { return ($1).match(/^([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])?(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})?$/) ? $$ : $1; });
15104	var a = r.split(":");
15105	var s = a[0].split(".")[0];
15106	return [s, a[0].split(".")[1] + (a.length > 1 ? (":" + (a[1].split(".")[1] || a[1].split(".")[0])) : "")];
15107}
15108
15109function csf_to_ods_3D(r/*:string*/)/*:string*/ {
15110	return r.replace(/!/,".");
15111}
15112
15113var strs = {}; // shared strings
15114var _ssfopts = {}; // spreadsheet formatting options
15115
15116
15117/*global Map */
15118var browser_has_Map = typeof Map !== 'undefined';
15119
15120function get_sst_id(sst/*:SST*/, str/*:string*/, rev)/*:number*/ {
15121	var i = 0, len = sst.length;
15122	if(rev) {
15123		if(browser_has_Map ? rev.has(str) : Object.prototype.hasOwnProperty.call(rev, str)) {
15124			var revarr = browser_has_Map ? rev.get(str) : rev[str];
15125			for(; i < revarr.length; ++i) {
15126				if(sst[revarr[i]].t === str) { sst.Count ++; return revarr[i]; }
15127			}
15128		}
15129	} else for(; i < len; ++i) {
15130		if(sst[i].t === str) { sst.Count ++; return i; }
15131	}
15132	sst[len] = ({t:str}/*:any*/); sst.Count ++; sst.Unique ++;
15133	if(rev) {
15134		if(browser_has_Map) {
15135			if(!rev.has(str)) rev.set(str, []);
15136			rev.get(str).push(len);
15137		} else {
15138			if(!Object.prototype.hasOwnProperty.call(rev, str)) rev[str] = [];
15139			rev[str].push(len);
15140		}
15141	}
15142	return len;
15143}
15144
15145function col_obj_w(C/*:number*/, col) {
15146	var p = ({min:C+1,max:C+1}/*:any*/);
15147	/* wch (chars), wpx (pixels) */
15148	var wch = -1;
15149	if(col.MDW) MDW = col.MDW;
15150	if(col.width != null) p.customWidth = 1;
15151	else if(col.wpx != null) wch = px2char(col.wpx);
15152	else if(col.wch != null) wch = col.wch;
15153	if(wch > -1) { p.width = char2width(wch); p.customWidth = 1; }
15154	else if(col.width != null) p.width = col.width;
15155	if(col.hidden) p.hidden = true;
15156	if(col.level != null) { p.outlineLevel = p.level = col.level; }
15157	return p;
15158}
15159
15160function default_margins(margins/*:Margins*/, mode/*:?string*/) {
15161	if(!margins) return;
15162	var defs = [0.7, 0.7, 0.75, 0.75, 0.3, 0.3];
15163	if(mode == 'xlml') defs = [1, 1, 1, 1, 0.5, 0.5];
15164	if(margins.left   == null) margins.left   = defs[0];
15165	if(margins.right  == null) margins.right  = defs[1];
15166	if(margins.top    == null) margins.top    = defs[2];
15167	if(margins.bottom == null) margins.bottom = defs[3];
15168	if(margins.header == null) margins.header = defs[4];
15169	if(margins.footer == null) margins.footer = defs[5];
15170}
15171
15172function get_cell_style(styles/*:Array<any>*/, cell/*:Cell*/, opts) {
15173	var z = opts.revssf[cell.z != null ? cell.z : "General"];
15174	var i = 0x3c, len = styles.length;
15175	if(z == null && opts.ssf) {
15176		for(; i < 0x188; ++i) if(opts.ssf[i] == null) {
15177			SSF__load(cell.z, i);
15178			// $FlowIgnore
15179			opts.ssf[i] = cell.z;
15180			opts.revssf[cell.z] = z = i;
15181			break;
15182		}
15183	}
15184	for(i = 0; i != len; ++i) if(styles[i].numFmtId === z) return i;
15185	styles[len] = {
15186		numFmtId:z,
15187		fontId:0,
15188		fillId:0,
15189		borderId:0,
15190		xfId:0,
15191		applyNumberFormat:1
15192	};
15193	return len;
15194}
15195
15196function safe_format(p/*:Cell*/, fmtid/*:number*/, fillid/*:?number*/, opts, themes, styles) {
15197	try {
15198		if(opts.cellNF) p.z = table_fmt[fmtid];
15199	} catch(e) { if(opts.WTF) throw e; }
15200	if(p.t === 'z' && !opts.cellStyles) return;
15201	if(p.t === 'd' && typeof p.v === 'string') p.v = parseDate(p.v);
15202	if((!opts || opts.cellText !== false) && p.t !== 'z') try {
15203		if(table_fmt[fmtid] == null) SSF__load(SSFImplicit[fmtid] || "General", fmtid);
15204		if(p.t === 'e') p.w = p.w || BErr[p.v];
15205		else if(fmtid === 0) {
15206			if(p.t === 'n') {
15207				if((p.v|0) === p.v) p.w = p.v.toString(10);
15208				else p.w = SSF_general_num(p.v);
15209			}
15210			else if(p.t === 'd') {
15211				var dd = datenum(p.v);
15212				if((dd|0) === dd) p.w = dd.toString(10);
15213				else p.w = SSF_general_num(dd);
15214			}
15215			else if(p.v === undefined) return "";
15216			else p.w = SSF_general(p.v,_ssfopts);
15217		}
15218		else if(p.t === 'd') p.w = SSF_format(fmtid,datenum(p.v),_ssfopts);
15219		else p.w = SSF_format(fmtid,p.v,_ssfopts);
15220	} catch(e) { if(opts.WTF) throw e; }
15221	if(!opts.cellStyles) return;
15222	if(fillid != null) try {
15223		p.s = styles.Fills[fillid];
15224		if (p.s.fgColor && p.s.fgColor.theme && !p.s.fgColor.rgb) {
15225			p.s.fgColor.rgb = rgb_tint(themes.themeElements.clrScheme[p.s.fgColor.theme].rgb, p.s.fgColor.tint || 0);
15226			if(opts.WTF) p.s.fgColor.raw_rgb = themes.themeElements.clrScheme[p.s.fgColor.theme].rgb;
15227		}
15228		if (p.s.bgColor && p.s.bgColor.theme) {
15229			p.s.bgColor.rgb = rgb_tint(themes.themeElements.clrScheme[p.s.bgColor.theme].rgb, p.s.bgColor.tint || 0);
15230			if(opts.WTF) p.s.bgColor.raw_rgb = themes.themeElements.clrScheme[p.s.bgColor.theme].rgb;
15231		}
15232	} catch(e) { if(opts.WTF && styles.Fills) throw e; }
15233}
15234
15235function check_ws(ws/*:Worksheet*/, sname/*:string*/, i/*:number*/) {
15236	if(ws && ws['!ref']) {
15237		var range = safe_decode_range(ws['!ref']);
15238		if(range.e.c < range.s.c || range.e.r < range.s.r) throw new Error("Bad range (" + i + "): " + ws['!ref']);
15239	}
15240}
15241function parse_ws_xml_dim(ws/*:Worksheet*/, s/*:string*/) {
15242	var d = safe_decode_range(s);
15243	if(d.s.r<=d.e.r && d.s.c<=d.e.c && d.s.r>=0 && d.s.c>=0) ws["!ref"] = encode_range(d);
15244}
15245var mergecregex = /<(?:\w:)?mergeCell ref="[A-Z0-9:]+"\s*[\/]?>/g;
15246var sheetdataregex = /<(?:\w+:)?sheetData[^>]*>([\s\S]*)<\/(?:\w+:)?sheetData>/;
15247var hlinkregex = /<(?:\w:)?hyperlink [^>]*>/mg;
15248var dimregex = /"(\w*:\w*)"/;
15249var colregex = /<(?:\w:)?col\b[^>]*[\/]?>/g;
15250var afregex = /<(?:\w:)?autoFilter[^>]*([\/]|>([\s\S]*)<\/(?:\w:)?autoFilter)>/g;
15251var marginregex= /<(?:\w:)?pageMargins[^>]*\/>/g;
15252var sheetprregex = /<(?:\w:)?sheetPr\b(?:[^>a-z][^>]*)?\/>/;
15253var sheetprregex2= /<(?:\w:)?sheetPr[^>]*(?:[\/]|>([\s\S]*)<\/(?:\w:)?sheetPr)>/;
15254var svsregex = /<(?:\w:)?sheetViews[^>]*(?:[\/]|>([\s\S]*)<\/(?:\w:)?sheetViews)>/;
15255
15256/* 18.3 Worksheets */
15257function parse_ws_xml(data/*:?string*/, opts, idx/*:number*/, rels, wb/*:WBWBProps*/, themes, styles)/*:Worksheet*/ {
15258	if(!data) return data;
15259	if(!rels) rels = {'!id':{}};
15260	if(DENSE != null && opts.dense == null) opts.dense = DENSE;
15261
15262	/* 18.3.1.99 worksheet CT_Worksheet */
15263	var s = opts.dense ? ([]/*:any*/) : ({}/*:any*/);
15264	var refguess/*:Range*/ = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }/*:any*/);
15265
15266	var data1 = "", data2 = "";
15267	var mtch/*:?any*/ = data.match(sheetdataregex);
15268	if(mtch) {
15269		data1 = data.slice(0, mtch.index);
15270		data2 = data.slice(mtch.index + mtch[0].length);
15271	} else data1 = data2 = data;
15272
15273	/* 18.3.1.82 sheetPr CT_SheetPr */
15274	var sheetPr = data1.match(sheetprregex);
15275	if(sheetPr) parse_ws_xml_sheetpr(sheetPr[0], s, wb, idx);
15276	else if((sheetPr = data1.match(sheetprregex2))) parse_ws_xml_sheetpr2(sheetPr[0], sheetPr[1]||"", s, wb, idx, styles, themes);
15277
15278	/* 18.3.1.35 dimension CT_SheetDimension */
15279	var ridx = (data1.match(/<(?:\w*:)?dimension/)||{index:-1}).index;
15280	if(ridx > 0) {
15281		var ref = data1.slice(ridx,ridx+50).match(dimregex);
15282		if(ref) parse_ws_xml_dim(s, ref[1]);
15283	}
15284
15285	/* 18.3.1.88 sheetViews CT_SheetViews */
15286	var svs = data1.match(svsregex);
15287	if(svs && svs[1]) parse_ws_xml_sheetviews(svs[1], wb);
15288
15289	/* 18.3.1.17 cols CT_Cols */
15290	var columns/*:Array<ColInfo>*/ = [];
15291	if(opts.cellStyles) {
15292		/* 18.3.1.13 col CT_Col */
15293		var cols = data1.match(colregex);
15294		if(cols) parse_ws_xml_cols(columns, cols);
15295	}
15296
15297	/* 18.3.1.80 sheetData CT_SheetData ? */
15298	if(mtch) parse_ws_xml_data(mtch[1], s, opts, refguess, themes, styles);
15299
15300	/* 18.3.1.2  autoFilter CT_AutoFilter */
15301	var afilter = data2.match(afregex);
15302	if(afilter) s['!autofilter'] = parse_ws_xml_autofilter(afilter[0]);
15303
15304	/* 18.3.1.55 mergeCells CT_MergeCells */
15305	var merges/*:Array<Range>*/ = [];
15306	var _merge = data2.match(mergecregex);
15307	if(_merge) for(ridx = 0; ridx != _merge.length; ++ridx)
15308		merges[ridx] = safe_decode_range(_merge[ridx].slice(_merge[ridx].indexOf("\"")+1));
15309
15310	/* 18.3.1.48 hyperlinks CT_Hyperlinks */
15311	var hlink = data2.match(hlinkregex);
15312	if(hlink) parse_ws_xml_hlinks(s, hlink, rels);
15313
15314	/* 18.3.1.62 pageMargins CT_PageMargins */
15315	var margins = data2.match(marginregex);
15316	if(margins) s['!margins'] = parse_ws_xml_margins(parsexmltag(margins[0]));
15317
15318	if(!s["!ref"] && refguess.e.c >= refguess.s.c && refguess.e.r >= refguess.s.r) s["!ref"] = encode_range(refguess);
15319	if(opts.sheetRows > 0 && s["!ref"]) {
15320		var tmpref = safe_decode_range(s["!ref"]);
15321		if(opts.sheetRows <= +tmpref.e.r) {
15322			tmpref.e.r = opts.sheetRows - 1;
15323			if(tmpref.e.r > refguess.e.r) tmpref.e.r = refguess.e.r;
15324			if(tmpref.e.r < tmpref.s.r) tmpref.s.r = tmpref.e.r;
15325			if(tmpref.e.c > refguess.e.c) tmpref.e.c = refguess.e.c;
15326			if(tmpref.e.c < tmpref.s.c) tmpref.s.c = tmpref.e.c;
15327			s["!fullref"] = s["!ref"];
15328			s["!ref"] = encode_range(tmpref);
15329		}
15330	}
15331	if(columns.length > 0) s["!cols"] = columns;
15332	if(merges.length > 0) s["!merges"] = merges;
15333	return s;
15334}
15335
15336function write_ws_xml_merges(merges/*:Array<Range>*/)/*:string*/ {
15337	if(merges.length === 0) return "";
15338	var o = '<mergeCells count="' + merges.length + '">';
15339	for(var i = 0; i != merges.length; ++i) o += '<mergeCell ref="' + encode_range(merges[i]) + '"/>';
15340	return o + '</mergeCells>';
15341}
15342
15343/* 18.3.1.82-3 sheetPr CT_ChartsheetPr / CT_SheetPr */
15344function parse_ws_xml_sheetpr(sheetPr/*:string*/, s, wb/*:WBWBProps*/, idx/*:number*/) {
15345	var data = parsexmltag(sheetPr);
15346	if(!wb.Sheets[idx]) wb.Sheets[idx] = {};
15347	if(data.codeName) wb.Sheets[idx].CodeName = unescapexml(utf8read(data.codeName));
15348}
15349function parse_ws_xml_sheetpr2(sheetPr/*:string*/, body/*:string*/, s, wb/*:WBWBProps*/, idx/*:number*/) {
15350	parse_ws_xml_sheetpr(sheetPr.slice(0, sheetPr.indexOf(">")), s, wb, idx);
15351}
15352function write_ws_xml_sheetpr(ws, wb, idx, opts, o) {
15353	var needed = false;
15354	var props = {}, payload = null;
15355	if(opts.bookType !== 'xlsx' && wb.vbaraw) {
15356		var cname = wb.SheetNames[idx];
15357		try { if(wb.Workbook) cname = wb.Workbook.Sheets[idx].CodeName || cname; } catch(e) {}
15358		needed = true;
15359		props.codeName = utf8write(escapexml(cname));
15360	}
15361
15362	if(ws && ws["!outline"]) {
15363		var outlineprops = {summaryBelow:1, summaryRight:1};
15364		if(ws["!outline"].above) outlineprops.summaryBelow = 0;
15365		if(ws["!outline"].left) outlineprops.summaryRight = 0;
15366		payload = (payload||"") + writextag('outlinePr', null, outlineprops);
15367	}
15368
15369	if(!needed && !payload) return;
15370	o[o.length] = (writextag('sheetPr', payload, props));
15371}
15372
15373/* 18.3.1.85 sheetProtection CT_SheetProtection */
15374var sheetprot_deffalse = ["objects", "scenarios", "selectLockedCells", "selectUnlockedCells"];
15375var sheetprot_deftrue = [
15376	"formatColumns", "formatRows", "formatCells",
15377	"insertColumns", "insertRows", "insertHyperlinks",
15378	"deleteColumns", "deleteRows",
15379	"sort", "autoFilter", "pivotTables"
15380];
15381function write_ws_xml_protection(sp)/*:string*/ {
15382	// algorithmName, hashValue, saltValue, spinCount
15383	var o = ({sheet:1}/*:any*/);
15384	sheetprot_deffalse.forEach(function(n) { if(sp[n] != null && sp[n]) o[n] = "1"; });
15385	sheetprot_deftrue.forEach(function(n) { if(sp[n] != null && !sp[n]) o[n] = "0"; });
15386	/* TODO: algorithm */
15387	if(sp.password) o.password = crypto_CreatePasswordVerifier_Method1(sp.password).toString(16).toUpperCase();
15388	return writextag('sheetProtection', null, o);
15389}
15390
15391function parse_ws_xml_hlinks(s, data/*:Array<string>*/, rels) {
15392	var dense = Array.isArray(s);
15393	for(var i = 0; i != data.length; ++i) {
15394		var val = parsexmltag(utf8read(data[i]), true);
15395		if(!val.ref) return;
15396		var rel = ((rels || {})['!id']||[])[val.id];
15397		if(rel) {
15398			val.Target = rel.Target;
15399			if(val.location) val.Target += "#"+unescapexml(val.location);
15400		} else {
15401			val.Target = "#" + unescapexml(val.location);
15402			rel = {Target: val.Target, TargetMode: 'Internal'};
15403		}
15404		val.Rel = rel;
15405		if(val.tooltip) { val.Tooltip = val.tooltip; delete val.tooltip; }
15406		var rng = safe_decode_range(val.ref);
15407		for(var R=rng.s.r;R<=rng.e.r;++R) for(var C=rng.s.c;C<=rng.e.c;++C) {
15408			var addr = encode_cell({c:C,r:R});
15409			if(dense) {
15410				if(!s[R]) s[R] = [];
15411				if(!s[R][C]) s[R][C] = {t:"z",v:undefined};
15412				s[R][C].l = val;
15413			} else {
15414				if(!s[addr]) s[addr] = {t:"z",v:undefined};
15415				s[addr].l = val;
15416			}
15417		}
15418	}
15419}
15420
15421function parse_ws_xml_margins(margin) {
15422	var o = {};
15423	["left", "right", "top", "bottom", "header", "footer"].forEach(function(k) {
15424		if(margin[k]) o[k] = parseFloat(margin[k]);
15425	});
15426	return o;
15427}
15428function write_ws_xml_margins(margin)/*:string*/ {
15429	default_margins(margin);
15430	return writextag('pageMargins', null, margin);
15431}
15432
15433function parse_ws_xml_cols(columns, cols) {
15434	var seencol = false;
15435	for(var coli = 0; coli != cols.length; ++coli) {
15436		var coll = parsexmltag(cols[coli], true);
15437		if(coll.hidden) coll.hidden = parsexmlbool(coll.hidden);
15438		var colm=parseInt(coll.min, 10)-1, colM=parseInt(coll.max,10)-1;
15439		if(coll.outlineLevel) coll.level = (+coll.outlineLevel || 0);
15440		delete coll.min; delete coll.max; coll.width = +coll.width;
15441		if(!seencol && coll.width) { seencol = true; find_mdw_colw(coll.width); }
15442		process_col(coll);
15443		while(colm <= colM) columns[colm++] = dup(coll);
15444	}
15445}
15446function write_ws_xml_cols(ws, cols)/*:string*/ {
15447	var o = ["<cols>"], col;
15448	for(var i = 0; i != cols.length; ++i) {
15449		if(!(col = cols[i])) continue;
15450		o[o.length] = (writextag('col', null, col_obj_w(i, col)));
15451	}
15452	o[o.length] = "</cols>";
15453	return o.join("");
15454}
15455
15456function parse_ws_xml_autofilter(data/*:string*/) {
15457	var o = { ref: (data.match(/ref="([^"]*)"/)||[])[1]};
15458	return o;
15459}
15460function write_ws_xml_autofilter(data, ws, wb, idx)/*:string*/ {
15461	var ref = typeof data.ref == "string" ? data.ref : encode_range(data.ref);
15462	if(!wb.Workbook) wb.Workbook = ({Sheets:[]}/*:any*/);
15463	if(!wb.Workbook.Names) wb.Workbook.Names = [];
15464	var names/*: Array<any> */ = wb.Workbook.Names;
15465	var range = decode_range(ref);
15466	if(range.s.r == range.e.r) { range.e.r = decode_range(ws["!ref"]).e.r; ref = encode_range(range); }
15467	for(var i = 0; i < names.length; ++i) {
15468		var name = names[i];
15469		if(name.Name != '_xlnm._FilterDatabase') continue;
15470		if(name.Sheet != idx) continue;
15471		name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
15472	}
15473	if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref  });
15474	return writextag("autoFilter", null, {ref:ref});
15475}
15476
15477/* 18.3.1.88 sheetViews CT_SheetViews */
15478/* 18.3.1.87 sheetView CT_SheetView */
15479var sviewregex = /<(?:\w:)?sheetView(?:[^>a-z][^>]*)?\/?>/g;
15480function parse_ws_xml_sheetviews(data, wb/*:WBWBProps*/) {
15481	if(!wb.Views) wb.Views = [{}];
15482	(data.match(sviewregex)||[]).forEach(function(r/*:string*/, i/*:number*/) {
15483		var tag = parsexmltag(r);
15484		// $FlowIgnore
15485		if(!wb.Views[i]) wb.Views[i] = {};
15486		// $FlowIgnore
15487		if(+tag.zoomScale) wb.Views[i].zoom = +tag.zoomScale;
15488		// $FlowIgnore
15489		if(tag.rightToLeft && parsexmlbool(tag.rightToLeft)) wb.Views[i].RTL = true;
15490	});
15491}
15492function write_ws_xml_sheetviews(ws, opts, idx, wb)/*:string*/ {
15493	var sview = ({workbookViewId:"0"}/*:any*/);
15494	// $FlowIgnore
15495	if((((wb||{}).Workbook||{}).Views||[])[0]) sview.rightToLeft = wb.Workbook.Views[0].RTL ? "1" : "0";
15496	return writextag("sheetViews", writextag("sheetView", null, sview), {});
15497}
15498
15499function write_ws_xml_cell(cell/*:Cell*/, ref, ws, opts/*::, idx, wb*/)/*:string*/ {
15500	if(cell.c) ws['!comments'].push([ref, cell.c]);
15501	if((cell.v === undefined || cell.t === "z" && !(opts||{}).sheetStubs) && typeof cell.f !== "string" && typeof cell.z == "undefined") return "";
15502	var vv = "";
15503	var oldt = cell.t, oldv = cell.v;
15504	if(cell.t !== "z") switch(cell.t) {
15505		case 'b': vv = cell.v ? "1" : "0"; break;
15506		case 'n': vv = ''+cell.v; break;
15507		case 'e': vv = BErr[cell.v]; break;
15508		case 'd':
15509			if(opts && opts.cellDates) vv = parseDate(cell.v, -1).toISOString();
15510			else {
15511				cell = dup(cell);
15512				cell.t = 'n';
15513				vv = ''+(cell.v = datenum(parseDate(cell.v)));
15514			}
15515			if(typeof cell.z === 'undefined') cell.z = table_fmt[14];
15516			break;
15517		default: vv = cell.v; break;
15518	}
15519	var v = (cell.t == "z" || cell.v == null)? "" : writetag('v', escapexml(vv)), o = ({r:ref}/*:any*/);
15520	/* TODO: cell style */
15521	var os = get_cell_style(opts.cellXfs, cell, opts);
15522	if(os !== 0) o.s = os;
15523	switch(cell.t) {
15524		case 'n': break;
15525		case 'd': o.t = "d"; break;
15526		case 'b': o.t = "b"; break;
15527		case 'e': o.t = "e"; break;
15528		case 'z': break;
15529		default: if(cell.v == null) { delete cell.t; break; }
15530			if(cell.v.length > 32767) throw new Error("Text length must not exceed 32767 characters");
15531			if(opts && opts.bookSST) {
15532				v = writetag('v', ''+get_sst_id(opts.Strings, cell.v, opts.revStrings));
15533				o.t = "s"; break;
15534			}
15535			else o.t = "str"; break;
15536	}
15537	if(cell.t != oldt) { cell.t = oldt; cell.v = oldv; }
15538	if(typeof cell.f == "string" && cell.f) {
15539		var ff = cell.F && cell.F.slice(0, ref.length) == ref ? {t:"array", ref:cell.F} : null;
15540		v = writextag('f', escapexml(cell.f), ff) + (cell.v != null ? v : "");
15541	}
15542	if(cell.l) {
15543		cell.l.display = escapexml(vv);
15544		ws['!links'].push([ref, cell.l]);
15545	}
15546	if(cell.D) o.cm = 1;
15547	return writextag('c', v, o);
15548}
15549
15550var parse_ws_xml_data = /*#__PURE__*/(function() {
15551	var cellregex = /<(?:\w+:)?c[ \/>]/, rowregex = /<\/(?:\w+:)?row>/;
15552	var rregex = /r=["']([^"']*)["']/, isregex = /<(?:\w+:)?is>([\S\s]*?)<\/(?:\w+:)?is>/;
15553	var refregex = /ref=["']([^"']*)["']/;
15554	var match_v = matchtag("v"), match_f = matchtag("f");
15555
15556return function parse_ws_xml_data(sdata/*:string*/, s, opts, guess/*:Range*/, themes, styles) {
15557	var ri = 0, x = "", cells/*:Array<string>*/ = [], cref/*:?Array<string>*/ = [], idx=0, i=0, cc=0, d="", p/*:any*/;
15558	var tag, tagr = 0, tagc = 0;
15559	var sstr, ftag;
15560	var fmtid = 0, fillid = 0;
15561	var do_format = Array.isArray(styles.CellXf), cf;
15562	var arrayf/*:Array<[Range, string]>*/ = [];
15563	var sharedf = [];
15564	var dense = Array.isArray(s);
15565	var rows/*:Array<RowInfo>*/ = [], rowobj = {}, rowrite = false;
15566	var sheetStubs = !!opts.sheetStubs;
15567	for(var marr = sdata.split(rowregex), mt = 0, marrlen = marr.length; mt != marrlen; ++mt) {
15568		x = marr[mt].trim();
15569		var xlen = x.length;
15570		if(xlen === 0) continue;
15571
15572		/* 18.3.1.73 row CT_Row */
15573		var rstarti = 0;
15574		outa: for(ri = 0; ri < xlen; ++ri) switch(/*x.charCodeAt(ri)*/x[ri]) {
15575			case ">" /*62*/:
15576				if(/*x.charCodeAt(ri-1) != 47*/x[ri-1] != "/") { ++ri; break outa; }
15577				if(opts && opts.cellStyles) {
15578					// TODO: avoid duplication
15579					tag = parsexmltag(x.slice(rstarti,ri), true);
15580					tagr = tag.r != null ? parseInt(tag.r, 10) : tagr+1; tagc = -1;
15581					if(opts.sheetRows && opts.sheetRows < tagr) continue;
15582					rowobj = {}; rowrite = false;
15583					if(tag.ht) { rowrite = true; rowobj.hpt = parseFloat(tag.ht); rowobj.hpx = pt2px(rowobj.hpt); }
15584					if(tag.hidden && parsexmlbool(tag.hidden)) { rowrite = true; rowobj.hidden = true; }
15585					if(tag.outlineLevel != null) { rowrite = true; rowobj.level = +tag.outlineLevel; }
15586					if(rowrite) rows[tagr-1] = rowobj;
15587				}
15588				break;
15589			case "<" /*60*/: rstarti = ri; break;
15590		}
15591		if(rstarti >= ri) break;
15592		tag = parsexmltag(x.slice(rstarti,ri), true);
15593		tagr = tag.r != null ? parseInt(tag.r, 10) : tagr+1; tagc = -1;
15594		if(opts.sheetRows && opts.sheetRows < tagr) continue;
15595		if(guess.s.r > tagr - 1) guess.s.r = tagr - 1;
15596		if(guess.e.r < tagr - 1) guess.e.r = tagr - 1;
15597
15598		if(opts && opts.cellStyles) {
15599			rowobj = {}; rowrite = false;
15600			if(tag.ht) { rowrite = true; rowobj.hpt = parseFloat(tag.ht); rowobj.hpx = pt2px(rowobj.hpt); }
15601			if(tag.hidden && parsexmlbool(tag.hidden)) { rowrite = true; rowobj.hidden = true; }
15602			if(tag.outlineLevel != null) { rowrite = true; rowobj.level = +tag.outlineLevel; }
15603			if(rowrite) rows[tagr-1] = rowobj;
15604		}
15605
15606		/* 18.3.1.4 c CT_Cell */
15607		cells = x.slice(ri).split(cellregex);
15608		for(var rslice = 0; rslice != cells.length; ++rslice) if(cells[rslice].trim().charAt(0) != "<") break;
15609		cells = cells.slice(rslice);
15610		for(ri = 0; ri != cells.length; ++ri) {
15611			x = cells[ri].trim();
15612			if(x.length === 0) continue;
15613			cref = x.match(rregex); idx = ri; i=0; cc=0;
15614			x = "<c " + (x.slice(0,1)=="<"?">":"") + x;
15615			if(cref != null && cref.length === 2) {
15616				idx = 0; d=cref[1];
15617				for(i=0; i != d.length; ++i) {
15618					if((cc=d.charCodeAt(i)-64) < 1 || cc > 26) break;
15619					idx = 26*idx + cc;
15620				}
15621				--idx;
15622				tagc = idx;
15623			} else ++tagc;
15624			for(i = 0; i != x.length; ++i) if(x.charCodeAt(i) === 62) break; ++i;
15625			tag = parsexmltag(x.slice(0,i), true);
15626			if(!tag.r) tag.r = encode_cell({r:tagr-1, c:tagc});
15627			d = x.slice(i);
15628			p = ({t:""}/*:any*/);
15629
15630			if((cref=d.match(match_v))!= null && /*::cref != null && */cref[1] !== '') p.v=unescapexml(cref[1]);
15631			if(opts.cellFormula) {
15632				if((cref=d.match(match_f))!= null && /*::cref != null && */cref[1] !== '') {
15633					/* TODO: match against XLSXFutureFunctions */
15634					p.f=unescapexml(utf8read(cref[1]), true);
15635					if(!opts.xlfn) p.f = _xlfn(p.f);
15636					if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="array"') > -1) {
15637						p.F = (d.match(refregex)||[])[1];
15638						if(p.F.indexOf(":") > -1) arrayf.push([safe_decode_range(p.F), p.F]);
15639					} else if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="shared"') > -1) {
15640						// TODO: parse formula
15641						ftag = parsexmltag(cref[0]);
15642						var ___f = unescapexml(utf8read(cref[1]));
15643						if(!opts.xlfn) ___f = _xlfn(___f);
15644						sharedf[parseInt(ftag.si, 10)] = [ftag, ___f, tag.r];
15645					}
15646				} else if((cref=d.match(/<f[^>]*\/>/))) {
15647					ftag = parsexmltag(cref[0]);
15648					if(sharedf[ftag.si]) p.f = shift_formula_xlsx(sharedf[ftag.si][1], sharedf[ftag.si][2]/*[0].ref*/, tag.r);
15649				}
15650				/* TODO: factor out contains logic */
15651				var _tag = decode_cell(tag.r);
15652				for(i = 0; i < arrayf.length; ++i)
15653					if(_tag.r >= arrayf[i][0].s.r && _tag.r <= arrayf[i][0].e.r)
15654						if(_tag.c >= arrayf[i][0].s.c && _tag.c <= arrayf[i][0].e.c)
15655							p.F = arrayf[i][1];
15656			}
15657
15658			if(tag.t == null && p.v === undefined) {
15659				if(p.f || p.F) {
15660					p.v = 0; p.t = "n";
15661				} else if(!sheetStubs) continue;
15662				else p.t = "z";
15663			}
15664			else p.t = tag.t || "n";
15665			if(guess.s.c > tagc) guess.s.c = tagc;
15666			if(guess.e.c < tagc) guess.e.c = tagc;
15667			/* 18.18.11 t ST_CellType */
15668			switch(p.t) {
15669				case 'n':
15670					if(p.v == "" || p.v == null) {
15671						if(!sheetStubs) continue;
15672						p.t = 'z';
15673					} else p.v = parseFloat(p.v);
15674					break;
15675				case 's':
15676					if(typeof p.v == 'undefined') {
15677						if(!sheetStubs) continue;
15678						p.t = 'z';
15679					} else {
15680						sstr = strs[parseInt(p.v, 10)];
15681						p.v = sstr.t;
15682						p.r = sstr.r;
15683						if(opts.cellHTML) p.h = sstr.h;
15684					}
15685					break;
15686				case 'str':
15687					p.t = "s";
15688					p.v = (p.v!=null) ? unescapexml(utf8read(p.v), true) : '';
15689					if(opts.cellHTML) p.h = escapehtml(p.v);
15690					break;
15691				case 'inlineStr':
15692					cref = d.match(isregex);
15693					p.t = 's';
15694					if(cref != null && (sstr = parse_si(cref[1]))) {
15695						p.v = sstr.t;
15696						if(opts.cellHTML) p.h = sstr.h;
15697					} else p.v = "";
15698					break;
15699				case 'b': p.v = parsexmlbool(p.v); break;
15700				case 'd':
15701					if(opts.cellDates) p.v = parseDate(p.v, 1);
15702					else { p.v = datenum(parseDate(p.v, 1)); p.t = 'n'; }
15703					break;
15704				/* error string in .w, number in .v */
15705				case 'e':
15706					if(!opts || opts.cellText !== false) p.w = p.v;
15707					p.v = RBErr[p.v]; break;
15708			}
15709			/* formatting */
15710			fmtid = fillid = 0;
15711			cf = null;
15712			if(do_format && tag.s !== undefined) {
15713				cf = styles.CellXf[tag.s];
15714				if(cf != null) {
15715					if(cf.numFmtId != null) fmtid = cf.numFmtId;
15716					if(opts.cellStyles) {
15717						if(cf.fillId != null) fillid = cf.fillId;
15718					}
15719				}
15720			}
15721			safe_format(p, fmtid, fillid, opts, themes, styles);
15722			if(opts.cellDates && do_format && p.t == 'n' && fmt_is_date(table_fmt[fmtid])) { p.t = 'd'; p.v = numdate(p.v); }
15723			if(tag.cm && opts.xlmeta) {
15724				var cm = (opts.xlmeta.Cell||[])[+tag.cm-1];
15725				if(cm && cm.type == 'XLDAPR') p.D = true;
15726			}
15727			if(dense) {
15728				var _r = decode_cell(tag.r);
15729				if(!s[_r.r]) s[_r.r] = [];
15730				s[_r.r][_r.c] = p;
15731			} else s[tag.r] = p;
15732		}
15733	}
15734	if(rows.length > 0) s['!rows'] = rows;
15735}; })();
15736
15737function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*//*::, rels*/)/*:string*/ {
15738	var o/*:Array<string>*/ = [], r/*:Array<string>*/ = [], range = safe_decode_range(ws['!ref']), cell="", ref, rr = "", cols/*:Array<string>*/ = [], R=0, C=0, rows = ws['!rows'];
15739	var dense = Array.isArray(ws);
15740	var params = ({r:rr}/*:any*/), row/*:RowInfo*/, height = -1;
15741	for(C = range.s.c; C <= range.e.c; ++C) cols[C] = encode_col(C);
15742	for(R = range.s.r; R <= range.e.r; ++R) {
15743		r = [];
15744		rr = encode_row(R);
15745		for(C = range.s.c; C <= range.e.c; ++C) {
15746			ref = cols[C] + rr;
15747			var _cell = dense ? (ws[R]||[])[C]: ws[ref];
15748			if(_cell === undefined) continue;
15749			if((cell = write_ws_xml_cell(_cell, ref, ws, opts, idx, wb)) != null) r.push(cell);
15750		}
15751		if(r.length > 0 || (rows && rows[R])) {
15752			params = ({r:rr}/*:any*/);
15753			if(rows && rows[R]) {
15754				row = rows[R];
15755				if(row.hidden) params.hidden = 1;
15756				height = -1;
15757				if(row.hpx) height = px2pt(row.hpx);
15758				else if(row.hpt) height = row.hpt;
15759				if(height > -1) { params.ht = height; params.customHeight = 1; }
15760				if(row.level) { params.outlineLevel = row.level; }
15761			}
15762			o[o.length] = (writextag('row', r.join(""), params));
15763		}
15764	}
15765	if(rows) for(; R < rows.length; ++R) {
15766		if(rows && rows[R]) {
15767			params = ({r:R+1}/*:any*/);
15768			row = rows[R];
15769			if(row.hidden) params.hidden = 1;
15770			height = -1;
15771			if (row.hpx) height = px2pt(row.hpx);
15772			else if (row.hpt) height = row.hpt;
15773			if (height > -1) { params.ht = height; params.customHeight = 1; }
15774			if (row.level) { params.outlineLevel = row.level; }
15775			o[o.length] = (writextag('row', "", params));
15776		}
15777	}
15778	return o.join("");
15779}
15780
15781function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ {
15782	var o = [XML_HEADER, writextag('worksheet', null, {
15783		'xmlns': XMLNS_main[0],
15784		'xmlns:r': XMLNS.r
15785	})];
15786	var s = wb.SheetNames[idx], sidx = 0, rdata = "";
15787	var ws = wb.Sheets[s];
15788	if(ws == null) ws = {};
15789	var ref = ws['!ref'] || 'A1';
15790	var range = safe_decode_range(ref);
15791	if(range.e.c > 0x3FFF || range.e.r > 0xFFFFF) {
15792		if(opts.WTF) throw new Error("Range " + ref + " exceeds format limit A1:XFD1048576");
15793		range.e.c = Math.min(range.e.c, 0x3FFF);
15794		range.e.r = Math.min(range.e.c, 0xFFFFF);
15795		ref = encode_range(range);
15796	}
15797	if(!rels) rels = {};
15798	ws['!comments'] = [];
15799	var _drawing = [];
15800
15801	write_ws_xml_sheetpr(ws, wb, idx, opts, o);
15802
15803	o[o.length] = (writextag('dimension', null, {'ref': ref}));
15804
15805	o[o.length] = write_ws_xml_sheetviews(ws, opts, idx, wb);
15806
15807	/* TODO: store in WB, process styles */
15808	if(opts.sheetFormat) o[o.length] = (writextag('sheetFormatPr', null, {
15809		defaultRowHeight:opts.sheetFormat.defaultRowHeight||'16',
15810		baseColWidth:opts.sheetFormat.baseColWidth||'10',
15811		outlineLevelRow:opts.sheetFormat.outlineLevelRow||'7'
15812	}));
15813
15814	if(ws['!cols'] != null && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols']));
15815
15816	o[sidx = o.length] = '<sheetData/>';
15817	ws['!links'] = [];
15818	if(ws['!ref'] != null) {
15819		rdata = write_ws_xml_data(ws, opts, idx, wb, rels);
15820		if(rdata.length > 0) o[o.length] = (rdata);
15821	}
15822	if(o.length>sidx+1) { o[o.length] = ('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); }
15823
15824	/* sheetCalcPr */
15825
15826	if(ws['!protect']) o[o.length] = write_ws_xml_protection(ws['!protect']);
15827
15828	/* protectedRanges */
15829	/* scenarios */
15830
15831	if(ws['!autofilter'] != null) o[o.length] = write_ws_xml_autofilter(ws['!autofilter'], ws, wb, idx);
15832
15833	/* sortState */
15834	/* dataConsolidate */
15835	/* customSheetViews */
15836
15837	if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges']));
15838
15839	/* phoneticPr */
15840	/* conditionalFormatting */
15841	/* dataValidations */
15842
15843	var relc = -1, rel, rId = -1;
15844	if(/*::(*/ws['!links']/*::||[])*/.length > 0) {
15845		o[o.length] = "<hyperlinks>";
15846		/*::(*/ws['!links']/*::||[])*/.forEach(function(l) {
15847			if(!l[1].Target) return;
15848			rel = ({"ref":l[0]}/*:any*/);
15849			if(l[1].Target.charAt(0) != "#") {
15850				rId = add_rels(rels, -1, escapexml(l[1].Target).replace(/#.*$/, ""), RELS.HLINK);
15851				rel["r:id"] = "rId"+rId;
15852			}
15853			if((relc = l[1].Target.indexOf("#")) > -1) rel.location = escapexml(l[1].Target.slice(relc+1));
15854			if(l[1].Tooltip) rel.tooltip = escapexml(l[1].Tooltip);
15855			rel.display = l[1].display;
15856			o[o.length] = writextag("hyperlink",null,rel);
15857		});
15858		o[o.length] = "</hyperlinks>";
15859	}
15860	delete ws['!links'];
15861
15862	/* printOptions */
15863
15864	if(ws['!margins'] != null) o[o.length] =  write_ws_xml_margins(ws['!margins']);
15865
15866	/* pageSetup */
15867	/* headerFooter */
15868	/* rowBreaks */
15869	/* colBreaks */
15870	/* customProperties */
15871	/* cellWatches */
15872
15873	if(!opts || opts.ignoreEC || (opts.ignoreEC == (void 0))) o[o.length] = writetag("ignoredErrors", writextag("ignoredError", null, {numberStoredAsText:1, sqref:ref}));
15874
15875	/* smartTags */
15876
15877	if(_drawing.length > 0) {
15878		rId = add_rels(rels, -1, "../drawings/drawing" + (idx+1) + ".xml", RELS.DRAW);
15879		o[o.length] = writextag("drawing", null, {"r:id":"rId" + rId});
15880		ws['!drawing'] = _drawing;
15881	}
15882
15883	if(ws['!comments'].length > 0) {
15884		rId = add_rels(rels, -1, "../drawings/vmlDrawing" + (idx+1) + ".vml", RELS.VML);
15885		o[o.length] = writextag("legacyDrawing", null, {"r:id":"rId" + rId});
15886		ws['!legacy'] = rId;
15887	}
15888
15889	/* legacyDrawingHF */
15890	/* picture */
15891	/* oleObjects */
15892	/* controls */
15893	/* webPublishItems */
15894	/* tableParts */
15895	/* extLst */
15896
15897	if(o.length>1) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); }
15898	return o.join("");
15899}
15900
15901/* [MS-XLSB] 2.4.726 BrtRowHdr */
15902function parse_BrtRowHdr(data, length) {
15903	var z = ({}/*:any*/);
15904	var tgt = data.l + length;
15905	z.r = data.read_shift(4);
15906	data.l += 4; // TODO: ixfe
15907	var miyRw = data.read_shift(2);
15908	data.l += 1; // TODO: top/bot padding
15909	var flags = data.read_shift(1);
15910	data.l = tgt;
15911	if(flags & 0x07) z.level = flags & 0x07;
15912	if(flags & 0x10) z.hidden = true;
15913	if(flags & 0x20) z.hpt = miyRw / 20;
15914	return z;
15915}
15916function write_BrtRowHdr(R/*:number*/, range, ws) {
15917	var o = new_buf(17+8*16);
15918	var row = (ws['!rows']||[])[R]||{};
15919	o.write_shift(4, R);
15920
15921	o.write_shift(4, 0); /* TODO: ixfe */
15922
15923	var miyRw = 0x0140;
15924	if(row.hpx) miyRw = px2pt(row.hpx) * 20;
15925	else if(row.hpt) miyRw = row.hpt * 20;
15926	o.write_shift(2, miyRw);
15927
15928	o.write_shift(1, 0); /* top/bot padding */
15929
15930	var flags = 0x0;
15931	if(row.level) flags |= row.level;
15932	if(row.hidden) flags |= 0x10;
15933	if(row.hpx || row.hpt) flags |= 0x20;
15934	o.write_shift(1, flags);
15935
15936	o.write_shift(1, 0); /* phonetic guide */
15937
15938	/* [MS-XLSB] 2.5.8 BrtColSpan explains the mechanism */
15939	var ncolspan = 0, lcs = o.l;
15940	o.l += 4;
15941
15942	var caddr = {r:R, c:0};
15943	for(var i = 0; i < 16; ++i) {
15944		if((range.s.c > ((i+1) << 10)) || (range.e.c < (i << 10))) continue;
15945		var first = -1, last = -1;
15946		for(var j = (i<<10); j < ((i+1)<<10); ++j) {
15947			caddr.c = j;
15948			var cell = Array.isArray(ws) ? (ws[caddr.r]||[])[caddr.c] : ws[encode_cell(caddr)];
15949			if(cell) { if(first < 0) first = j; last = j; }
15950		}
15951		if(first < 0) continue;
15952		++ncolspan;
15953		o.write_shift(4, first);
15954		o.write_shift(4, last);
15955	}
15956
15957	var l = o.l;
15958	o.l = lcs;
15959	o.write_shift(4, ncolspan);
15960	o.l = l;
15961
15962	return o.length > o.l ? o.slice(0, o.l) : o;
15963}
15964function write_row_header(ba, ws, range, R) {
15965	var o = write_BrtRowHdr(R, range, ws);
15966	if((o.length > 17) || (ws['!rows']||[])[R]) write_record(ba, 0x0000 /* BrtRowHdr */, o);
15967}
15968
15969/* [MS-XLSB] 2.4.820 BrtWsDim */
15970var parse_BrtWsDim = parse_UncheckedRfX;
15971var write_BrtWsDim = write_UncheckedRfX;
15972
15973/* [MS-XLSB] 2.4.821 BrtWsFmtInfo */
15974function parse_BrtWsFmtInfo(/*::data, length*/) {
15975}
15976//function write_BrtWsFmtInfo(ws, o) { }
15977
15978/* [MS-XLSB] 2.4.823 BrtWsProp */
15979function parse_BrtWsProp(data, length) {
15980	var z = {};
15981	var f = data[data.l]; ++data.l;
15982	z.above = !(f & 0x40);
15983	z.left  = !(f & 0x80);
15984	/* TODO: pull flags */
15985	data.l += 18;
15986	z.name = parse_XLSBCodeName(data, length - 19);
15987	return z;
15988}
15989function write_BrtWsProp(str, outl, o) {
15990	if(o == null) o = new_buf(84+4*str.length);
15991	var f = 0xC0;
15992	if(outl) {
15993		if(outl.above) f &= ~0x40;
15994		if(outl.left)  f &= ~0x80;
15995	}
15996	o.write_shift(1, f);
15997	for(var i = 1; i < 3; ++i) o.write_shift(1,0);
15998	write_BrtColor({auto:1}, o);
15999	o.write_shift(-4,-1);
16000	o.write_shift(-4,-1);
16001	write_XLSBCodeName(str, o);
16002	return o.slice(0, o.l);
16003}
16004
16005/* [MS-XLSB] 2.4.306 BrtCellBlank */
16006function parse_BrtCellBlank(data) {
16007	var cell = parse_XLSBCell(data);
16008	return [cell];
16009}
16010function write_BrtCellBlank(cell, ncell, o) {
16011	if(o == null) o = new_buf(8);
16012	return write_XLSBCell(ncell, o);
16013}
16014function parse_BrtShortBlank(data) {
16015	var cell = parse_XLSBShortCell(data);
16016	return [cell];
16017}
16018function write_BrtShortBlank(cell, ncell, o) {
16019	if(o == null) o = new_buf(4);
16020	return write_XLSBShortCell(ncell, o);
16021}
16022
16023/* [MS-XLSB] 2.4.307 BrtCellBool */
16024function parse_BrtCellBool(data) {
16025	var cell = parse_XLSBCell(data);
16026	var fBool = data.read_shift(1);
16027	return [cell, fBool, 'b'];
16028}
16029function write_BrtCellBool(cell, ncell, o) {
16030	if(o == null) o = new_buf(9);
16031	write_XLSBCell(ncell, o);
16032	o.write_shift(1, cell.v ? 1 : 0);
16033	return o;
16034}
16035function parse_BrtShortBool(data) {
16036	var cell = parse_XLSBShortCell(data);
16037	var fBool = data.read_shift(1);
16038	return [cell, fBool, 'b'];
16039}
16040function write_BrtShortBool(cell, ncell, o) {
16041	if(o == null) o = new_buf(5);
16042	write_XLSBShortCell(ncell, o);
16043	o.write_shift(1, cell.v ? 1 : 0);
16044	return o;
16045}
16046
16047/* [MS-XLSB] 2.4.308 BrtCellError */
16048function parse_BrtCellError(data) {
16049	var cell = parse_XLSBCell(data);
16050	var bError = data.read_shift(1);
16051	return [cell, bError, 'e'];
16052}
16053function write_BrtCellError(cell, ncell, o) {
16054	if(o == null) o = new_buf(9);
16055	write_XLSBCell(ncell, o);
16056	o.write_shift(1, cell.v);
16057	return o;
16058}
16059function parse_BrtShortError(data) {
16060	var cell = parse_XLSBShortCell(data);
16061	var bError = data.read_shift(1);
16062	return [cell, bError, 'e'];
16063}
16064function write_BrtShortError(cell, ncell, o) {
16065	if(o == null) o = new_buf(8);
16066	write_XLSBShortCell(ncell, o);
16067	o.write_shift(1, cell.v);
16068	o.write_shift(2, 0);
16069	o.write_shift(1, 0);
16070	return o;
16071}
16072
16073
16074/* [MS-XLSB] 2.4.311 BrtCellIsst */
16075function parse_BrtCellIsst(data) {
16076	var cell = parse_XLSBCell(data);
16077	var isst = data.read_shift(4);
16078	return [cell, isst, 's'];
16079}
16080function write_BrtCellIsst(cell, ncell, o) {
16081	if(o == null) o = new_buf(12);
16082	write_XLSBCell(ncell, o);
16083	o.write_shift(4, ncell.v);
16084	return o;
16085}
16086function parse_BrtShortIsst(data) {
16087	var cell = parse_XLSBShortCell(data);
16088	var isst = data.read_shift(4);
16089	return [cell, isst, 's'];
16090}
16091function write_BrtShortIsst(cell, ncell, o) {
16092	if(o == null) o = new_buf(8);
16093	write_XLSBShortCell(ncell, o);
16094	o.write_shift(4, ncell.v);
16095	return o;
16096}
16097
16098/* [MS-XLSB] 2.4.313 BrtCellReal */
16099function parse_BrtCellReal(data) {
16100	var cell = parse_XLSBCell(data);
16101	var value = parse_Xnum(data);
16102	return [cell, value, 'n'];
16103}
16104function write_BrtCellReal(cell, ncell, o) {
16105	if(o == null) o = new_buf(16);
16106	write_XLSBCell(ncell, o);
16107	write_Xnum(cell.v, o);
16108	return o;
16109}
16110function parse_BrtShortReal(data) {
16111	var cell = parse_XLSBShortCell(data);
16112	var value = parse_Xnum(data);
16113	return [cell, value, 'n'];
16114}
16115function write_BrtShortReal(cell, ncell, o) {
16116	if(o == null) o = new_buf(12);
16117	write_XLSBShortCell(ncell, o);
16118	write_Xnum(cell.v, o);
16119	return o;
16120}
16121
16122/* [MS-XLSB] 2.4.314 BrtCellRk */
16123function parse_BrtCellRk(data) {
16124	var cell = parse_XLSBCell(data);
16125	var value = parse_RkNumber(data);
16126	return [cell, value, 'n'];
16127}
16128function write_BrtCellRk(cell, ncell, o) {
16129	if(o == null) o = new_buf(12);
16130	write_XLSBCell(ncell, o);
16131	write_RkNumber(cell.v, o);
16132	return o;
16133}
16134function parse_BrtShortRk(data) {
16135	var cell = parse_XLSBShortCell(data);
16136	var value = parse_RkNumber(data);
16137	return [cell, value, 'n'];
16138}
16139function write_BrtShortRk(cell, ncell, o) {
16140	if(o == null) o = new_buf(8);
16141	write_XLSBShortCell(ncell, o);
16142	write_RkNumber(cell.v, o);
16143	return o;
16144}
16145
16146/* [MS-XLSB] 2.4.323 BrtCellRString */
16147function parse_BrtCellRString(data) {
16148	var cell = parse_XLSBCell(data);
16149	var value = parse_RichStr(data);
16150	return [cell, value, 'is'];
16151}
16152
16153/* [MS-XLSB] 2.4.317 BrtCellSt */
16154function parse_BrtCellSt(data) {
16155	var cell = parse_XLSBCell(data);
16156	var value = parse_XLWideString(data);
16157	return [cell, value, 'str'];
16158}
16159function write_BrtCellSt(cell, ncell, o) {
16160	var data = cell.v == null ? "" : String(cell.v);
16161	if(o == null) o = new_buf(12 + 4 * cell.v.length);
16162	write_XLSBCell(ncell, o);
16163	write_XLWideString(data, o);
16164	return o.length > o.l ? o.slice(0, o.l) : o;
16165}
16166function parse_BrtShortSt(data) {
16167	var cell = parse_XLSBShortCell(data);
16168	var value = parse_XLWideString(data);
16169	return [cell, value, 'str'];
16170}
16171function write_BrtShortSt(cell, ncell, o) {
16172	var data = cell.v == null ? "" : String(cell.v);
16173	if(o == null) o = new_buf(8 + 4 * data.length);
16174	write_XLSBShortCell(ncell, o);
16175	write_XLWideString(data, o);
16176	return o.length > o.l ? o.slice(0, o.l) : o;
16177}
16178
16179/* [MS-XLSB] 2.4.653 BrtFmlaBool */
16180function parse_BrtFmlaBool(data, length, opts) {
16181	var end = data.l + length;
16182	var cell = parse_XLSBCell(data);
16183	cell.r = opts['!row'];
16184	var value = data.read_shift(1);
16185	var o = [cell, value, 'b'];
16186	if(opts.cellFormula) {
16187		data.l += 2;
16188		var formula = parse_XLSBCellParsedFormula(data, end - data.l, opts);
16189		o[3] = stringify_formula(formula, null/*range*/, cell, opts.supbooks, opts);/* TODO */
16190	}
16191	else data.l = end;
16192	return o;
16193}
16194
16195/* [MS-XLSB] 2.4.654 BrtFmlaError */
16196function parse_BrtFmlaError(data, length, opts) {
16197	var end = data.l + length;
16198	var cell = parse_XLSBCell(data);
16199	cell.r = opts['!row'];
16200	var value = data.read_shift(1);
16201	var o = [cell, value, 'e'];
16202	if(opts.cellFormula) {
16203		data.l += 2;
16204		var formula = parse_XLSBCellParsedFormula(data, end - data.l, opts);
16205		o[3] = stringify_formula(formula, null/*range*/, cell, opts.supbooks, opts);/* TODO */
16206	}
16207	else data.l = end;
16208	return o;
16209}
16210
16211/* [MS-XLSB] 2.4.655 BrtFmlaNum */
16212function parse_BrtFmlaNum(data, length, opts) {
16213	var end = data.l + length;
16214	var cell = parse_XLSBCell(data);
16215	cell.r = opts['!row'];
16216	var value = parse_Xnum(data);
16217	var o = [cell, value, 'n'];
16218	if(opts.cellFormula) {
16219		data.l += 2;
16220		var formula = parse_XLSBCellParsedFormula(data, end - data.l, opts);
16221		o[3] = stringify_formula(formula, null/*range*/, cell, opts.supbooks, opts);/* TODO */
16222	}
16223	else data.l = end;
16224	return o;
16225}
16226
16227/* [MS-XLSB] 2.4.656 BrtFmlaString */
16228function parse_BrtFmlaString(data, length, opts) {
16229	var end = data.l + length;
16230	var cell = parse_XLSBCell(data);
16231	cell.r = opts['!row'];
16232	var value = parse_XLWideString(data);
16233	var o = [cell, value, 'str'];
16234	if(opts.cellFormula) {
16235		data.l += 2;
16236		var formula = parse_XLSBCellParsedFormula(data, end - data.l, opts);
16237		o[3] = stringify_formula(formula, null/*range*/, cell, opts.supbooks, opts);/* TODO */
16238	}
16239	else data.l = end;
16240	return o;
16241}
16242
16243/* [MS-XLSB] 2.4.682 BrtMergeCell */
16244var parse_BrtMergeCell = parse_UncheckedRfX;
16245var write_BrtMergeCell = write_UncheckedRfX;
16246/* [MS-XLSB] 2.4.107 BrtBeginMergeCells */
16247function write_BrtBeginMergeCells(cnt, o) {
16248	if(o == null) o = new_buf(4);
16249	o.write_shift(4, cnt);
16250	return o;
16251}
16252
16253/* [MS-XLSB] 2.4.662 BrtHLink */
16254function parse_BrtHLink(data, length/*::, opts*/) {
16255	var end = data.l + length;
16256	var rfx = parse_UncheckedRfX(data, 16);
16257	var relId = parse_XLNullableWideString(data);
16258	var loc = parse_XLWideString(data);
16259	var tooltip = parse_XLWideString(data);
16260	var display = parse_XLWideString(data);
16261	data.l = end;
16262	var o = ({rfx:rfx, relId:relId, loc:loc, display:display}/*:any*/);
16263	if(tooltip) o.Tooltip = tooltip;
16264	return o;
16265}
16266function write_BrtHLink(l, rId) {
16267	var o = new_buf(50+4*(l[1].Target.length + (l[1].Tooltip || "").length));
16268	write_UncheckedRfX({s:decode_cell(l[0]), e:decode_cell(l[0])}, o);
16269	write_RelID("rId" + rId, o);
16270	var locidx = l[1].Target.indexOf("#");
16271	var loc = locidx == -1 ? "" : l[1].Target.slice(locidx+1);
16272	write_XLWideString(loc || "", o);
16273	write_XLWideString(l[1].Tooltip || "", o);
16274	write_XLWideString("", o);
16275	return o.slice(0, o.l);
16276}
16277
16278/* [MS-XLSB] 2.4.692 BrtPane */
16279function parse_BrtPane(/*data, length, opts*/) {
16280}
16281
16282/* [MS-XLSB] 2.4.6 BrtArrFmla */
16283function parse_BrtArrFmla(data, length, opts) {
16284	var end = data.l + length;
16285	var rfx = parse_RfX(data, 16);
16286	var fAlwaysCalc = data.read_shift(1);
16287	var o = [rfx]; o[2] = fAlwaysCalc;
16288	if(opts.cellFormula) {
16289		var formula = parse_XLSBArrayParsedFormula(data, end - data.l, opts);
16290		o[1] = formula;
16291	} else data.l = end;
16292	return o;
16293}
16294
16295/* [MS-XLSB] 2.4.750 BrtShrFmla */
16296function parse_BrtShrFmla(data, length, opts) {
16297	var end = data.l + length;
16298	var rfx = parse_UncheckedRfX(data, 16);
16299	var o = [rfx];
16300	if(opts.cellFormula) {
16301		var formula = parse_XLSBSharedParsedFormula(data, end - data.l, opts);
16302		o[1] = formula;
16303		data.l = end;
16304	} else data.l = end;
16305	return o;
16306}
16307
16308/* [MS-XLSB] 2.4.323 BrtColInfo */
16309/* TODO: once XLS ColInfo is set, combine the functions */
16310function write_BrtColInfo(C/*:number*/, col, o) {
16311	if(o == null) o = new_buf(18);
16312	var p = col_obj_w(C, col);
16313	o.write_shift(-4, C);
16314	o.write_shift(-4, C);
16315	o.write_shift(4, (p.width || 10) * 256);
16316	o.write_shift(4, 0/*ixfe*/); // style
16317	var flags = 0;
16318	if(col.hidden) flags |= 0x01;
16319	if(typeof p.width == 'number') flags |= 0x02;
16320	if(col.level) flags |= (col.level << 8);
16321	o.write_shift(2, flags); // bit flag
16322	return o;
16323}
16324
16325/* [MS-XLSB] 2.4.678 BrtMargins */
16326var BrtMarginKeys = ["left","right","top","bottom","header","footer"];
16327function parse_BrtMargins(data/*::, length, opts*/)/*:Margins*/ {
16328	var margins = ({}/*:any*/);
16329	BrtMarginKeys.forEach(function(k) { margins[k] = parse_Xnum(data, 8); });
16330	return margins;
16331}
16332function write_BrtMargins(margins/*:Margins*/, o) {
16333	if(o == null) o = new_buf(6*8);
16334	default_margins(margins);
16335	BrtMarginKeys.forEach(function(k) { write_Xnum((margins/*:any*/)[k], o); });
16336	return o;
16337}
16338
16339/* [MS-XLSB] 2.4.299 BrtBeginWsView */
16340function parse_BrtBeginWsView(data/*::, length, opts*/) {
16341	var f = data.read_shift(2);
16342	data.l += 28;
16343	return { RTL: f & 0x20 };
16344}
16345function write_BrtBeginWsView(ws, Workbook, o) {
16346	if(o == null) o = new_buf(30);
16347	var f = 0x39c;
16348	if((((Workbook||{}).Views||[])[0]||{}).RTL) f |= 0x20;
16349	o.write_shift(2, f); // bit flag
16350	o.write_shift(4, 0);
16351	o.write_shift(4, 0); // view first row
16352	o.write_shift(4, 0); // view first col
16353	o.write_shift(1, 0); // gridline color ICV
16354	o.write_shift(1, 0);
16355	o.write_shift(2, 0);
16356	o.write_shift(2, 100); // zoom scale
16357	o.write_shift(2, 0);
16358	o.write_shift(2, 0);
16359	o.write_shift(2, 0);
16360	o.write_shift(4, 0); // workbook view id
16361	return o;
16362}
16363
16364/* [MS-XLSB] 2.4.309 BrtCellIgnoreEC */
16365function write_BrtCellIgnoreEC(ref) {
16366	var o = new_buf(24);
16367	o.write_shift(4, 4);
16368	o.write_shift(4, 1);
16369	write_UncheckedRfX(ref, o);
16370	return o;
16371}
16372
16373/* [MS-XLSB] 2.4.748 BrtSheetProtection */
16374function write_BrtSheetProtection(sp, o) {
16375	if(o == null) o = new_buf(16*4+2);
16376	o.write_shift(2, sp.password ? crypto_CreatePasswordVerifier_Method1(sp.password) : 0);
16377	o.write_shift(4, 1); // this record should not be written if no protection
16378	[
16379		["objects",             false], // fObjects
16380		["scenarios",           false], // fScenarios
16381		["formatCells",          true], // fFormatCells
16382		["formatColumns",        true], // fFormatColumns
16383		["formatRows",           true], // fFormatRows
16384		["insertColumns",        true], // fInsertColumns
16385		["insertRows",           true], // fInsertRows
16386		["insertHyperlinks",     true], // fInsertHyperlinks
16387		["deleteColumns",        true], // fDeleteColumns
16388		["deleteRows",           true], // fDeleteRows
16389		["selectLockedCells",   false], // fSelLockedCells
16390		["sort",                 true], // fSort
16391		["autoFilter",           true], // fAutoFilter
16392		["pivotTables",          true], // fPivotTables
16393		["selectUnlockedCells", false]  // fSelUnlockedCells
16394	].forEach(function(n) {
16395		/*:: if(o == null) throw "unreachable"; */
16396		if(n[1]) o.write_shift(4, sp[n[0]] != null && !sp[n[0]] ? 1 : 0);
16397		else      o.write_shift(4, sp[n[0]] != null && sp[n[0]] ? 0 : 1);
16398	});
16399	return o;
16400}
16401
16402function parse_BrtDVal(/*data, length, opts*/) {
16403}
16404function parse_BrtDVal14(/*data, length, opts*/) {
16405}
16406/* [MS-XLSB] 2.1.7.61 Worksheet */
16407function parse_ws_bin(data, _opts, idx, rels, wb/*:WBWBProps*/, themes, styles)/*:Worksheet*/ {
16408	if(!data) return data;
16409	var opts = _opts || {};
16410	if(!rels) rels = {'!id':{}};
16411	if(DENSE != null && opts.dense == null) opts.dense = DENSE;
16412	var s/*:Worksheet*/ = (opts.dense ? [] : {});
16413
16414	var ref;
16415	var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
16416
16417	var state/*:Array<string>*/ = [];
16418	var pass = false, end = false;
16419	var row, p, cf, R, C, addr, sstr, rr, cell/*:Cell*/;
16420	var merges/*:Array<Range>*/ = [];
16421	opts.biff = 12;
16422	opts['!row'] = 0;
16423
16424	var ai = 0, af = false;
16425
16426	var arrayf/*:Array<[Range, string]>*/ = [];
16427	var sharedf = {};
16428	var supbooks = opts.supbooks || /*::(*/wb/*:: :any)*/.supbooks || ([[]]/*:any*/);
16429	supbooks.sharedf = sharedf;
16430	supbooks.arrayf = arrayf;
16431	supbooks.SheetNames = wb.SheetNames || wb.Sheets.map(function(x) { return x.name; });
16432	if(!opts.supbooks) {
16433		opts.supbooks = supbooks;
16434		if(wb.Names) for(var i = 0; i < wb.Names.length; ++i) supbooks[0][i+1] = wb.Names[i];
16435	}
16436
16437	var colinfo/*:Array<ColInfo>*/ = [], rowinfo/*:Array<RowInfo>*/ = [];
16438	var seencol = false;
16439
16440	XLSBRecordEnum[0x0010] = { n:"BrtShortReal", f:parse_BrtShortReal };
16441
16442	var cm, vm;
16443
16444	recordhopper(data, function ws_parse(val, RR, RT) {
16445		if(end) return;
16446		switch(RT) {
16447			case 0x0094: /* 'BrtWsDim' */
16448				ref = val; break;
16449			case 0x0000: /* 'BrtRowHdr' */
16450				row = val;
16451				if(opts.sheetRows && opts.sheetRows <= row.r) end=true;
16452				rr = encode_row(R = row.r);
16453				opts['!row'] = row.r;
16454				if(val.hidden || val.hpt || val.level != null) {
16455					if(val.hpt) val.hpx = pt2px(val.hpt);
16456					rowinfo[val.r] = val;
16457				}
16458				break;
16459
16460			case 0x0002: /* 'BrtCellRk' */
16461			case 0x0003: /* 'BrtCellError' */
16462			case 0x0004: /* 'BrtCellBool' */
16463			case 0x0005: /* 'BrtCellReal' */
16464			case 0x0006: /* 'BrtCellSt' */
16465			case 0x0007: /* 'BrtCellIsst' */
16466			case 0x0008: /* 'BrtFmlaString' */
16467			case 0x0009: /* 'BrtFmlaNum' */
16468			case 0x000A: /* 'BrtFmlaBool' */
16469			case 0x000B: /* 'BrtFmlaError' */
16470			case 0x000D: /* 'BrtShortRk' */
16471			case 0x000E: /* 'BrtShortError' */
16472			case 0x000F: /* 'BrtShortBool' */
16473			case 0x0010: /* 'BrtShortReal' */
16474			case 0x0011: /* 'BrtShortSt' */
16475			case 0x0012: /* 'BrtShortIsst' */
16476			case 0x003E: /* 'BrtCellRString' */
16477				p = ({t:val[2]}/*:any*/);
16478				switch(val[2]) {
16479					case 'n': p.v = val[1]; break;
16480					case 's': sstr = strs[val[1]]; p.v = sstr.t; p.r = sstr.r; break;
16481					case 'b': p.v = val[1] ? true : false; break;
16482					case 'e': p.v = val[1]; if(opts.cellText !== false) p.w = BErr[p.v]; break;
16483					case 'str': p.t = 's'; p.v = val[1]; break;
16484					case 'is': p.t = 's'; p.v = val[1].t; break;
16485				}
16486				if((cf = styles.CellXf[val[0].iStyleRef])) safe_format(p,cf.numFmtId,null,opts, themes, styles);
16487				C = val[0].c == -1 ? C + 1 : val[0].c;
16488				if(opts.dense) { if(!s[R]) s[R] = []; s[R][C] = p; }
16489				else s[encode_col(C) + rr] = p;
16490				if(opts.cellFormula) {
16491					af = false;
16492					for(ai = 0; ai < arrayf.length; ++ai) {
16493						var aii = arrayf[ai];
16494						if(row.r >= aii[0].s.r && row.r <= aii[0].e.r)
16495							if(C >= aii[0].s.c && C <= aii[0].e.c) {
16496								p.F = encode_range(aii[0]); af = true;
16497							}
16498					}
16499					if(!af && val.length > 3) p.f = val[3];
16500				}
16501
16502				if(refguess.s.r > row.r) refguess.s.r = row.r;
16503				if(refguess.s.c > C) refguess.s.c = C;
16504				if(refguess.e.r < row.r) refguess.e.r = row.r;
16505				if(refguess.e.c < C) refguess.e.c = C;
16506				if(opts.cellDates && cf && p.t == 'n' && fmt_is_date(table_fmt[cf.numFmtId])) {
16507					var _d = SSF_parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
16508				}
16509				if(cm) {
16510					if(cm.type == 'XLDAPR') p.D = true;
16511					cm = void 0;
16512				}
16513				if(vm) vm = void 0;
16514				break;
16515
16516			case 0x0001: /* 'BrtCellBlank' */
16517			case 0x000C: /* 'BrtShortBlank' */
16518				if(!opts.sheetStubs || pass) break;
16519				p = ({t:'z',v:void 0}/*:any*/);
16520				C = val[0].c == -1 ? C + 1 : val[0].c;
16521				if(opts.dense) { if(!s[R]) s[R] = []; s[R][C] = p; }
16522				else s[encode_col(C) + rr] = p;
16523				if(refguess.s.r > row.r) refguess.s.r = row.r;
16524				if(refguess.s.c > C) refguess.s.c = C;
16525				if(refguess.e.r < row.r) refguess.e.r = row.r;
16526				if(refguess.e.c < C) refguess.e.c = C;
16527				if(cm) {
16528					if(cm.type == 'XLDAPR') p.D = true;
16529					cm = void 0;
16530				}
16531				if(vm) vm = void 0;
16532				break;
16533
16534			case 0x00B0: /* 'BrtMergeCell' */
16535				merges.push(val); break;
16536
16537			case 0x0031: { /* 'BrtCellMeta' */
16538				cm = ((opts.xlmeta||{}).Cell||[])[val-1];
16539			} break;
16540
16541			case 0x01EE: /* 'BrtHLink' */
16542				var rel = rels['!id'][val.relId];
16543				if(rel) {
16544					val.Target = rel.Target;
16545					if(val.loc) val.Target += "#"+val.loc;
16546					val.Rel = rel;
16547				} else if(val.relId == '') {
16548					val.Target = "#" + val.loc;
16549				}
16550				for(R=val.rfx.s.r;R<=val.rfx.e.r;++R) for(C=val.rfx.s.c;C<=val.rfx.e.c;++C) {
16551					if(opts.dense) {
16552						if(!s[R]) s[R] = [];
16553						if(!s[R][C]) s[R][C] = {t:'z',v:undefined};
16554						s[R][C].l = val;
16555					} else {
16556						addr = encode_cell({c:C,r:R});
16557						if(!s[addr]) s[addr] = {t:'z',v:undefined};
16558						s[addr].l = val;
16559					}
16560				}
16561				break;
16562
16563			case 0x01AA: /* 'BrtArrFmla' */
16564				if(!opts.cellFormula) break;
16565				arrayf.push(val);
16566				cell = ((opts.dense ? s[R][C] : s[encode_col(C) + rr])/*:any*/);
16567				cell.f = stringify_formula(val[1], refguess, {r:row.r, c:C}, supbooks, opts);
16568				cell.F = encode_range(val[0]);
16569				break;
16570			case 0x01AB: /* 'BrtShrFmla' */
16571				if(!opts.cellFormula) break;
16572				sharedf[encode_cell(val[0].s)] = val[1];
16573				cell = (opts.dense ? s[R][C] : s[encode_col(C) + rr]);
16574				cell.f = stringify_formula(val[1], refguess, {r:row.r, c:C}, supbooks, opts);
16575				break;
16576
16577			/* identical to 'ColInfo' in XLS */
16578			case 0x003C: /* 'BrtColInfo' */
16579				if(!opts.cellStyles) break;
16580				while(val.e >= val.s) {
16581					colinfo[val.e--] = { width: val.w/256, hidden: !!(val.flags & 0x01), level: val.level };
16582					if(!seencol) { seencol = true; find_mdw_colw(val.w/256); }
16583					process_col(colinfo[val.e+1]);
16584				}
16585				break;
16586
16587			case 0x00A1: /* 'BrtBeginAFilter' */
16588				s['!autofilter'] = { ref:encode_range(val) };
16589				break;
16590
16591			case 0x01DC: /* 'BrtMargins' */
16592				s['!margins'] = val;
16593				break;
16594
16595			case 0x0093: /* 'BrtWsProp' */
16596				if(!wb.Sheets[idx]) wb.Sheets[idx] = {};
16597				if(val.name) wb.Sheets[idx].CodeName = val.name;
16598				if(val.above || val.left) s['!outline'] = { above: val.above, left: val.left };
16599				break;
16600
16601			case 0x0089: /* 'BrtBeginWsView' */
16602				if(!wb.Views) wb.Views = [{}];
16603				if(!wb.Views[0]) wb.Views[0] = {};
16604				if(val.RTL) wb.Views[0].RTL = true;
16605				break;
16606
16607			case 0x01E5: /* 'BrtWsFmtInfo' */
16608				break;
16609
16610			case 0x0040: /* 'BrtDVal' */
16611			case 0x041D: /* 'BrtDVal14' */
16612				break;
16613
16614			case 0x0097: /* 'BrtPane' */
16615				break;
16616			case 0x0098: /* 'BrtSel' */
16617			case 0x00AF: /* 'BrtAFilterDateGroupItem' */
16618			case 0x0284: /* 'BrtActiveX' */
16619			case 0x0271: /* 'BrtBigName' */
16620			case 0x0232: /* 'BrtBkHim' */
16621			case 0x018C: /* 'BrtBrk' */
16622			case 0x0458: /* 'BrtCFIcon' */
16623			case 0x047A: /* 'BrtCFRuleExt' */
16624			case 0x01D7: /* 'BrtCFVO' */
16625			case 0x041A: /* 'BrtCFVO14' */
16626			case 0x0289: /* 'BrtCellIgnoreEC' */
16627			case 0x0451: /* 'BrtCellIgnoreEC14' */
16628			case 0x024D: /* 'BrtCellSmartTagProperty' */
16629			case 0x025F: /* 'BrtCellWatch' */
16630			case 0x0234: /* 'BrtColor' */
16631			case 0x041F: /* 'BrtColor14' */
16632			case 0x00A8: /* 'BrtColorFilter' */
16633			case 0x00AE: /* 'BrtCustomFilter' */
16634			case 0x049C: /* 'BrtCustomFilter14' */
16635			case 0x01F3: /* 'BrtDRef' */
16636			case 0x01FB: /* 'BrtDXF' */
16637			case 0x0226: /* 'BrtDrawing' */
16638			case 0x00AB: /* 'BrtDynamicFilter' */
16639			case 0x00A7: /* 'BrtFilter' */
16640			case 0x0499: /* 'BrtFilter14' */
16641			case 0x00A9: /* 'BrtIconFilter' */
16642			case 0x049D: /* 'BrtIconFilter14' */
16643			case 0x0227: /* 'BrtLegacyDrawing' */
16644			case 0x0228: /* 'BrtLegacyDrawingHF' */
16645			case 0x0295: /* 'BrtListPart' */
16646			case 0x027F: /* 'BrtOleObject' */
16647			case 0x01DE: /* 'BrtPageSetup' */
16648			case 0x0219: /* 'BrtPhoneticInfo' */
16649			case 0x01DD: /* 'BrtPrintOptions' */
16650			case 0x0218: /* 'BrtRangeProtection' */
16651			case 0x044F: /* 'BrtRangeProtection14' */
16652			case 0x02A8: /* 'BrtRangeProtectionIso' */
16653			case 0x0450: /* 'BrtRangeProtectionIso14' */
16654			case 0x0400: /* 'BrtRwDescent' */
16655			case 0x0297: /* 'BrtSheetCalcProp' */
16656			case 0x0217: /* 'BrtSheetProtection' */
16657			case 0x02A6: /* 'BrtSheetProtectionIso' */
16658			case 0x01F8: /* 'BrtSlc' */
16659			case 0x0413: /* 'BrtSparkline' */
16660			case 0x01AC: /* 'BrtTable' */
16661			case 0x00AA: /* 'BrtTop10Filter' */
16662			case 0x0C00: /* 'BrtUid' */
16663			case 0x0032: /* 'BrtValueMeta' */
16664			case 0x0816: /* 'BrtWebExtension' */
16665			case 0x0415: /* 'BrtWsFmtInfoEx14' */
16666				break;
16667
16668			case 0x0023: /* 'BrtFRTBegin' */
16669				pass = true; break;
16670			case 0x0024: /* 'BrtFRTEnd' */
16671				pass = false; break;
16672			case 0x0025: /* 'BrtACBegin' */
16673				state.push(RT); pass = true; break;
16674			case 0x0026: /* 'BrtACEnd' */
16675				state.pop(); pass = false; break;
16676
16677			default:
16678				if(RR.T){/* empty */}
16679				else if(!pass || opts.WTF) throw new Error("Unexpected record 0x" + RT.toString(16));
16680		}
16681	}, opts);
16682
16683	delete opts.supbooks;
16684	delete opts['!row'];
16685
16686	if(!s["!ref"] && (refguess.s.r < 2000000 || ref && (ref.e.r > 0 || ref.e.c > 0 || ref.s.r > 0 || ref.s.c > 0))) s["!ref"] = encode_range(ref || refguess);
16687	if(opts.sheetRows && s["!ref"]) {
16688		var tmpref = safe_decode_range(s["!ref"]);
16689		if(opts.sheetRows <= +tmpref.e.r) {
16690			tmpref.e.r = opts.sheetRows - 1;
16691			if(tmpref.e.r > refguess.e.r) tmpref.e.r = refguess.e.r;
16692			if(tmpref.e.r < tmpref.s.r) tmpref.s.r = tmpref.e.r;
16693			if(tmpref.e.c > refguess.e.c) tmpref.e.c = refguess.e.c;
16694			if(tmpref.e.c < tmpref.s.c) tmpref.s.c = tmpref.e.c;
16695			s["!fullref"] = s["!ref"];
16696			s["!ref"] = encode_range(tmpref);
16697		}
16698	}
16699	if(merges.length > 0) s["!merges"] = merges;
16700	if(colinfo.length > 0) s["!cols"] = colinfo;
16701	if(rowinfo.length > 0) s["!rows"] = rowinfo;
16702	return s;
16703}
16704
16705/* TODO: something useful -- this is a stub */
16706function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts, ws/*:Worksheet*/, last_seen/*:boolean*/)/*:boolean*/ {
16707	var o/*:any*/ = ({r:R, c:C}/*:any*/);
16708	if(cell.c) ws['!comments'].push([encode_cell(o), cell.c]);
16709	if(cell.v === undefined) return false;
16710	var vv = "";
16711	switch(cell.t) {
16712		case 'b': vv = cell.v ? "1" : "0"; break;
16713		case 'd': // no BrtCellDate :(
16714			cell = dup(cell);
16715			cell.z = cell.z || table_fmt[14];
16716			cell.v = datenum(parseDate(cell.v)); cell.t = 'n';
16717			break;
16718		/* falls through */
16719		case 'n': case 'e': vv = ''+cell.v; break;
16720		default: vv = cell.v; break;
16721	}
16722	/* TODO: cell style */
16723	o.s = get_cell_style(opts.cellXfs, cell, opts);
16724	if(cell.l) ws['!links'].push([encode_cell(o), cell.l]);
16725	switch(cell.t) {
16726		case 's': case 'str':
16727			if(opts.bookSST) {
16728				vv = get_sst_id(opts.Strings, (cell.v == null ? "" : String(cell.v)/*:any*/), opts.revStrings);
16729				o.t = "s"; o.v = vv;
16730				if(last_seen) write_record(ba, 0x0012 /* BrtShortIsst */, write_BrtShortIsst(cell, o));
16731				else write_record(ba, 0x0007 /* BrtCellIsst */, write_BrtCellIsst(cell, o));
16732			} else {
16733				o.t = "str";
16734				if(last_seen) write_record(ba, 0x0011 /* BrtShortSt */, write_BrtShortSt(cell, o));
16735				else write_record(ba, 0x0006 /* BrtCellSt */, write_BrtCellSt(cell, o));
16736			}
16737			return true;
16738		case 'n':
16739			/* TODO: determine threshold for Real vs RK */
16740			if(cell.v == (cell.v | 0) && cell.v > -1000 && cell.v < 1000) {
16741				if(last_seen) write_record(ba, 0x000D /* BrtShortRk */, write_BrtShortRk(cell, o));
16742				else write_record(ba, 0x0002 /* BrtCellRk */, write_BrtCellRk(cell, o));
16743			} else {
16744				if(last_seen) write_record(ba, 0x0010 /* BrtShortReal */, write_BrtShortReal(cell, o));
16745				else write_record(ba, 0x0005 /* BrtCellReal */, write_BrtCellReal(cell, o));
16746			} return true;
16747		case 'b':
16748			o.t = "b";
16749			if(last_seen) write_record(ba, 0x000F /* BrtShortBool */, write_BrtShortBool(cell, o));
16750			else write_record(ba, 0x0004 /* BrtCellBool */, write_BrtCellBool(cell, o));
16751			return true;
16752		case 'e':
16753			o.t = "e";
16754			if(last_seen) write_record(ba, 0x000E /* BrtShortError */, write_BrtShortError(cell, o));
16755			else write_record(ba, 0x0003 /* BrtCellError */, write_BrtCellError(cell, o));
16756			return true;
16757	}
16758	if(last_seen) write_record(ba, 0x000C /* BrtShortBlank */, write_BrtShortBlank(cell, o));
16759	else write_record(ba, 0x0001 /* BrtCellBlank */, write_BrtCellBlank(cell, o));
16760	return true;
16761}
16762
16763function write_CELLTABLE(ba, ws/*:Worksheet*/, idx/*:number*/, opts/*::, wb:Workbook*/) {
16764	var range = safe_decode_range(ws['!ref'] || "A1"), ref, rr = "", cols/*:Array<string>*/ = [];
16765	write_record(ba, 0x0091 /* BrtBeginSheetData */);
16766	var dense = Array.isArray(ws);
16767	var cap = range.e.r;
16768	if(ws['!rows']) cap = Math.max(range.e.r, ws['!rows'].length - 1);
16769	for(var R = range.s.r; R <= cap; ++R) {
16770		rr = encode_row(R);
16771		/* [ACCELLTABLE] */
16772		/* BrtRowHdr */
16773		write_row_header(ba, ws, range, R);
16774		var last_seen = false;
16775		if(R <= range.e.r) for(var C = range.s.c; C <= range.e.c; ++C) {
16776			/* *16384CELL */
16777			if(R === range.s.r) cols[C] = encode_col(C);
16778			ref = cols[C] + rr;
16779			var cell = dense ? (ws[R]||[])[C] : ws[ref];
16780			if(!cell) { last_seen = false; continue; }
16781			/* write cell */
16782			last_seen = write_ws_bin_cell(ba, cell, R, C, opts, ws, last_seen);
16783		}
16784	}
16785	write_record(ba, 0x0092 /* BrtEndSheetData */);
16786}
16787
16788function write_MERGECELLS(ba, ws/*:Worksheet*/) {
16789	if(!ws || !ws['!merges']) return;
16790	write_record(ba, 0x00B1 /* BrtBeginMergeCells */, write_BrtBeginMergeCells(ws['!merges'].length));
16791	ws['!merges'].forEach(function(m) { write_record(ba, 0x00B0 /* BrtMergeCell */, write_BrtMergeCell(m)); });
16792	write_record(ba, 0x00B2 /* BrtEndMergeCells */);
16793}
16794
16795function write_COLINFOS(ba, ws/*:Worksheet*//*::, idx:number, opts, wb:Workbook*/) {
16796	if(!ws || !ws['!cols']) return;
16797	write_record(ba, 0x0186 /* BrtBeginColInfos */);
16798	ws['!cols'].forEach(function(m, i) { if(m) write_record(ba, 0x003C /* 'BrtColInfo' */, write_BrtColInfo(i, m)); });
16799	write_record(ba, 0x0187 /* BrtEndColInfos */);
16800}
16801
16802function write_IGNOREECS(ba, ws/*:Worksheet*/) {
16803	if(!ws || !ws['!ref']) return;
16804	write_record(ba, 0x0288 /* BrtBeginCellIgnoreECs */);
16805	write_record(ba, 0x0289 /* BrtCellIgnoreEC */, write_BrtCellIgnoreEC(safe_decode_range(ws['!ref'])));
16806	write_record(ba, 0x028A /* BrtEndCellIgnoreECs */);
16807}
16808
16809function write_HLINKS(ba, ws/*:Worksheet*/, rels) {
16810	/* *BrtHLink */
16811	ws['!links'].forEach(function(l) {
16812		if(!l[1].Target) return;
16813		var rId = add_rels(rels, -1, l[1].Target.replace(/#.*$/, ""), RELS.HLINK);
16814		write_record(ba, 0x01EE /* BrtHLink */, write_BrtHLink(l, rId));
16815	});
16816	delete ws['!links'];
16817}
16818function write_LEGACYDRAWING(ba, ws/*:Worksheet*/, idx/*:number*/, rels) {
16819	/* [BrtLegacyDrawing] */
16820	if(ws['!comments'].length > 0) {
16821		var rId = add_rels(rels, -1, "../drawings/vmlDrawing" + (idx+1) + ".vml", RELS.VML);
16822		write_record(ba, 0x0227 /* BrtLegacyDrawing */, write_RelID("rId" + rId));
16823		ws['!legacy'] = rId;
16824	}
16825}
16826
16827function write_AUTOFILTER(ba, ws, wb, idx) {
16828	if(!ws['!autofilter']) return;
16829	var data = ws['!autofilter'];
16830	var ref = typeof data.ref === "string" ? data.ref : encode_range(data.ref);
16831
16832	/* Update FilterDatabase defined name for the worksheet */
16833	if(!wb.Workbook) wb.Workbook = ({Sheets:[]}/*:any*/);
16834	if(!wb.Workbook.Names) wb.Workbook.Names = [];
16835	var names/*: Array<any> */ = wb.Workbook.Names;
16836	var range = decode_range(ref);
16837	if(range.s.r == range.e.r) { range.e.r = decode_range(ws["!ref"]).e.r; ref = encode_range(range); }
16838	for(var i = 0; i < names.length; ++i) {
16839		var name = names[i];
16840		if(name.Name != '_xlnm._FilterDatabase') continue;
16841		if(name.Sheet != idx) continue;
16842		name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
16843	}
16844	if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref)  });
16845
16846	write_record(ba, 0x00A1 /* BrtBeginAFilter */, write_UncheckedRfX(safe_decode_range(ref)));
16847	/* *FILTERCOLUMN */
16848	/* [SORTSTATE] */
16849	/* BrtEndAFilter */
16850	write_record(ba, 0x00A2 /* BrtEndAFilter */);
16851}
16852
16853function write_WSVIEWS2(ba, ws, Workbook) {
16854	write_record(ba, 0x0085 /* BrtBeginWsViews */);
16855	{ /* 1*WSVIEW2 */
16856		/* [ACUID] */
16857		write_record(ba, 0x0089 /* BrtBeginWsView */, write_BrtBeginWsView(ws, Workbook));
16858		/* [BrtPane] */
16859		/* *4BrtSel */
16860		/* *4SXSELECT */
16861		/* *FRT */
16862		write_record(ba, 0x008A /* BrtEndWsView */);
16863	}
16864	/* *FRT */
16865	write_record(ba, 0x0086 /* BrtEndWsViews */);
16866}
16867
16868function write_WSFMTINFO(/*::ba, ws*/) {
16869	/* [ACWSFMTINFO] */
16870	// write_record(ba, 0x01E5 /* BrtWsFmtInfo */, write_BrtWsFmtInfo(ws));
16871}
16872
16873function write_SHEETPROTECT(ba, ws) {
16874	if(!ws['!protect']) return;
16875	/* [BrtSheetProtectionIso] */
16876	write_record(ba, 0x0217 /* BrtSheetProtection */, write_BrtSheetProtection(ws['!protect']));
16877}
16878
16879function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) {
16880	var ba = buf_array();
16881	var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {};
16882	var c/*:string*/ = s; try { if(wb && wb.Workbook) c = wb.Workbook.Sheets[idx].CodeName || c; } catch(e) {}
16883	var r = safe_decode_range(ws['!ref'] || "A1");
16884	if(r.e.c > 0x3FFF || r.e.r > 0xFFFFF) {
16885		if(opts.WTF) throw new Error("Range " + (ws['!ref'] || "A1") + " exceeds format limit A1:XFD1048576");
16886		r.e.c = Math.min(r.e.c, 0x3FFF);
16887		r.e.r = Math.min(r.e.c, 0xFFFFF);
16888	}
16889	ws['!links'] = [];
16890	/* passed back to write_zip and removed there */
16891	ws['!comments'] = [];
16892	write_record(ba, 0x0081 /* BrtBeginSheet */);
16893	if(wb.vbaraw || ws['!outline']) write_record(ba, 0x0093 /* BrtWsProp */, write_BrtWsProp(c, ws['!outline']));
16894	write_record(ba, 0x0094 /* BrtWsDim */, write_BrtWsDim(r));
16895	write_WSVIEWS2(ba, ws, wb.Workbook);
16896	write_WSFMTINFO(ba, ws);
16897	write_COLINFOS(ba, ws, idx, opts, wb);
16898	write_CELLTABLE(ba, ws, idx, opts, wb);
16899	/* [BrtSheetCalcProp] */
16900	write_SHEETPROTECT(ba, ws);
16901	/* *([BrtRangeProtectionIso] BrtRangeProtection) */
16902	/* [SCENMAN] */
16903	write_AUTOFILTER(ba, ws, wb, idx);
16904	/* [SORTSTATE] */
16905	/* [DCON] */
16906	/* [USERSHVIEWS] */
16907	write_MERGECELLS(ba, ws);
16908	/* [BrtPhoneticInfo] */
16909	/* *CONDITIONALFORMATTING */
16910	/* [DVALS] */
16911	write_HLINKS(ba, ws, rels);
16912	/* [BrtPrintOptions] */
16913	if(ws['!margins']) write_record(ba, 0x01DC /* BrtMargins */, write_BrtMargins(ws['!margins']));
16914	/* [BrtPageSetup] */
16915	/* [HEADERFOOTER] */
16916	/* [RWBRK] */
16917	/* [COLBRK] */
16918	/* *BrtBigName */
16919	/* [CELLWATCHES] */
16920	if(!opts || opts.ignoreEC || (opts.ignoreEC == (void 0))) write_IGNOREECS(ba, ws);
16921	/* [SMARTTAGS] */
16922	/* [BrtDrawing] */
16923	write_LEGACYDRAWING(ba, ws, idx, rels);
16924	/* [BrtLegacyDrawingHF] */
16925	/* [BrtBkHim] */
16926	/* [OLEOBJECTS] */
16927	/* [ACTIVEXCONTROLS] */
16928	/* [WEBPUBITEMS] */
16929	/* [LISTPARTS] */
16930	/* FRTWORKSHEET */
16931	write_record(ba, 0x0082 /* BrtEndSheet */);
16932	return ba.end();
16933}
16934function parse_Cache(data/*:string*/)/*:[Array<number|string>, string, ?string]*/ {
16935	var col/*:Array<number|string>*/ = [];
16936	var num = data.match(/^<c:numCache>/);
16937	var f;
16938
16939	/* 21.2.2.150 pt CT_NumVal */
16940	(data.match(/<c:pt idx="(\d*)">(.*?)<\/c:pt>/mg)||[]).forEach(function(pt) {
16941		var q = pt.match(/<c:pt idx="(\d*?)"><c:v>(.*)<\/c:v><\/c:pt>/);
16942		if(!q) return;
16943		col[+q[1]] = num ? +q[2] : q[2];
16944	});
16945
16946	/* 21.2.2.71 formatCode CT_Xstring */
16947	var nf = unescapexml((data.match(/<c:formatCode>([\s\S]*?)<\/c:formatCode>/) || ["","General"])[1]);
16948
16949	(data.match(/<c:f>(.*?)<\/c:f>/mg)||[]).forEach(function(F) { f = F.replace(/<.*?>/g,""); });
16950
16951	return [col, nf, f];
16952}
16953
16954/* 21.2 DrawingML - Charts */
16955function parse_chart(data/*:?string*/, name/*:string*/, opts, rels, wb, csheet) {
16956	var cs/*:Worksheet*/ = ((csheet || {"!type":"chart"})/*:any*/);
16957	if(!data) return csheet;
16958	/* 21.2.2.27 chart CT_Chart */
16959
16960	var C = 0, R = 0, col = "A";
16961	var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
16962
16963	/* 21.2.2.120 numCache CT_NumData */
16964	(data.match(/<c:numCache>[\s\S]*?<\/c:numCache>/gm)||[]).forEach(function(nc) {
16965		var cache = parse_Cache(nc);
16966		refguess.s.r = refguess.s.c = 0;
16967		refguess.e.c = C;
16968		col = encode_col(C);
16969		cache[0].forEach(function(n,i) {
16970			cs[col + encode_row(i)] = {t:'n', v:n, z:cache[1] };
16971			R = i;
16972		});
16973		if(refguess.e.r < R) refguess.e.r = R;
16974		++C;
16975	});
16976	if(C > 0) cs["!ref"] = encode_range(refguess);
16977	return cs;
16978}
16979/* 18.3 Worksheets also covers Chartsheets */
16980function parse_cs_xml(data/*:?string*/, opts, idx/*:number*/, rels, wb/*::, themes, styles*/)/*:Worksheet*/ {
16981	if(!data) return data;
16982	/* 18.3.1.12 chartsheet CT_ChartSheet */
16983	if(!rels) rels = {'!id':{}};
16984	var s = ({'!type':"chart", '!drawel':null, '!rel':""}/*:any*/);
16985	var m;
16986
16987	/* 18.3.1.83 sheetPr CT_ChartsheetPr */
16988	var sheetPr = data.match(sheetprregex);
16989	if(sheetPr) parse_ws_xml_sheetpr(sheetPr[0], s, wb, idx);
16990
16991	/* 18.3.1.36 drawing CT_Drawing */
16992	if((m = data.match(/drawing r:id="(.*?)"/))) s['!rel'] = m[1];
16993
16994	if(rels['!id'][s['!rel']]) s['!drawel'] = rels['!id'][s['!rel']];
16995	return s;
16996}
16997//function write_cs_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ {
16998//	var o = [XML_HEADER, writextag('chartsheet', null, {
16999//		'xmlns': XMLNS_main[0],
17000//		'xmlns:r': XMLNS.r
17001//	})];
17002//	o[o.length] = writextag("drawing", null, {"r:id": "rId1"});
17003//	add_rels(rels, -1, "../drawings/drawing" + (idx+1) + ".xml", RELS.DRAW);
17004//	if(o.length>2) { o[o.length] = ('</chartsheet>'); o[1]=o[1].replace("/>",">"); }
17005//	return o.join("");
17006//}
17007
17008/* [MS-XLSB] 2.4.331 BrtCsProp */
17009function parse_BrtCsProp(data, length/*:number*/) {
17010	data.l += 10;
17011	var name = parse_XLWideString(data, length - 10);
17012	return { name: name };
17013}
17014
17015/* [MS-XLSB] 2.1.7.7 Chart Sheet */
17016function parse_cs_bin(data, opts, idx/*:number*/, rels, wb/*::, themes, styles*/)/*:Worksheet*/ {
17017	if(!data) return data;
17018	if(!rels) rels = {'!id':{}};
17019	var s = {'!type':"chart", '!drawel':null, '!rel':""};
17020	var state/*:Array<string>*/ = [];
17021	var pass = false;
17022	recordhopper(data, function cs_parse(val, R, RT) {
17023		switch(RT) {
17024
17025			case 0x0226: /* 'BrtDrawing' */
17026				s['!rel'] = val; break;
17027
17028			case 0x028B: /* 'BrtCsProp' */
17029				if(!wb.Sheets[idx]) wb.Sheets[idx] = {};
17030				if(val.name) wb.Sheets[idx].CodeName = val.name;
17031				break;
17032
17033			case 0x0232: /* 'BrtBkHim' */
17034			case 0x028C: /* 'BrtCsPageSetup' */
17035			case 0x029D: /* 'BrtCsProtection' */
17036			case 0x02A7: /* 'BrtCsProtectionIso' */
17037			case 0x0227: /* 'BrtLegacyDrawing' */
17038			case 0x0228: /* 'BrtLegacyDrawingHF' */
17039			case 0x01DC: /* 'BrtMargins' */
17040			case 0x0C00: /* 'BrtUid' */
17041				break;
17042
17043			case 0x0023: /* 'BrtFRTBegin' */
17044				pass = true; break;
17045			case 0x0024: /* 'BrtFRTEnd' */
17046				pass = false; break;
17047			case 0x0025: /* 'BrtACBegin' */
17048				state.push(RT); break;
17049			case 0x0026: /* 'BrtACEnd' */
17050				state.pop(); break;
17051
17052			default:
17053				if(R.T > 0) state.push(RT);
17054				else if(R.T < 0) state.pop();
17055				else if(!pass || opts.WTF) throw new Error("Unexpected record 0x" + RT.toString(16));
17056		}
17057	}, opts);
17058
17059	if(rels['!id'][s['!rel']]) s['!drawel'] = rels['!id'][s['!rel']];
17060	return s;
17061}
17062//function write_cs_bin(/*::idx:number, opts, wb:Workbook, rels*/) {
17063//	var ba = buf_array();
17064//	write_record(ba, 0x0081 /* BrtBeginSheet */);
17065//	/* [BrtCsProp] */
17066//	/* CSVIEWS */
17067//	/* [[BrtCsProtectionIso] BrtCsProtection] */
17068//	/* [USERCSVIEWS] */
17069//	/* [BrtMargins] */
17070//	/* [BrtCsPageSetup] */
17071//	/* [HEADERFOOTER] */
17072//	/* BrtDrawing */
17073//	/* [BrtLegacyDrawing] */
17074//	/* [BrtLegacyDrawingHF] */
17075//	/* [BrtBkHim] */
17076//	/* [WEBPUBITEMS] */
17077//	/* FRTCHARTSHEET */
17078//	write_record(ba, 0x0082 /* BrtEndSheet */);
17079//	return ba.end();
17080//}
17081/* 18.2.28 (CT_WorkbookProtection) Defaults */
17082var WBPropsDef = [
17083	['allowRefreshQuery',           false, "bool"],
17084	['autoCompressPictures',        true,  "bool"],
17085	['backupFile',                  false, "bool"],
17086	['checkCompatibility',          false, "bool"],
17087	['CodeName',                    ''],
17088	['date1904',                    false, "bool"],
17089	['defaultThemeVersion',         0,      "int"],
17090	['filterPrivacy',               false, "bool"],
17091	['hidePivotFieldList',          false, "bool"],
17092	['promptedSolutions',           false, "bool"],
17093	['publishItems',                false, "bool"],
17094	['refreshAllConnections',       false, "bool"],
17095	['saveExternalLinkValues',      true,  "bool"],
17096	['showBorderUnselectedTables',  true,  "bool"],
17097	['showInkAnnotation',           true,  "bool"],
17098	['showObjects',                 'all'],
17099	['showPivotChartFilter',        false, "bool"],
17100	['updateLinks', 'userSet']
17101];
17102
17103/* 18.2.30 (CT_BookView) Defaults */
17104var WBViewDef = [
17105	['activeTab',                   0,      "int"],
17106	['autoFilterDateGrouping',      true,  "bool"],
17107	['firstSheet',                  0,      "int"],
17108	['minimized',                   false, "bool"],
17109	['showHorizontalScroll',        true,  "bool"],
17110	['showSheetTabs',               true,  "bool"],
17111	['showVerticalScroll',          true,  "bool"],
17112	['tabRatio',                    600,    "int"],
17113	['visibility',                  'visible']
17114	//window{Height,Width}, {x,y}Window
17115];
17116
17117/* 18.2.19 (CT_Sheet) Defaults */
17118var SheetDef = [
17119	//['state', 'visible']
17120];
17121
17122/* 18.2.2  (CT_CalcPr) Defaults */
17123var CalcPrDef = [
17124	['calcCompleted', 'true'],
17125	['calcMode', 'auto'],
17126	['calcOnSave', 'true'],
17127	['concurrentCalc', 'true'],
17128	['fullCalcOnLoad', 'false'],
17129	['fullPrecision', 'true'],
17130	['iterate', 'false'],
17131	['iterateCount', '100'],
17132	['iterateDelta', '0.001'],
17133	['refMode', 'A1']
17134];
17135
17136/* 18.2.3 (CT_CustomWorkbookView) Defaults */
17137/*var CustomWBViewDef = [
17138	['autoUpdate', 'false'],
17139	['changesSavedWin', 'false'],
17140	['includeHiddenRowCol', 'true'],
17141	['includePrintSettings', 'true'],
17142	['maximized', 'false'],
17143	['minimized', 'false'],
17144	['onlySync', 'false'],
17145	['personalView', 'false'],
17146	['showComments', 'commIndicator'],
17147	['showFormulaBar', 'true'],
17148	['showHorizontalScroll', 'true'],
17149	['showObjects', 'all'],
17150	['showSheetTabs', 'true'],
17151	['showStatusbar', 'true'],
17152	['showVerticalScroll', 'true'],
17153	['tabRatio', '600'],
17154	['xWindow', '0'],
17155	['yWindow', '0']
17156];*/
17157
17158function push_defaults_array(target, defaults) {
17159	for(var j = 0; j != target.length; ++j) { var w = target[j];
17160		for(var i=0; i != defaults.length; ++i) { var z = defaults[i];
17161			if(w[z[0]] == null) w[z[0]] = z[1];
17162			else switch(z[2]) {
17163			case "bool": if(typeof w[z[0]] == "string") w[z[0]] = parsexmlbool(w[z[0]]); break;
17164			case "int": if(typeof w[z[0]] == "string") w[z[0]] = parseInt(w[z[0]], 10); break;
17165			}
17166		}
17167	}
17168}
17169function push_defaults(target, defaults) {
17170	for(var i = 0; i != defaults.length; ++i) { var z = defaults[i];
17171		if(target[z[0]] == null) target[z[0]] = z[1];
17172		else switch(z[2]) {
17173			case "bool": if(typeof target[z[0]] == "string") target[z[0]] = parsexmlbool(target[z[0]]); break;
17174			case "int": if(typeof target[z[0]] == "string") target[z[0]] = parseInt(target[z[0]], 10); break;
17175		}
17176	}
17177}
17178
17179function parse_wb_defaults(wb) {
17180	push_defaults(wb.WBProps, WBPropsDef);
17181	push_defaults(wb.CalcPr, CalcPrDef);
17182
17183	push_defaults_array(wb.WBView, WBViewDef);
17184	push_defaults_array(wb.Sheets, SheetDef);
17185
17186	_ssfopts.date1904 = parsexmlbool(wb.WBProps.date1904);
17187}
17188
17189function safe1904(wb/*:Workbook*/)/*:string*/ {
17190	/* TODO: store date1904 somewhere else */
17191	if(!wb.Workbook) return "false";
17192	if(!wb.Workbook.WBProps) return "false";
17193	return parsexmlbool(wb.Workbook.WBProps.date1904) ? "true" : "false";
17194}
17195
17196var badchars = /*#__PURE__*/":][*?\/\\".split("");
17197function check_ws_name(n/*:string*/, safe/*:?boolean*/)/*:boolean*/ {
17198	if(n.length > 31) { if(safe) return false; throw new Error("Sheet names cannot exceed 31 chars"); }
17199	var _good = true;
17200	badchars.forEach(function(c) {
17201		if(n.indexOf(c) == -1) return;
17202		if(!safe) throw new Error("Sheet name cannot contain : \\ / ? * [ ]");
17203		_good = false;
17204	});
17205	return _good;
17206}
17207function check_wb_names(N, S, codes) {
17208	N.forEach(function(n,i) {
17209		check_ws_name(n);
17210		for(var j = 0; j < i; ++j) if(n == N[j]) throw new Error("Duplicate Sheet Name: " + n);
17211		if(codes) {
17212			var cn = (S && S[i] && S[i].CodeName) || n;
17213			if(cn.charCodeAt(0) == 95 && cn.length > 22) throw new Error("Bad Code Name: Worksheet" + cn);
17214		}
17215	});
17216}
17217function check_wb(wb) {
17218	if(!wb || !wb.SheetNames || !wb.Sheets) throw new Error("Invalid Workbook");
17219	if(!wb.SheetNames.length) throw new Error("Workbook is empty");
17220	var Sheets = (wb.Workbook && wb.Workbook.Sheets) || [];
17221	check_wb_names(wb.SheetNames, Sheets, !!wb.vbaraw);
17222	for(var i = 0; i < wb.SheetNames.length; ++i) check_ws(wb.Sheets[wb.SheetNames[i]], wb.SheetNames[i], i);
17223	wb.SheetNames.forEach(function(n, i) {
17224		var ws = wb.Sheets[n];
17225		if(!ws || !ws["!autofilter"]) return;
17226		var DN;
17227		if(!wb.Workbook) wb.Workbook = {};
17228		if(!wb.Workbook.Names) wb.Workbook.Names = [];
17229		wb.Workbook.Names.forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == i) DN = dn; });
17230		var nn = formula_quote_sheet_name(n) + "!" + fix_range(ws["!autofilter"].ref);
17231		if(DN) DN.Ref = nn;
17232		else wb.Workbook.Names.push({Name: "_xlnm._FilterDatabase", Sheet: i, Ref: nn});
17233	});
17234	/* TODO: validate workbook */
17235}
17236/* 18.2 Workbook */
17237var wbnsregex = /<\w+:workbook/;
17238function parse_wb_xml(data, opts)/*:WorkbookFile*/ {
17239	if(!data) throw new Error("Could not find file");
17240	var wb = /*::(*/{ AppVersion:{}, WBProps:{}, WBView:[], Sheets:[], CalcPr:{}, Names:[], xmlns: "" }/*::)*/;
17241	var pass = false, xmlns = "xmlns";
17242	var dname = {}, dnstart = 0;
17243	data.replace(tagregex, function xml_wb(x, idx) {
17244		var y/*:any*/ = parsexmltag(x);
17245		switch(strip_ns(y[0])) {
17246			case '<?xml': break;
17247
17248			/* 18.2.27 workbook CT_Workbook 1 */
17249			case '<workbook':
17250				if(x.match(wbnsregex)) xmlns = "xmlns" + x.match(/<(\w+):/)[1];
17251				wb.xmlns = y[xmlns];
17252				break;
17253			case '</workbook>': break;
17254
17255			/* 18.2.13 fileVersion CT_FileVersion ? */
17256			case '<fileVersion': delete y[0]; wb.AppVersion = y; break;
17257			case '<fileVersion/>': case '</fileVersion>': break;
17258
17259			/* 18.2.12 fileSharing CT_FileSharing ? */
17260			case '<fileSharing':
17261				break;
17262			case '<fileSharing/>': break;
17263
17264			/* 18.2.28 workbookPr CT_WorkbookPr ? */
17265			case '<workbookPr':
17266			case '<workbookPr/>':
17267				WBPropsDef.forEach(function(w) {
17268					if(y[w[0]] == null) return;
17269					switch(w[2]) {
17270						case "bool": wb.WBProps[w[0]] = parsexmlbool(y[w[0]]); break;
17271						case "int": wb.WBProps[w[0]] = parseInt(y[w[0]], 10); break;
17272						default: wb.WBProps[w[0]] = y[w[0]];
17273					}
17274				});
17275				if(y.codeName) wb.WBProps.CodeName = utf8read(y.codeName);
17276				break;
17277			case '</workbookPr>': break;
17278
17279			/* 18.2.29 workbookProtection CT_WorkbookProtection ? */
17280			case '<workbookProtection':
17281				break;
17282			case '<workbookProtection/>': break;
17283
17284			/* 18.2.1  bookViews CT_BookViews ? */
17285			case '<bookViews': case '<bookViews>': case '</bookViews>': break;
17286			/* 18.2.30   workbookView CT_BookView + */
17287			case '<workbookView': case '<workbookView/>': delete y[0]; wb.WBView.push(y); break;
17288			case '</workbookView>': break;
17289
17290			/* 18.2.20 sheets CT_Sheets 1 */
17291			case '<sheets': case '<sheets>': case '</sheets>': break; // aggregate sheet
17292			/* 18.2.19   sheet CT_Sheet + */
17293			case '<sheet':
17294				switch(y.state) {
17295					case "hidden": y.Hidden = 1; break;
17296					case "veryHidden": y.Hidden = 2; break;
17297					default: y.Hidden = 0;
17298				}
17299				delete y.state;
17300				y.name = unescapexml(utf8read(y.name));
17301				delete y[0]; wb.Sheets.push(y); break;
17302			case '</sheet>': break;
17303
17304			/* 18.2.15 functionGroups CT_FunctionGroups ? */
17305			case '<functionGroups': case '<functionGroups/>': break;
17306			/* 18.2.14   functionGroup CT_FunctionGroup + */
17307			case '<functionGroup': break;
17308
17309			/* 18.2.9  externalReferences CT_ExternalReferences ? */
17310			case '<externalReferences': case '</externalReferences>': case '<externalReferences>': break;
17311			/* 18.2.8    externalReference CT_ExternalReference + */
17312			case '<externalReference': break;
17313
17314			/* 18.2.6  definedNames CT_DefinedNames ? */
17315			case '<definedNames/>': break;
17316			case '<definedNames>': case '<definedNames': pass=true; break;
17317			case '</definedNames>': pass=false; break;
17318			/* 18.2.5    definedName CT_DefinedName + */
17319			case '<definedName': {
17320				dname = {};
17321				dname.Name = utf8read(y.name);
17322				if(y.comment) dname.Comment = y.comment;
17323				if(y.localSheetId) dname.Sheet = +y.localSheetId;
17324				if(parsexmlbool(y.hidden||"0")) dname.Hidden = true;
17325				dnstart = idx + x.length;
17326			}	break;
17327			case '</definedName>': {
17328				dname.Ref = unescapexml(utf8read(data.slice(dnstart, idx)));
17329				wb.Names.push(dname);
17330			} break;
17331			case '<definedName/>': break;
17332
17333			/* 18.2.2  calcPr CT_CalcPr ? */
17334			case '<calcPr': delete y[0]; wb.CalcPr = y; break;
17335			case '<calcPr/>': delete y[0]; wb.CalcPr = y; break;
17336			case '</calcPr>': break;
17337
17338			/* 18.2.16 oleSize CT_OleSize ? (ref required) */
17339			case '<oleSize': break;
17340
17341			/* 18.2.4  customWorkbookViews CT_CustomWorkbookViews ? */
17342			case '<customWorkbookViews>': case '</customWorkbookViews>': case '<customWorkbookViews': break;
17343			/* 18.2.3  customWorkbookView CT_CustomWorkbookView + */
17344			case '<customWorkbookView': case '</customWorkbookView>': break;
17345
17346			/* 18.2.18 pivotCaches CT_PivotCaches ? */
17347			case '<pivotCaches>': case '</pivotCaches>': case '<pivotCaches': break;
17348			/* 18.2.17 pivotCache CT_PivotCache ? */
17349			case '<pivotCache': break;
17350
17351			/* 18.2.21 smartTagPr CT_SmartTagPr ? */
17352			case '<smartTagPr': case '<smartTagPr/>': break;
17353
17354			/* 18.2.23 smartTagTypes CT_SmartTagTypes ? */
17355			case '<smartTagTypes': case '<smartTagTypes>': case '</smartTagTypes>': break;
17356			/* 18.2.22 smartTagType CT_SmartTagType ? */
17357			case '<smartTagType': break;
17358
17359			/* 18.2.24 webPublishing CT_WebPublishing ? */
17360			case '<webPublishing': case '<webPublishing/>': break;
17361
17362			/* 18.2.11 fileRecoveryPr CT_FileRecoveryPr ? */
17363			case '<fileRecoveryPr': case '<fileRecoveryPr/>': break;
17364
17365			/* 18.2.26 webPublishObjects CT_WebPublishObjects ? */
17366			case '<webPublishObjects>': case '<webPublishObjects': case '</webPublishObjects>': break;
17367			/* 18.2.25 webPublishObject CT_WebPublishObject ? */
17368			case '<webPublishObject': break;
17369
17370			/* 18.2.10 extLst CT_ExtensionList ? */
17371			case '<extLst': case '<extLst>': case '</extLst>': case '<extLst/>': break;
17372			/* 18.2.7  ext CT_Extension + */
17373			case '<ext': pass=true; break; //TODO: check with versions of excel
17374			case '</ext>': pass=false; break;
17375
17376			/* Others */
17377			case '<ArchID': break;
17378			case '<AlternateContent':
17379			case '<AlternateContent>': pass=true; break;
17380			case '</AlternateContent>': pass=false; break;
17381
17382			/* TODO */
17383			case '<revisionPtr': break;
17384
17385			default: if(!pass && opts.WTF) throw new Error('unrecognized ' + y[0] + ' in workbook');
17386		}
17387		return x;
17388	});
17389	if(XMLNS_main.indexOf(wb.xmlns) === -1) throw new Error("Unknown Namespace: " + wb.xmlns);
17390
17391	parse_wb_defaults(wb);
17392
17393	return wb;
17394}
17395
17396function write_wb_xml(wb/*:Workbook*//*::, opts:?WriteOpts*/)/*:string*/ {
17397	var o = [XML_HEADER];
17398	o[o.length] = writextag('workbook', null, {
17399		'xmlns': XMLNS_main[0],
17400		//'xmlns:mx': XMLNS.mx,
17401		//'xmlns:s': XMLNS_main[0],
17402		'xmlns:r': XMLNS.r
17403	});
17404
17405	var write_names = (wb.Workbook && (wb.Workbook.Names||[]).length > 0);
17406
17407	/* fileVersion */
17408	/* fileSharing */
17409
17410	var workbookPr/*:any*/ = ({codeName:"ThisWorkbook"}/*:any*/);
17411	if(wb.Workbook && wb.Workbook.WBProps) {
17412		WBPropsDef.forEach(function(x) {
17413			/*:: if(!wb.Workbook || !wb.Workbook.WBProps) throw "unreachable"; */
17414			if((wb.Workbook.WBProps[x[0]]/*:any*/) == null) return;
17415			if((wb.Workbook.WBProps[x[0]]/*:any*/) == x[1]) return;
17416			workbookPr[x[0]] = (wb.Workbook.WBProps[x[0]]/*:any*/);
17417		});
17418		/*:: if(!wb.Workbook || !wb.Workbook.WBProps) throw "unreachable"; */
17419		if(wb.Workbook.WBProps.CodeName) { workbookPr.codeName = wb.Workbook.WBProps.CodeName; delete workbookPr.CodeName; }
17420	}
17421	o[o.length] = (writextag('workbookPr', null, workbookPr));
17422
17423	/* workbookProtection */
17424
17425	var sheets = wb.Workbook && wb.Workbook.Sheets || [];
17426	var i = 0;
17427
17428	/* bookViews only written if first worksheet is hidden */
17429	if(sheets && sheets[0] && !!sheets[0].Hidden) {
17430		o[o.length] = "<bookViews>";
17431		for(i = 0; i != wb.SheetNames.length; ++i) {
17432			if(!sheets[i]) break;
17433			if(!sheets[i].Hidden) break;
17434		}
17435		if(i == wb.SheetNames.length) i = 0;
17436		o[o.length] = '<workbookView firstSheet="' + i + '" activeTab="' + i + '"/>';
17437		o[o.length] = "</bookViews>";
17438	}
17439
17440	o[o.length] = "<sheets>";
17441	for(i = 0; i != wb.SheetNames.length; ++i) {
17442		var sht = ({name:escapexml(wb.SheetNames[i].slice(0,31))}/*:any*/);
17443		sht.sheetId = ""+(i+1);
17444		sht["r:id"] = "rId"+(i+1);
17445		if(sheets[i]) switch(sheets[i].Hidden) {
17446			case 1: sht.state = "hidden"; break;
17447			case 2: sht.state = "veryHidden"; break;
17448		}
17449		o[o.length] = (writextag('sheet',null,sht));
17450	}
17451	o[o.length] = "</sheets>";
17452
17453	/* functionGroups */
17454	/* externalReferences */
17455
17456	if(write_names) {
17457		o[o.length] = "<definedNames>";
17458		if(wb.Workbook && wb.Workbook.Names) wb.Workbook.Names.forEach(function(n) {
17459			var d/*:any*/ = {name:n.Name};
17460			if(n.Comment) d.comment = n.Comment;
17461			if(n.Sheet != null) d.localSheetId = ""+n.Sheet;
17462			if(n.Hidden) d.hidden = "1";
17463			if(!n.Ref) return;
17464			o[o.length] = writextag('definedName', escapexml(n.Ref), d);
17465		});
17466		o[o.length] = "</definedNames>";
17467	}
17468
17469	/* calcPr */
17470	/* oleSize */
17471	/* customWorkbookViews */
17472	/* pivotCaches */
17473	/* smartTagPr */
17474	/* smartTagTypes */
17475	/* webPublishing */
17476	/* fileRecoveryPr */
17477	/* webPublishObjects */
17478	/* extLst */
17479
17480	if(o.length>2){ o[o.length] = '</workbook>'; o[1]=o[1].replace("/>",">"); }
17481	return o.join("");
17482}
17483/* [MS-XLSB] 2.4.304 BrtBundleSh */
17484function parse_BrtBundleSh(data, length/*:number*/) {
17485	var z = {};
17486	z.Hidden = data.read_shift(4); //hsState ST_SheetState
17487	z.iTabID = data.read_shift(4);
17488	z.strRelID = parse_RelID(data,length-8);
17489	z.name = parse_XLWideString(data);
17490	return z;
17491}
17492function write_BrtBundleSh(data, o) {
17493	if(!o) o = new_buf(127);
17494	o.write_shift(4, data.Hidden);
17495	o.write_shift(4, data.iTabID);
17496	write_RelID(data.strRelID, o);
17497	write_XLWideString(data.name.slice(0,31), o);
17498	return o.length > o.l ? o.slice(0, o.l) : o;
17499}
17500
17501/* [MS-XLSB] 2.4.815 BrtWbProp */
17502function parse_BrtWbProp(data, length)/*:WBProps*/ {
17503	var o/*:WBProps*/ = ({}/*:any*/);
17504	var flags = data.read_shift(4);
17505	o.defaultThemeVersion = data.read_shift(4);
17506	var strName = (length > 8) ? parse_XLWideString(data) : "";
17507	if(strName.length > 0) o.CodeName = strName;
17508	o.autoCompressPictures = !!(flags & 0x10000);
17509	o.backupFile = !!(flags & 0x40);
17510	o.checkCompatibility = !!(flags & 0x1000);
17511	o.date1904 = !!(flags & 0x01);
17512	o.filterPrivacy = !!(flags & 0x08);
17513	o.hidePivotFieldList = !!(flags & 0x400);
17514	o.promptedSolutions = !!(flags & 0x10);
17515	o.publishItems = !!(flags & 0x800);
17516	o.refreshAllConnections = !!(flags & 0x40000);
17517	o.saveExternalLinkValues = !!(flags & 0x80);
17518	o.showBorderUnselectedTables = !!(flags & 0x04);
17519	o.showInkAnnotation = !!(flags & 0x20);
17520	o.showObjects = ["all", "placeholders", "none"][(flags >> 13) & 0x03];
17521	o.showPivotChartFilter = !!(flags & 0x8000);
17522	o.updateLinks = ["userSet", "never", "always"][(flags >> 8) & 0x03];
17523	return o;
17524}
17525function write_BrtWbProp(data/*:?WBProps*/, o) {
17526	if(!o) o = new_buf(72);
17527	var flags = 0;
17528	if(data) {
17529		/* TODO: mirror parse_BrtWbProp fields */
17530		if(data.date1904) flags |= 0x01;
17531		if(data.filterPrivacy) flags |= 0x08;
17532	}
17533	o.write_shift(4, flags);
17534	o.write_shift(4, 0);
17535	write_XLSBCodeName(data && data.CodeName || "ThisWorkbook", o);
17536	return o.slice(0, o.l);
17537}
17538
17539function parse_BrtFRTArchID$(data, length) {
17540	var o = {};
17541	data.read_shift(4);
17542	o.ArchID = data.read_shift(4);
17543	data.l += length - 8;
17544	return o;
17545}
17546
17547/* [MS-XLSB] 2.4.687 BrtName */
17548function parse_BrtName(data, length, opts) {
17549	var end = data.l + length;
17550	var flags = data.read_shift(4);
17551	data.l += 1; //var chKey = data.read_shift(1);
17552	var itab = data.read_shift(4);
17553	var name = parse_XLNameWideString(data);
17554	var formula = parse_XLSBNameParsedFormula(data, 0, opts);
17555	var comment = parse_XLNullableWideString(data);
17556	if(flags & 0x20) name = "_xlnm." + name;
17557	//if(0 /* fProc */) {
17558		// unusedstring1: XLNullableWideString
17559		// description: XLNullableWideString
17560		// helpTopic: XLNullableWideString
17561		// unusedstring2: XLNullableWideString
17562	//}
17563	data.l = end;
17564	var out = ({Name:name, Ptg:formula, Flags: flags}/*:any*/);
17565	if(itab < 0xFFFFFFF) out.Sheet = itab;
17566	if(comment) out.Comment = comment;
17567	return out;
17568}
17569function write_BrtName(name, wb) {
17570	var o = new_buf(9);
17571	var flags = 0;
17572	var dname = name.Name;
17573	if(XLSLblBuiltIn.indexOf(dname) > -1) { flags |= 0x20; dname = dname.slice(6); }
17574	o.write_shift(4, flags); // flags
17575	o.write_shift(1, 0); // chKey
17576	o.write_shift(4, name.Sheet == null ? 0xFFFFFFFF : name.Sheet);
17577
17578	var arr = [
17579		o,
17580		write_XLWideString(dname),
17581		write_XLSBNameParsedFormula(name.Ref, wb)
17582	];
17583	if(name.Comment) arr.push(write_XLNullableWideString(name.Comment));
17584	else {
17585		var x = new_buf(4);
17586		x.write_shift(4, 0xFFFFFFFF);
17587		arr.push(x);
17588	}
17589
17590	// if macro (flags & 0x0F):
17591	// write_shift(4, 0xFFFFFFFF);
17592	// write_XLNullableWideString(description)
17593	// write_XLNullableWideString(helpTopic)
17594	// write_shift(4, 0xFFFFFFFF);
17595
17596	return bconcat(arr);
17597}
17598
17599/* [MS-XLSB] 2.1.7.61 Workbook */
17600function parse_wb_bin(data, opts)/*:WorkbookFile*/ {
17601	var wb = { AppVersion:{}, WBProps:{}, WBView:[], Sheets:[], CalcPr:{}, xmlns: "" };
17602	var state/*:Array<string>*/ = [];
17603	var pass = false;
17604
17605	if(!opts) opts = {};
17606	opts.biff = 12;
17607
17608	var Names = [];
17609	var supbooks = ([[]]/*:any*/);
17610	supbooks.SheetNames = [];
17611	supbooks.XTI = [];
17612
17613	XLSBRecordEnum[0x0010] = { n:"BrtFRTArchID$", f:parse_BrtFRTArchID$ };
17614
17615	recordhopper(data, function hopper_wb(val, R, RT) {
17616		switch(RT) {
17617			case 0x009C: /* 'BrtBundleSh' */
17618				supbooks.SheetNames.push(val.name);
17619				wb.Sheets.push(val); break;
17620
17621			case 0x0099: /* 'BrtWbProp' */
17622				wb.WBProps = val; break;
17623
17624			case 0x0027: /* 'BrtName' */
17625				if(val.Sheet != null) opts.SID = val.Sheet;
17626				val.Ref = stringify_formula(val.Ptg, null, null, supbooks, opts);
17627				delete opts.SID;
17628				delete val.Ptg;
17629				Names.push(val);
17630				break;
17631			case 0x040C: /* 'BrtNameExt' */ break;
17632
17633			case 0x0165: /* 'BrtSupSelf' */
17634			case 0x0166: /* 'BrtSupSame' */
17635			case 0x0163: /* 'BrtSupBookSrc' */
17636			case 0x029B: /* 'BrtSupAddin' */
17637				if(!supbooks[0].length) supbooks[0] = [RT, val];
17638				else supbooks.push([RT, val]);
17639				supbooks[supbooks.length - 1].XTI = [];
17640				break;
17641			case 0x016A: /* 'BrtExternSheet' */
17642				if(supbooks.length === 0) { supbooks[0] = []; supbooks[0].XTI = []; }
17643				supbooks[supbooks.length - 1].XTI = supbooks[supbooks.length - 1].XTI.concat(val);
17644				supbooks.XTI = supbooks.XTI.concat(val);
17645				break;
17646			case 0x0169: /* 'BrtPlaceholderName' */
17647				break;
17648
17649			case 0x0817: /* 'BrtAbsPath15' */
17650			case 0x009E: /* 'BrtBookView' */
17651			case 0x008F: /* 'BrtBeginBundleShs' */
17652			case 0x0298: /* 'BrtBeginFnGroup' */
17653			case 0x0161: /* 'BrtBeginExternals' */
17654				break;
17655
17656			/* case 'BrtModelTimeGroupingCalcCol' */
17657			case 0x0C00: /* 'BrtUid' */
17658			case 0x0C01: /* 'BrtRevisionPtr' */
17659			case 0x0216: /* 'BrtBookProtection' */
17660			case 0x02A5: /* 'BrtBookProtectionIso' */
17661			case 0x009D: /* 'BrtCalcProp' */
17662			case 0x0262: /* 'BrtCrashRecErr' */
17663			case 0x0802: /* 'BrtDecoupledPivotCacheID' */
17664			case 0x009B: /* 'BrtFileRecover' */
17665			case 0x0224: /* 'BrtFileSharing' */
17666			case 0x02A4: /* 'BrtFileSharingIso' */
17667			case 0x0080: /* 'BrtFileVersion' */
17668			case 0x0299: /* 'BrtFnGroup' */
17669			case 0x0850: /* 'BrtModelRelationship' */
17670			case 0x084D: /* 'BrtModelTable' */
17671			case 0x0225: /* 'BrtOleSize' */
17672			case 0x0805: /* 'BrtPivotTableRef' */
17673			case 0x0254: /* 'BrtSmartTagType' */
17674			case 0x081C: /* 'BrtTableSlicerCacheID' */
17675			case 0x081B: /* 'BrtTableSlicerCacheIDs' */
17676			case 0x0822: /* 'BrtTimelineCachePivotCacheID' */
17677			case 0x018D: /* 'BrtUserBookView' */
17678			case 0x009A: /* 'BrtWbFactoid' */
17679			case 0x045D: /* 'BrtWbProp14' */
17680			case 0x0229: /* 'BrtWebOpt' */
17681			case 0x082B: /* 'BrtWorkBookPr15' */
17682				break;
17683
17684			case 0x0023: /* 'BrtFRTBegin' */
17685				state.push(RT); pass = true; break;
17686			case 0x0024: /* 'BrtFRTEnd' */
17687				state.pop(); pass = false; break;
17688			case 0x0025: /* 'BrtACBegin' */
17689				state.push(RT); pass = true; break;
17690			case 0x0026: /* 'BrtACEnd' */
17691				state.pop(); pass = false; break;
17692
17693			case 0x0010: /* 'BrtFRTArchID$' */ break;
17694
17695			default:
17696				if(R.T){/* empty */}
17697				else if(!pass || (opts.WTF && state[state.length-1] != 0x0025 /* BrtACBegin */ && state[state.length-1] != 0x0023 /* BrtFRTBegin */)) throw new Error("Unexpected record 0x" + RT.toString(16));
17698		}
17699	}, opts);
17700
17701	parse_wb_defaults(wb);
17702
17703	// $FlowIgnore
17704	wb.Names = Names;
17705
17706	(wb/*:any*/).supbooks = supbooks;
17707	return wb;
17708}
17709
17710function write_BUNDLESHS(ba, wb/*::, opts*/) {
17711	write_record(ba, 0x008F /* BrtBeginBundleShs */);
17712	for(var idx = 0; idx != wb.SheetNames.length; ++idx) {
17713		var viz = wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx] && wb.Workbook.Sheets[idx].Hidden || 0;
17714		var d = { Hidden: viz, iTabID: idx+1, strRelID: 'rId' + (idx+1), name: wb.SheetNames[idx] };
17715		write_record(ba, 0x009C /* BrtBundleSh */, write_BrtBundleSh(d));
17716	}
17717	write_record(ba, 0x0090 /* BrtEndBundleShs */);
17718}
17719
17720/* [MS-XLSB] 2.4.649 BrtFileVersion */
17721function write_BrtFileVersion(data, o) {
17722	if(!o) o = new_buf(127);
17723	for(var i = 0; i != 4; ++i) o.write_shift(4, 0);
17724	write_XLWideString("SheetJS", o);
17725	write_XLWideString(XLSX.version, o);
17726	write_XLWideString(XLSX.version, o);
17727	write_XLWideString("7262", o);
17728	return o.length > o.l ? o.slice(0, o.l) : o;
17729}
17730
17731/* [MS-XLSB] 2.4.301 BrtBookView */
17732function write_BrtBookView(idx, o) {
17733	if(!o) o = new_buf(29);
17734	o.write_shift(-4, 0);
17735	o.write_shift(-4, 460);
17736	o.write_shift(4,  28800);
17737	o.write_shift(4,  17600);
17738	o.write_shift(4,  500);
17739	o.write_shift(4,  idx);
17740	o.write_shift(4,  idx);
17741	var flags = 0x78;
17742	o.write_shift(1,  flags);
17743	return o.length > o.l ? o.slice(0, o.l) : o;
17744}
17745
17746function write_BOOKVIEWS(ba, wb/*::, opts*/) {
17747	/* required if hidden tab appears before visible tab */
17748	if(!wb.Workbook || !wb.Workbook.Sheets) return;
17749	var sheets = wb.Workbook.Sheets;
17750	var i = 0, vistab = -1, hidden = -1;
17751	for(; i < sheets.length; ++i) {
17752		if(!sheets[i] || !sheets[i].Hidden && vistab == -1) vistab = i;
17753		else if(sheets[i].Hidden == 1 && hidden == -1) hidden = i;
17754	}
17755	if(hidden > vistab) return;
17756	write_record(ba, 0x0087 /* BrtBeginBookViews */);
17757	write_record(ba, 0x009E /* BrtBookView */, write_BrtBookView(vistab));
17758	/* 1*(BrtBookView *FRT) */
17759	write_record(ba, 0x0088 /* BrtEndBookViews */);
17760}
17761
17762function write_BRTNAMES(ba, wb) {
17763	if(!wb.Workbook || !wb.Workbook.Names) return;
17764	wb.Workbook.Names.forEach(function(name) { try {
17765		if(name.Flags & 0x0e) return; // TODO: macro name write
17766		write_record(ba, 0x0027 /* BrtName */, write_BrtName(name, wb));
17767	} catch(e) {
17768		console.error("Could not serialize defined name " + JSON.stringify(name));
17769	} });
17770}
17771
17772function write_SELF_EXTERNS_xlsb(wb) {
17773	var L = wb.SheetNames.length;
17774	var o = new_buf(12 * L + 28);
17775	o.write_shift(4, L + 2);
17776	o.write_shift(4, 0); o.write_shift(4, -2); o.write_shift(4, -2); // workbook-level reference
17777	o.write_shift(4, 0); o.write_shift(4, -1); o.write_shift(4, -1); // #REF!...
17778	for(var i = 0; i < L; ++i) {
17779		o.write_shift(4, 0); o.write_shift(4, i); o.write_shift(4, i);
17780	}
17781	return o;
17782}
17783function write_EXTERNALS_xlsb(ba, wb) {
17784	write_record(ba, 0x0161 /* BrtBeginExternals */);
17785	write_record(ba, 0x0165 /* BrtSupSelf */);
17786	write_record(ba, 0x016A /* BrtExternSheet */, write_SELF_EXTERNS_xlsb(wb, 0));
17787	write_record(ba, 0x0162 /* BrtEndExternals */);
17788}
17789
17790/* [MS-XLSB] 2.4.305 BrtCalcProp */
17791/*function write_BrtCalcProp(data, o) {
17792	if(!o) o = new_buf(26);
17793	o.write_shift(4,0); // force recalc
17794	o.write_shift(4,1);
17795	o.write_shift(4,0);
17796	write_Xnum(0, o);
17797	o.write_shift(-4, 1023);
17798	o.write_shift(1, 0x33);
17799	o.write_shift(1, 0x00);
17800	return o;
17801}*/
17802
17803/* [MS-XLSB] 2.4.646 BrtFileRecover */
17804/*function write_BrtFileRecover(data, o) {
17805	if(!o) o = new_buf(1);
17806	o.write_shift(1,0);
17807	return o;
17808}*/
17809
17810/* [MS-XLSB] 2.1.7.61 Workbook */
17811function write_wb_bin(wb, opts) {
17812	var ba = buf_array();
17813	write_record(ba, 0x0083 /* BrtBeginBook */);
17814	write_record(ba, 0x0080 /* BrtFileVersion */, write_BrtFileVersion());
17815	/* [[BrtFileSharingIso] BrtFileSharing] */
17816	write_record(ba, 0x0099 /* BrtWbProp */, write_BrtWbProp(wb.Workbook && wb.Workbook.WBProps || null));
17817	/* [ACABSPATH] */
17818	/* [[BrtBookProtectionIso] BrtBookProtection] */
17819	write_BOOKVIEWS(ba, wb, opts);
17820	write_BUNDLESHS(ba, wb, opts);
17821	/* [FNGROUP] */
17822	write_EXTERNALS_xlsb(ba, wb);
17823	if((wb.Workbook||{}).Names) write_BRTNAMES(ba, wb);
17824	/* write_record(ba, 0x009D BrtCalcProp, write_BrtCalcProp()); */
17825	/* [BrtOleSize] */
17826	/* *(BrtUserBookView *FRT) */
17827	/* [PIVOTCACHEIDS] */
17828	/* [BrtWbFactoid] */
17829	/* [SMARTTAGTYPES] */
17830	/* [BrtWebOpt] */
17831	/* write_record(ba, 0x009B BrtFileRecover, write_BrtFileRecover()); */
17832	/* [WEBPUBITEMS] */
17833	/* [CRERRS] */
17834	/* FRTWORKBOOK */
17835	write_record(ba, 0x0084 /* BrtEndBook */);
17836
17837	return ba.end();
17838}
17839function parse_wb(data, name/*:string*/, opts)/*:WorkbookFile*/ {
17840	if(name.slice(-4)===".bin") return parse_wb_bin((data/*:any*/), opts);
17841	return parse_wb_xml((data/*:any*/), opts);
17842}
17843
17844function parse_ws(data, name/*:string*/, idx/*:number*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
17845	if(name.slice(-4)===".bin") return parse_ws_bin((data/*:any*/), opts, idx, rels, wb, themes, styles);
17846	return parse_ws_xml((data/*:any*/), opts, idx, rels, wb, themes, styles);
17847}
17848
17849function parse_cs(data, name/*:string*/, idx/*:number*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
17850	if(name.slice(-4)===".bin") return parse_cs_bin((data/*:any*/), opts, idx, rels, wb, themes, styles);
17851	return parse_cs_xml((data/*:any*/), opts, idx, rels, wb, themes, styles);
17852}
17853
17854function parse_ms(data, name/*:string*/, idx/*:number*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
17855	if(name.slice(-4)===".bin") return parse_ms_bin((data/*:any*/), opts, idx, rels, wb, themes, styles);
17856	return parse_ms_xml((data/*:any*/), opts, idx, rels, wb, themes, styles);
17857}
17858
17859function parse_ds(data, name/*:string*/, idx/*:number*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
17860	if(name.slice(-4)===".bin") return parse_ds_bin((data/*:any*/), opts, idx, rels, wb, themes, styles);
17861	return parse_ds_xml((data/*:any*/), opts, idx, rels, wb, themes, styles);
17862}
17863
17864function parse_sty(data, name/*:string*/, themes, opts) {
17865	if(name.slice(-4)===".bin") return parse_sty_bin((data/*:any*/), themes, opts);
17866	return parse_sty_xml((data/*:any*/), themes, opts);
17867}
17868
17869function parse_sst(data, name/*:string*/, opts)/*:SST*/ {
17870	if(name.slice(-4)===".bin") return parse_sst_bin((data/*:any*/), opts);
17871	return parse_sst_xml((data/*:any*/), opts);
17872}
17873
17874function parse_cmnt(data, name/*:string*/, opts)/*:Array<RawComment>*/ {
17875	if(name.slice(-4)===".bin") return parse_comments_bin((data/*:any*/), opts);
17876	return parse_comments_xml((data/*:any*/), opts);
17877}
17878
17879function parse_cc(data, name/*:string*/, opts) {
17880	if(name.slice(-4)===".bin") return parse_cc_bin((data/*:any*/), name, opts);
17881	return parse_cc_xml((data/*:any*/), name, opts);
17882}
17883
17884function parse_xlink(data, rel, name/*:string*/, opts) {
17885	if(name.slice(-4)===".bin") return parse_xlink_bin((data/*:any*/), rel, name, opts);
17886	return parse_xlink_xml((data/*:any*/), rel, name, opts);
17887}
17888
17889function parse_xlmeta(data, name/*:string*/, opts) {
17890	if(name.slice(-4)===".bin") return parse_xlmeta_bin((data/*:any*/), name, opts);
17891	return parse_xlmeta_xml((data/*:any*/), name, opts);
17892}
17893var attregexg2=/([\w:]+)=((?:")([^"]*)(?:")|(?:')([^']*)(?:'))/g;
17894var attregex2=/([\w:]+)=((?:")(?:[^"]*)(?:")|(?:')(?:[^']*)(?:'))/;
17895function xlml_parsexmltag(tag/*:string*/, skip_root/*:?boolean*/) {
17896	var words = tag.split(/\s+/);
17897	var z/*:any*/ = ([]/*:any*/); if(!skip_root) z[0] = words[0];
17898	if(words.length === 1) return z;
17899	var m = tag.match(attregexg2), y, j, w, i;
17900	if(m) for(i = 0; i != m.length; ++i) {
17901		y = m[i].match(attregex2);
17902/*:: if(!y || !y[2]) continue; */
17903		if((j=y[1].indexOf(":")) === -1) z[y[1]] = y[2].slice(1,y[2].length-1);
17904		else {
17905			if(y[1].slice(0,6) === "xmlns:") w = "xmlns"+y[1].slice(6);
17906			else w = y[1].slice(j+1);
17907			z[w] = y[2].slice(1,y[2].length-1);
17908		}
17909	}
17910	return z;
17911}
17912function xlml_parsexmltagobj(tag/*:string*/) {
17913	var words = tag.split(/\s+/);
17914	var z = {};
17915	if(words.length === 1) return z;
17916	var m = tag.match(attregexg2), y, j, w, i;
17917	if(m) for(i = 0; i != m.length; ++i) {
17918		y = m[i].match(attregex2);
17919/*:: if(!y || !y[2]) continue; */
17920		if((j=y[1].indexOf(":")) === -1) z[y[1]] = y[2].slice(1,y[2].length-1);
17921		else {
17922			if(y[1].slice(0,6) === "xmlns:") w = "xmlns"+y[1].slice(6);
17923			else w = y[1].slice(j+1);
17924			z[w] = y[2].slice(1,y[2].length-1);
17925		}
17926	}
17927	return z;
17928}
17929
17930// ----
17931
17932/* map from xlml named formats to SSF TODO: localize */
17933var XLMLFormatMap/*: {[string]:string}*/;
17934
17935function xlml_format(format, value)/*:string*/ {
17936	var fmt = XLMLFormatMap[format] || unescapexml(format);
17937	if(fmt === "General") return SSF_general(value);
17938	return SSF_format(fmt, value);
17939}
17940
17941function xlml_set_custprop(Custprops, key, cp, val/*:string*/) {
17942	var oval/*:any*/ = val;
17943	switch((cp[0].match(/dt:dt="([\w.]+)"/)||["",""])[1]) {
17944		case "boolean": oval = parsexmlbool(val); break;
17945		case "i2": case "int": oval = parseInt(val, 10); break;
17946		case "r4": case "float": oval = parseFloat(val); break;
17947		case "date": case "dateTime.tz": oval = parseDate(val); break;
17948		case "i8": case "string": case "fixed": case "uuid": case "bin.base64": break;
17949		default: throw new Error("bad custprop:" + cp[0]);
17950	}
17951	Custprops[unescapexml(key)] = oval;
17952}
17953
17954function safe_format_xlml(cell/*:Cell*/, nf, o) {
17955	if(cell.t === 'z') return;
17956	if(!o || o.cellText !== false) try {
17957		if(cell.t === 'e') { cell.w = cell.w || BErr[cell.v]; }
17958		else if(nf === "General") {
17959			if(cell.t === 'n') {
17960				if((cell.v|0) === cell.v) cell.w = cell.v.toString(10);
17961				else cell.w = SSF_general_num(cell.v);
17962			}
17963			else cell.w = SSF_general(cell.v);
17964		}
17965		else cell.w = xlml_format(nf||"General", cell.v);
17966	} catch(e) { if(o.WTF) throw e; }
17967	try {
17968		var z = XLMLFormatMap[nf]||nf||"General";
17969		if(o.cellNF) cell.z = z;
17970		if(o.cellDates && cell.t == 'n' && fmt_is_date(z)) {
17971			var _d = SSF_parse_date_code(cell.v); if(_d) { cell.t = 'd'; cell.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
17972		}
17973	} catch(e) { if(o.WTF) throw e; }
17974}
17975
17976function process_style_xlml(styles, stag, opts) {
17977	if(opts.cellStyles) {
17978		if(stag.Interior) {
17979			var I = stag.Interior;
17980			if(I.Pattern) I.patternType = XLMLPatternTypeMap[I.Pattern] || I.Pattern;
17981		}
17982	}
17983	styles[stag.ID] = stag;
17984}
17985
17986/* TODO: there must exist some form of OSP-blessed spec */
17987function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, arrayf, o) {
17988	var nf = "General", sid = cell.StyleID, S = {}; o = o || {};
17989	var interiors = [];
17990	var i = 0;
17991	if(sid === undefined && row) sid = row.StyleID;
17992	if(sid === undefined && csty) sid = csty.StyleID;
17993	while(styles[sid] !== undefined) {
17994		if(styles[sid].nf) nf = styles[sid].nf;
17995		if(styles[sid].Interior) interiors.push(styles[sid].Interior);
17996		if(!styles[sid].Parent) break;
17997		sid = styles[sid].Parent;
17998	}
17999	switch(data.Type) {
18000		case 'Boolean':
18001			cell.t = 'b';
18002			cell.v = parsexmlbool(xml);
18003			break;
18004		case 'String':
18005			cell.t = 's'; cell.r = xlml_fixstr(unescapexml(xml));
18006			cell.v = (xml.indexOf("<") > -1 ? unescapexml(ss||xml).replace(/<.*?>/g, "") : cell.r); // todo: BR etc
18007			break;
18008		case 'DateTime':
18009			if(xml.slice(-1) != "Z") xml += "Z";
18010			cell.v = (parseDate(xml) - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
18011			if(cell.v !== cell.v) cell.v = unescapexml(xml);
18012			else if(cell.v<60) cell.v = cell.v -1;
18013			if(!nf || nf == "General") nf = "yyyy-mm-dd";
18014			/* falls through */
18015		case 'Number':
18016			if(cell.v === undefined) cell.v=+xml;
18017			if(!cell.t) cell.t = 'n';
18018			break;
18019		case 'Error': cell.t = 'e'; cell.v = RBErr[xml]; if(o.cellText !== false) cell.w = xml; break;
18020		default:
18021			if(xml == "" && ss == "") { cell.t = 'z'; }
18022			else { cell.t = 's'; cell.v = xlml_fixstr(ss||xml); }
18023			break;
18024	}
18025	safe_format_xlml(cell, nf, o);
18026	if(o.cellFormula !== false) {
18027		if(cell.Formula) {
18028			var fstr = unescapexml(cell.Formula);
18029			/* strictly speaking, the leading = is required but some writers omit */
18030			if(fstr.charCodeAt(0) == 61 /* = */) fstr = fstr.slice(1);
18031			cell.f = rc_to_a1(fstr, base);
18032			delete cell.Formula;
18033			if(cell.ArrayRange == "RC") cell.F = rc_to_a1("RC:RC", base);
18034			else if(cell.ArrayRange) {
18035				cell.F = rc_to_a1(cell.ArrayRange, base);
18036				arrayf.push([safe_decode_range(cell.F), cell.F]);
18037			}
18038		} else {
18039			for(i = 0; i < arrayf.length; ++i)
18040				if(base.r >= arrayf[i][0].s.r && base.r <= arrayf[i][0].e.r)
18041					if(base.c >= arrayf[i][0].s.c && base.c <= arrayf[i][0].e.c)
18042						cell.F = arrayf[i][1];
18043		}
18044	}
18045	if(o.cellStyles) {
18046		interiors.forEach(function(x) {
18047			if(!S.patternType && x.patternType) S.patternType = x.patternType;
18048		});
18049		cell.s = S;
18050	}
18051	if(cell.StyleID !== undefined) cell.ixfe = cell.StyleID;
18052}
18053
18054function xlml_prefix_dname(dname) {
18055	return XLSLblBuiltIn.indexOf("_xlnm." + dname) > -1 ? "_xlnm." + dname : dname;
18056}
18057
18058function xlml_clean_comment(comment/*:any*/) {
18059	comment.t = comment.v || "";
18060	comment.t = comment.t.replace(/\r\n/g,"\n").replace(/\r/g,"\n");
18061	comment.v = comment.w = comment.ixfe = undefined;
18062}
18063
18064/* TODO: Everything */
18065function parse_xlml_xml(d, _opts)/*:Workbook*/ {
18066	var opts = _opts || {};
18067	make_ssf();
18068	var str = debom(xlml_normalize(d));
18069	if(opts.type == 'binary' || opts.type == 'array' || opts.type == 'base64') {
18070		if(typeof $cptable !== 'undefined') str = $cptable.utils.decode(65001, char_codes(str));
18071		else str = utf8read(str);
18072	}
18073	var opening = str.slice(0, 1024).toLowerCase(), ishtml = false;
18074	opening = opening.replace(/".*?"/g, "");
18075	if((opening.indexOf(">") & 1023) > Math.min((opening.indexOf(",") & 1023), (opening.indexOf(";")&1023))) { var _o = dup(opts); _o.type = "string"; return PRN.to_workbook(str, _o); }
18076	if(opening.indexOf("<?xml") == -1) ["html", "table", "head", "meta", "script", "style", "div"].forEach(function(tag) { if(opening.indexOf("<" + tag) >= 0) ishtml = true; });
18077	if(ishtml) return html_to_workbook(str, opts);
18078
18079	XLMLFormatMap = ({
18080		"General Number": "General",
18081		"General Date": table_fmt[22],
18082		"Long Date": "dddd, mmmm dd, yyyy",
18083		"Medium Date": table_fmt[15],
18084		"Short Date": table_fmt[14],
18085		"Long Time": table_fmt[19],
18086		"Medium Time": table_fmt[18],
18087		"Short Time": table_fmt[20],
18088		"Currency": '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)',
18089		"Fixed": table_fmt[2],
18090		"Standard": table_fmt[4],
18091		"Percent": table_fmt[10],
18092		"Scientific": table_fmt[11],
18093		"Yes/No": '"Yes";"Yes";"No";@',
18094		"True/False": '"True";"True";"False";@',
18095		"On/Off": '"Yes";"Yes";"No";@'
18096	}/*:any*/);
18097
18098
18099	var Rn;
18100	var state = [], tmp;
18101	if(DENSE != null && opts.dense == null) opts.dense = DENSE;
18102	var sheets = {}, sheetnames/*:Array<string>*/ = [], cursheet/*:Worksheet*/ = (opts.dense ? [] : {}), sheetname = "";
18103	var cell = ({}/*:any*/), row = {};// eslint-disable-line no-unused-vars
18104	var dtag = xlml_parsexmltag('<Data ss:Type="String">'), didx = 0;
18105	var c = 0, r = 0;
18106	var refguess/*:Range*/ = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
18107	var styles = {}, stag = {};
18108	var ss = "", fidx = 0;
18109	var merges/*:Array<Range>*/ = [];
18110	var Props = {}, Custprops = {}, pidx = 0, cp = [];
18111	var comments/*:Array<Comment>*/ = [], comment/*:Comment*/ = ({}/*:any*/);
18112	var cstys = [], csty, seencol = false;
18113	var arrayf/*:Array<[Range, string]>*/ = [];
18114	var rowinfo/*:Array<RowInfo>*/ = [], rowobj = {}, cc = 0, rr = 0;
18115	var Workbook/*:WBWBProps*/ = ({ Sheets:[], WBProps:{date1904:false} }/*:any*/), wsprops = {};
18116	xlmlregex.lastIndex = 0;
18117	str = str.replace(/<!--([\s\S]*?)-->/mg,"");
18118	var raw_Rn3 = "";
18119	while((Rn = xlmlregex.exec(str))) switch((Rn[3] = (raw_Rn3 = Rn[3]).toLowerCase())) {
18120		case 'data' /*case 'Data'*/:
18121			if(raw_Rn3 == "data") {
18122				if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));}
18123				else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], true]);
18124				break;
18125			}
18126			if(state[state.length-1][1]) break;
18127			if(Rn[1]==='/') parse_xlml_data(str.slice(didx, Rn.index), ss, dtag, state[state.length-1][0]==/*"Comment"*/"comment"?comment:cell, {c:c,r:r}, styles, cstys[c], row, arrayf, opts);
18128			else { ss = ""; dtag = xlml_parsexmltag(Rn[0]); didx = Rn.index + Rn[0].length; }
18129			break;
18130		case 'cell' /*case 'Cell'*/:
18131			if(Rn[1]==='/'){
18132				if(comments.length > 0) cell.c = comments;
18133				if((!opts.sheetRows || opts.sheetRows > r) && cell.v !== void 0) {
18134					if(opts.dense) {
18135						if(!cursheet[r]) cursheet[r] = [];
18136						cursheet[r][c] = cell;
18137					} else cursheet[encode_col(c) + encode_row(r)] = cell;
18138				}
18139				if(cell.HRef) {
18140					cell.l = ({Target:unescapexml(cell.HRef)}/*:any*/);
18141					if(cell.HRefScreenTip) cell.l.Tooltip = cell.HRefScreenTip;
18142					delete cell.HRef; delete cell.HRefScreenTip;
18143				}
18144				if(cell.MergeAcross || cell.MergeDown) {
18145					cc = c + (parseInt(cell.MergeAcross,10)|0);
18146					rr = r + (parseInt(cell.MergeDown,10)|0);
18147					if(cc > c || rr > r) merges.push({s:{c:c,r:r},e:{c:cc,r:rr}});
18148				}
18149				if(!opts.sheetStubs) { if(cell.MergeAcross) c = cc + 1; else ++c; }
18150				else if(cell.MergeAcross || cell.MergeDown) {
18151					/*:: if(!cc) cc = 0; if(!rr) rr = 0; */
18152					for(var cma = c; cma <= cc; ++cma) {
18153						for(var cmd = r; cmd <= rr; ++cmd) {
18154							if(cma > c || cmd > r) {
18155								if(opts.dense) {
18156									if(!cursheet[cmd]) cursheet[cmd] = [];
18157									cursheet[cmd][cma] = {t:'z'};
18158								} else cursheet[encode_col(cma) + encode_row(cmd)] = {t:'z'};
18159							}
18160						}
18161					}
18162					c = cc + 1;
18163				}
18164				else ++c;
18165			} else {
18166				cell = xlml_parsexmltagobj(Rn[0]);
18167				if(cell.Index) c = +cell.Index - 1;
18168				if(c < refguess.s.c) refguess.s.c = c;
18169				if(c > refguess.e.c) refguess.e.c = c;
18170				if(Rn[0].slice(-2) === "/>") ++c;
18171				comments = [];
18172			}
18173			break;
18174		case 'row' /*case 'Row'*/:
18175			if(Rn[1]==='/' || Rn[0].slice(-2) === "/>") {
18176				if(r < refguess.s.r) refguess.s.r = r;
18177				if(r > refguess.e.r) refguess.e.r = r;
18178				if(Rn[0].slice(-2) === "/>") {
18179					row = xlml_parsexmltag(Rn[0]);
18180					if(row.Index) r = +row.Index - 1;
18181				}
18182				c = 0; ++r;
18183			} else {
18184				row = xlml_parsexmltag(Rn[0]);
18185				if(row.Index) r = +row.Index - 1;
18186				rowobj = {};
18187				if(row.AutoFitHeight == "0" || row.Height) {
18188					rowobj.hpx = parseInt(row.Height, 10); rowobj.hpt = px2pt(rowobj.hpx);
18189					rowinfo[r] = rowobj;
18190				}
18191				if(row.Hidden == "1") { rowobj.hidden = true; rowinfo[r] = rowobj; }
18192			}
18193			break;
18194		case 'worksheet' /*case 'Worksheet'*/: /* TODO: read range from FullRows/FullColumns */
18195			if(Rn[1]==='/'){
18196				if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));
18197				sheetnames.push(sheetname);
18198				if(refguess.s.r <= refguess.e.r && refguess.s.c <= refguess.e.c) {
18199					cursheet["!ref"] = encode_range(refguess);
18200					if(opts.sheetRows && opts.sheetRows <= refguess.e.r) {
18201						cursheet["!fullref"] = cursheet["!ref"];
18202						refguess.e.r = opts.sheetRows - 1;
18203						cursheet["!ref"] = encode_range(refguess);
18204					}
18205				}
18206				if(merges.length) cursheet["!merges"] = merges;
18207				if(cstys.length > 0) cursheet["!cols"] = cstys;
18208				if(rowinfo.length > 0) cursheet["!rows"] = rowinfo;
18209				sheets[sheetname] = cursheet;
18210			} else {
18211				refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} };
18212				r = c = 0;
18213				state.push([Rn[3], false]);
18214				tmp = xlml_parsexmltag(Rn[0]);
18215				sheetname = unescapexml(tmp.Name);
18216				cursheet = (opts.dense ? [] : {});
18217				merges = [];
18218				arrayf = [];
18219				rowinfo = [];
18220				wsprops = {name:sheetname, Hidden:0};
18221				Workbook.Sheets.push(wsprops);
18222			}
18223			break;
18224		case 'table' /*case 'Table'*/:
18225			if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));}
18226			else if(Rn[0].slice(-2) == "/>") break;
18227			else {
18228				state.push([Rn[3], false]);
18229				cstys = []; seencol = false;
18230			}
18231			break;
18232
18233		case 'style' /*case 'Style'*/:
18234			if(Rn[1]==='/') process_style_xlml(styles, stag, opts);
18235			else stag = xlml_parsexmltag(Rn[0]);
18236			break;
18237
18238		case 'numberformat' /*case 'NumberFormat'*/:
18239			stag.nf = unescapexml(xlml_parsexmltag(Rn[0]).Format || "General");
18240			if(XLMLFormatMap[stag.nf]) stag.nf = XLMLFormatMap[stag.nf];
18241			for(var ssfidx = 0; ssfidx != 0x188; ++ssfidx) if(table_fmt[ssfidx] == stag.nf) break;
18242			if(ssfidx == 0x188) for(ssfidx = 0x39; ssfidx != 0x188; ++ssfidx) if(table_fmt[ssfidx] == null) { SSF__load(stag.nf, ssfidx); break; }
18243			break;
18244
18245		case 'column' /*case 'Column'*/:
18246			if(state[state.length-1][0] !== /*'Table'*/'table') break;
18247			if(Rn[1]==='/') break;
18248			csty = xlml_parsexmltag(Rn[0]);
18249			if(csty.Hidden) { csty.hidden = true; delete csty.Hidden; }
18250			if(csty.Width) csty.wpx = parseInt(csty.Width, 10);
18251			if(!seencol && csty.wpx > 10) {
18252				seencol = true; MDW = DEF_MDW; //find_mdw_wpx(csty.wpx);
18253				for(var _col = 0; _col < cstys.length; ++_col) if(cstys[_col]) process_col(cstys[_col]);
18254			}
18255			if(seencol) process_col(csty);
18256			cstys[(csty.Index-1||cstys.length)] = csty;
18257			for(var i = 0; i < +csty.Span; ++i) cstys[cstys.length] = dup(csty);
18258			break;
18259
18260		case 'namedrange' /*case 'NamedRange'*/:
18261			if(Rn[1]==='/') break;
18262			if(!Workbook.Names) Workbook.Names = [];
18263			var _NamedRange = parsexmltag(Rn[0]);
18264			var _DefinedName/*:DefinedName*/ = ({
18265				Name: xlml_prefix_dname(_NamedRange.Name),
18266				Ref: rc_to_a1(_NamedRange.RefersTo.slice(1), {r:0, c:0})
18267			}/*:any*/);
18268			if(Workbook.Sheets.length>0) _DefinedName.Sheet=Workbook.Sheets.length-1;
18269			/*:: if(Workbook.Names) */Workbook.Names.push(_DefinedName);
18270			break;
18271
18272		case 'namedcell' /*case 'NamedCell'*/: break;
18273		case 'b' /*case 'B'*/: break;
18274		case 'i' /*case 'I'*/: break;
18275		case 'u' /*case 'U'*/: break;
18276		case 's' /*case 'S'*/: break;
18277		case 'em' /*case 'EM'*/: break;
18278		case 'h2' /*case 'H2'*/: break;
18279		case 'h3' /*case 'H3'*/: break;
18280		case 'sub' /*case 'Sub'*/: break;
18281		case 'sup' /*case 'Sup'*/: break;
18282		case 'span' /*case 'Span'*/: break;
18283		case 'alignment' /*case 'Alignment'*/:
18284			break;
18285		case 'borders' /*case 'Borders'*/: break;
18286		case 'border' /*case 'Border'*/: break;
18287		case 'font' /*case 'Font'*/:
18288			if(Rn[0].slice(-2) === "/>") break;
18289			else if(Rn[1]==="/") ss += str.slice(fidx, Rn.index);
18290			else fidx = Rn.index + Rn[0].length;
18291			break;
18292		case 'interior' /*case 'Interior'*/:
18293			if(!opts.cellStyles) break;
18294			stag.Interior = xlml_parsexmltag(Rn[0]);
18295			break;
18296		case 'protection' /*case 'Protection'*/: break;
18297
18298		case 'author' /*case 'Author'*/:
18299		case 'title' /*case 'Title'*/:
18300		case 'description' /*case 'Description'*/:
18301		case 'created' /*case 'Created'*/:
18302		case 'keywords' /*case 'Keywords'*/:
18303		case 'subject' /*case 'Subject'*/:
18304		case 'category' /*case 'Category'*/:
18305		case 'company' /*case 'Company'*/:
18306		case 'lastauthor' /*case 'LastAuthor'*/:
18307		case 'lastsaved' /*case 'LastSaved'*/:
18308		case 'lastprinted' /*case 'LastPrinted'*/:
18309		case 'version' /*case 'Version'*/:
18310		case 'revision' /*case 'Revision'*/:
18311		case 'totaltime' /*case 'TotalTime'*/:
18312		case 'hyperlinkbase' /*case 'HyperlinkBase'*/:
18313		case 'manager' /*case 'Manager'*/:
18314		case 'contentstatus' /*case 'ContentStatus'*/:
18315		case 'identifier' /*case 'Identifier'*/:
18316		case 'language' /*case 'Language'*/:
18317		case 'appname' /*case 'AppName'*/:
18318			if(Rn[0].slice(-2) === "/>") break;
18319			else if(Rn[1]==="/") xlml_set_prop(Props, raw_Rn3, str.slice(pidx, Rn.index));
18320			else pidx = Rn.index + Rn[0].length;
18321			break;
18322		case 'paragraphs' /*case 'Paragraphs'*/: break;
18323
18324		case 'styles' /*case 'Styles'*/:
18325		case 'workbook' /*case 'Workbook'*/:
18326			if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));}
18327			else state.push([Rn[3], false]);
18328			break;
18329
18330		case 'comment' /*case 'Comment'*/:
18331			if(Rn[1]==='/'){
18332				if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));
18333				xlml_clean_comment(comment);
18334				comments.push(comment);
18335			} else {
18336				state.push([Rn[3], false]);
18337				tmp = xlml_parsexmltag(Rn[0]);
18338				comment = ({a:tmp.Author}/*:any*/);
18339			}
18340			break;
18341
18342		case 'autofilter' /*case 'AutoFilter'*/:
18343			if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));}
18344			else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
18345				var AutoFilter = xlml_parsexmltag(Rn[0]);
18346				cursheet['!autofilter'] = { ref:rc_to_a1(AutoFilter.Range).replace(/\$/g,"") };
18347				state.push([Rn[3], true]);
18348			}
18349			break;
18350
18351		case 'name' /*case 'Name'*/: break;
18352
18353		case 'datavalidation' /*case 'DataValidation'*/:
18354			if(Rn[1]==='/'){
18355				if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));
18356			} else {
18357				if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], true]);
18358			}
18359			break;
18360
18361		case 'pixelsperinch' /*case 'PixelsPerInch'*/:
18362			break;
18363		case 'componentoptions' /*case 'ComponentOptions'*/:
18364		case 'documentproperties' /*case 'DocumentProperties'*/:
18365		case 'customdocumentproperties' /*case 'CustomDocumentProperties'*/:
18366		case 'officedocumentsettings' /*case 'OfficeDocumentSettings'*/:
18367		case 'pivottable' /*case 'PivotTable'*/:
18368		case 'pivotcache' /*case 'PivotCache'*/:
18369		case 'names' /*case 'Names'*/:
18370		case 'mapinfo' /*case 'MapInfo'*/:
18371		case 'pagebreaks' /*case 'PageBreaks'*/:
18372		case 'querytable' /*case 'QueryTable'*/:
18373		case 'sorting' /*case 'Sorting'*/:
18374		case 'schema' /*case 'Schema'*/: //case 'data' /*case 'data'*/:
18375		case 'conditionalformatting' /*case 'ConditionalFormatting'*/:
18376		case 'smarttagtype' /*case 'SmartTagType'*/:
18377		case 'smarttags' /*case 'SmartTags'*/:
18378		case 'excelworkbook' /*case 'ExcelWorkbook'*/:
18379		case 'workbookoptions' /*case 'WorkbookOptions'*/:
18380		case 'worksheetoptions' /*case 'WorksheetOptions'*/:
18381			if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));}
18382			else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], true]);
18383			break;
18384
18385		case 'null' /*case 'Null'*/: break;
18386
18387		default:
18388			/* FODS file root is <office:document> */
18389			if(state.length == 0 && Rn[3] == "document") return parse_fods(str, opts);
18390			/* UOS file root is <uof:UOF> */
18391			if(state.length == 0 && Rn[3] == "uof"/*"UOF"*/) return parse_fods(str, opts);
18392
18393			var seen = true;
18394			switch(state[state.length-1][0]) {
18395				/* OfficeDocumentSettings */
18396				case 'officedocumentsettings' /*case 'OfficeDocumentSettings'*/: switch(Rn[3]) {
18397					case 'allowpng' /*case 'AllowPNG'*/: break;
18398					case 'removepersonalinformation' /*case 'RemovePersonalInformation'*/: break;
18399					case 'downloadcomponents' /*case 'DownloadComponents'*/: break;
18400					case 'locationofcomponents' /*case 'LocationOfComponents'*/: break;
18401					case 'colors' /*case 'Colors'*/: break;
18402					case 'color' /*case 'Color'*/: break;
18403					case 'index' /*case 'Index'*/: break;
18404					case 'rgb' /*case 'RGB'*/: break;
18405					case 'targetscreensize' /*case 'TargetScreenSize'*/: break;
18406					case 'readonlyrecommended' /*case 'ReadOnlyRecommended'*/: break;
18407					default: seen = false;
18408				} break;
18409
18410				/* ComponentOptions */
18411				case 'componentoptions' /*case 'ComponentOptions'*/: switch(Rn[3]) {
18412					case 'toolbar' /*case 'Toolbar'*/: break;
18413					case 'hideofficelogo' /*case 'HideOfficeLogo'*/: break;
18414					case 'spreadsheetautofit' /*case 'SpreadsheetAutoFit'*/: break;
18415					case 'label' /*case 'Label'*/: break;
18416					case 'caption' /*case 'Caption'*/: break;
18417					case 'maxheight' /*case 'MaxHeight'*/: break;
18418					case 'maxwidth' /*case 'MaxWidth'*/: break;
18419					case 'nextsheetnumber' /*case 'NextSheetNumber'*/: break;
18420					default: seen = false;
18421				} break;
18422
18423				/* ExcelWorkbook */
18424				case 'excelworkbook' /*case 'ExcelWorkbook'*/: switch(Rn[3]) {
18425					case 'date1904' /*case 'Date1904'*/:
18426						/*:: if(!Workbook.WBProps) Workbook.WBProps = {}; */
18427						Workbook.WBProps.date1904 = true;
18428						break;
18429					case 'windowheight' /*case 'WindowHeight'*/: break;
18430					case 'windowwidth' /*case 'WindowWidth'*/: break;
18431					case 'windowtopx' /*case 'WindowTopX'*/: break;
18432					case 'windowtopy' /*case 'WindowTopY'*/: break;
18433					case 'tabratio' /*case 'TabRatio'*/: break;
18434					case 'protectstructure' /*case 'ProtectStructure'*/: break;
18435					case 'protectwindow' /*case 'ProtectWindow'*/: break;
18436					case 'protectwindows' /*case 'ProtectWindows'*/: break;
18437					case 'activesheet' /*case 'ActiveSheet'*/: break;
18438					case 'displayinknotes' /*case 'DisplayInkNotes'*/: break;
18439					case 'firstvisiblesheet' /*case 'FirstVisibleSheet'*/: break;
18440					case 'supbook' /*case 'SupBook'*/: break;
18441					case 'sheetname' /*case 'SheetName'*/: break;
18442					case 'sheetindex' /*case 'SheetIndex'*/: break;
18443					case 'sheetindexfirst' /*case 'SheetIndexFirst'*/: break;
18444					case 'sheetindexlast' /*case 'SheetIndexLast'*/: break;
18445					case 'dll' /*case 'Dll'*/: break;
18446					case 'acceptlabelsinformulas' /*case 'AcceptLabelsInFormulas'*/: break;
18447					case 'donotsavelinkvalues' /*case 'DoNotSaveLinkValues'*/: break;
18448					case 'iteration' /*case 'Iteration'*/: break;
18449					case 'maxiterations' /*case 'MaxIterations'*/: break;
18450					case 'maxchange' /*case 'MaxChange'*/: break;
18451					case 'path' /*case 'Path'*/: break;
18452					case 'xct' /*case 'Xct'*/: break;
18453					case 'count' /*case 'Count'*/: break;
18454					case 'selectedsheets' /*case 'SelectedSheets'*/: break;
18455					case 'calculation' /*case 'Calculation'*/: break;
18456					case 'uncalced' /*case 'Uncalced'*/: break;
18457					case 'startupprompt' /*case 'StartupPrompt'*/: break;
18458					case 'crn' /*case 'Crn'*/: break;
18459					case 'externname' /*case 'ExternName'*/: break;
18460					case 'formula' /*case 'Formula'*/: break;
18461					case 'colfirst' /*case 'ColFirst'*/: break;
18462					case 'collast' /*case 'ColLast'*/: break;
18463					case 'wantadvise' /*case 'WantAdvise'*/: break;
18464					case 'boolean' /*case 'Boolean'*/: break;
18465					case 'error' /*case 'Error'*/: break;
18466					case 'text' /*case 'Text'*/: break;
18467					case 'ole' /*case 'OLE'*/: break;
18468					case 'noautorecover' /*case 'NoAutoRecover'*/: break;
18469					case 'publishobjects' /*case 'PublishObjects'*/: break;
18470					case 'donotcalculatebeforesave' /*case 'DoNotCalculateBeforeSave'*/: break;
18471					case 'number' /*case 'Number'*/: break;
18472					case 'refmoder1c1' /*case 'RefModeR1C1'*/: break;
18473					case 'embedsavesmarttags' /*case 'EmbedSaveSmartTags'*/: break;
18474					default: seen = false;
18475				} break;
18476
18477				/* WorkbookOptions */
18478				case 'workbookoptions' /*case 'WorkbookOptions'*/: switch(Rn[3]) {
18479					case 'owcversion' /*case 'OWCVersion'*/: break;
18480					case 'height' /*case 'Height'*/: break;
18481					case 'width' /*case 'Width'*/: break;
18482					default: seen = false;
18483				} break;
18484
18485				/* WorksheetOptions */
18486				case 'worksheetoptions' /*case 'WorksheetOptions'*/: switch(Rn[3]) {
18487					case 'visible' /*case 'Visible'*/:
18488						if(Rn[0].slice(-2) === "/>"){/* empty */}
18489						else if(Rn[1]==="/") switch(str.slice(pidx, Rn.index)) {
18490							case "SheetHidden": wsprops.Hidden = 1; break;
18491							case "SheetVeryHidden": wsprops.Hidden = 2; break;
18492						}
18493						else pidx = Rn.index + Rn[0].length;
18494						break;
18495					case 'header' /*case 'Header'*/:
18496						if(!cursheet['!margins']) default_margins(cursheet['!margins']={}, 'xlml');
18497						if(!isNaN(+parsexmltag(Rn[0]).Margin)) cursheet['!margins'].header = +parsexmltag(Rn[0]).Margin;
18498						break;
18499					case 'footer' /*case 'Footer'*/:
18500						if(!cursheet['!margins']) default_margins(cursheet['!margins']={}, 'xlml');
18501						if(!isNaN(+parsexmltag(Rn[0]).Margin)) cursheet['!margins'].footer = +parsexmltag(Rn[0]).Margin;
18502						break;
18503					case 'pagemargins' /*case 'PageMargins'*/:
18504						var pagemargins = parsexmltag(Rn[0]);
18505						if(!cursheet['!margins']) default_margins(cursheet['!margins']={},'xlml');
18506						if(!isNaN(+pagemargins.Top)) cursheet['!margins'].top = +pagemargins.Top;
18507						if(!isNaN(+pagemargins.Left)) cursheet['!margins'].left = +pagemargins.Left;
18508						if(!isNaN(+pagemargins.Right)) cursheet['!margins'].right = +pagemargins.Right;
18509						if(!isNaN(+pagemargins.Bottom)) cursheet['!margins'].bottom = +pagemargins.Bottom;
18510						break;
18511					case 'displayrighttoleft' /*case 'DisplayRightToLeft'*/:
18512						if(!Workbook.Views) Workbook.Views = [];
18513						if(!Workbook.Views[0]) Workbook.Views[0] = {};
18514						Workbook.Views[0].RTL = true;
18515						break;
18516
18517					case 'freezepanes' /*case 'FreezePanes'*/: break;
18518					case 'frozennosplit' /*case 'FrozenNoSplit'*/: break;
18519
18520					case 'splithorizontal' /*case 'SplitHorizontal'*/:
18521					case 'splitvertical' /*case 'SplitVertical'*/:
18522						break;
18523
18524					case 'donotdisplaygridlines' /*case 'DoNotDisplayGridlines'*/:
18525						break;
18526
18527					case 'activerow' /*case 'ActiveRow'*/: break;
18528					case 'activecol' /*case 'ActiveCol'*/: break;
18529					case 'toprowbottompane' /*case 'TopRowBottomPane'*/: break;
18530					case 'leftcolumnrightpane' /*case 'LeftColumnRightPane'*/: break;
18531
18532					case 'unsynced' /*case 'Unsynced'*/: break;
18533					case 'print' /*case 'Print'*/: break;
18534					case 'printerrors' /*case 'PrintErrors'*/: break;
18535					case 'panes' /*case 'Panes'*/: break;
18536					case 'scale' /*case 'Scale'*/: break;
18537					case 'pane' /*case 'Pane'*/: break;
18538					case 'number' /*case 'Number'*/: break;
18539					case 'layout' /*case 'Layout'*/: break;
18540					case 'pagesetup' /*case 'PageSetup'*/: break;
18541					case 'selected' /*case 'Selected'*/: break;
18542					case 'protectobjects' /*case 'ProtectObjects'*/: break;
18543					case 'enableselection' /*case 'EnableSelection'*/: break;
18544					case 'protectscenarios' /*case 'ProtectScenarios'*/: break;
18545					case 'validprinterinfo' /*case 'ValidPrinterInfo'*/: break;
18546					case 'horizontalresolution' /*case 'HorizontalResolution'*/: break;
18547					case 'verticalresolution' /*case 'VerticalResolution'*/: break;
18548					case 'numberofcopies' /*case 'NumberofCopies'*/: break;
18549					case 'activepane' /*case 'ActivePane'*/: break;
18550					case 'toprowvisible' /*case 'TopRowVisible'*/: break;
18551					case 'leftcolumnvisible' /*case 'LeftColumnVisible'*/: break;
18552					case 'fittopage' /*case 'FitToPage'*/: break;
18553					case 'rangeselection' /*case 'RangeSelection'*/: break;
18554					case 'papersizeindex' /*case 'PaperSizeIndex'*/: break;
18555					case 'pagelayoutzoom' /*case 'PageLayoutZoom'*/: break;
18556					case 'pagebreakzoom' /*case 'PageBreakZoom'*/: break;
18557					case 'filteron' /*case 'FilterOn'*/: break;
18558					case 'fitwidth' /*case 'FitWidth'*/: break;
18559					case 'fitheight' /*case 'FitHeight'*/: break;
18560					case 'commentslayout' /*case 'CommentsLayout'*/: break;
18561					case 'zoom' /*case 'Zoom'*/: break;
18562					case 'lefttoright' /*case 'LeftToRight'*/: break;
18563					case 'gridlines' /*case 'Gridlines'*/: break;
18564					case 'allowsort' /*case 'AllowSort'*/: break;
18565					case 'allowfilter' /*case 'AllowFilter'*/: break;
18566					case 'allowinsertrows' /*case 'AllowInsertRows'*/: break;
18567					case 'allowdeleterows' /*case 'AllowDeleteRows'*/: break;
18568					case 'allowinsertcols' /*case 'AllowInsertCols'*/: break;
18569					case 'allowdeletecols' /*case 'AllowDeleteCols'*/: break;
18570					case 'allowinserthyperlinks' /*case 'AllowInsertHyperlinks'*/: break;
18571					case 'allowformatcells' /*case 'AllowFormatCells'*/: break;
18572					case 'allowsizecols' /*case 'AllowSizeCols'*/: break;
18573					case 'allowsizerows' /*case 'AllowSizeRows'*/: break;
18574					case 'nosummaryrowsbelowdetail' /*case 'NoSummaryRowsBelowDetail'*/:
18575						if(!cursheet["!outline"]) cursheet["!outline"] = {};
18576						cursheet["!outline"].above = true;
18577						break;
18578					case 'tabcolorindex' /*case 'TabColorIndex'*/: break;
18579					case 'donotdisplayheadings' /*case 'DoNotDisplayHeadings'*/: break;
18580					case 'showpagelayoutzoom' /*case 'ShowPageLayoutZoom'*/: break;
18581					case 'nosummarycolumnsrightdetail' /*case 'NoSummaryColumnsRightDetail'*/:
18582						if(!cursheet["!outline"]) cursheet["!outline"] = {};
18583						cursheet["!outline"].left = true;
18584						break;
18585					case 'blackandwhite' /*case 'BlackAndWhite'*/: break;
18586					case 'donotdisplayzeros' /*case 'DoNotDisplayZeros'*/: break;
18587					case 'displaypagebreak' /*case 'DisplayPageBreak'*/: break;
18588					case 'rowcolheadings' /*case 'RowColHeadings'*/: break;
18589					case 'donotdisplayoutline' /*case 'DoNotDisplayOutline'*/: break;
18590					case 'noorientation' /*case 'NoOrientation'*/: break;
18591					case 'allowusepivottables' /*case 'AllowUsePivotTables'*/: break;
18592					case 'zeroheight' /*case 'ZeroHeight'*/: break;
18593					case 'viewablerange' /*case 'ViewableRange'*/: break;
18594					case 'selection' /*case 'Selection'*/: break;
18595					case 'protectcontents' /*case 'ProtectContents'*/: break;
18596					default: seen = false;
18597				} break;
18598
18599				/* PivotTable */
18600				case 'pivottable' /*case 'PivotTable'*/: case 'pivotcache' /*case 'PivotCache'*/: switch(Rn[3]) {
18601					case 'immediateitemsondrop' /*case 'ImmediateItemsOnDrop'*/: break;
18602					case 'showpagemultipleitemlabel' /*case 'ShowPageMultipleItemLabel'*/: break;
18603					case 'compactrowindent' /*case 'CompactRowIndent'*/: break;
18604					case 'location' /*case 'Location'*/: break;
18605					case 'pivotfield' /*case 'PivotField'*/: break;
18606					case 'orientation' /*case 'Orientation'*/: break;
18607					case 'layoutform' /*case 'LayoutForm'*/: break;
18608					case 'layoutsubtotallocation' /*case 'LayoutSubtotalLocation'*/: break;
18609					case 'layoutcompactrow' /*case 'LayoutCompactRow'*/: break;
18610					case 'position' /*case 'Position'*/: break;
18611					case 'pivotitem' /*case 'PivotItem'*/: break;
18612					case 'datatype' /*case 'DataType'*/: break;
18613					case 'datafield' /*case 'DataField'*/: break;
18614					case 'sourcename' /*case 'SourceName'*/: break;
18615					case 'parentfield' /*case 'ParentField'*/: break;
18616					case 'ptlineitems' /*case 'PTLineItems'*/: break;
18617					case 'ptlineitem' /*case 'PTLineItem'*/: break;
18618					case 'countofsameitems' /*case 'CountOfSameItems'*/: break;
18619					case 'item' /*case 'Item'*/: break;
18620					case 'itemtype' /*case 'ItemType'*/: break;
18621					case 'ptsource' /*case 'PTSource'*/: break;
18622					case 'cacheindex' /*case 'CacheIndex'*/: break;
18623					case 'consolidationreference' /*case 'ConsolidationReference'*/: break;
18624					case 'filename' /*case 'FileName'*/: break;
18625					case 'reference' /*case 'Reference'*/: break;
18626					case 'nocolumngrand' /*case 'NoColumnGrand'*/: break;
18627					case 'norowgrand' /*case 'NoRowGrand'*/: break;
18628					case 'blanklineafteritems' /*case 'BlankLineAfterItems'*/: break;
18629					case 'hidden' /*case 'Hidden'*/: break;
18630					case 'subtotal' /*case 'Subtotal'*/: break;
18631					case 'basefield' /*case 'BaseField'*/: break;
18632					case 'mapchilditems' /*case 'MapChildItems'*/: break;
18633					case 'function' /*case 'Function'*/: break;
18634					case 'refreshonfileopen' /*case 'RefreshOnFileOpen'*/: break;
18635					case 'printsettitles' /*case 'PrintSetTitles'*/: break;
18636					case 'mergelabels' /*case 'MergeLabels'*/: break;
18637					case 'defaultversion' /*case 'DefaultVersion'*/: break;
18638					case 'refreshname' /*case 'RefreshName'*/: break;
18639					case 'refreshdate' /*case 'RefreshDate'*/: break;
18640					case 'refreshdatecopy' /*case 'RefreshDateCopy'*/: break;
18641					case 'versionlastrefresh' /*case 'VersionLastRefresh'*/: break;
18642					case 'versionlastupdate' /*case 'VersionLastUpdate'*/: break;
18643					case 'versionupdateablemin' /*case 'VersionUpdateableMin'*/: break;
18644					case 'versionrefreshablemin' /*case 'VersionRefreshableMin'*/: break;
18645					case 'calculation' /*case 'Calculation'*/: break;
18646					default: seen = false;
18647				} break;
18648
18649				/* PageBreaks */
18650				case 'pagebreaks' /*case 'PageBreaks'*/: switch(Rn[3]) {
18651					case 'colbreaks' /*case 'ColBreaks'*/: break;
18652					case 'colbreak' /*case 'ColBreak'*/: break;
18653					case 'rowbreaks' /*case 'RowBreaks'*/: break;
18654					case 'rowbreak' /*case 'RowBreak'*/: break;
18655					case 'colstart' /*case 'ColStart'*/: break;
18656					case 'colend' /*case 'ColEnd'*/: break;
18657					case 'rowend' /*case 'RowEnd'*/: break;
18658					default: seen = false;
18659				} break;
18660
18661				/* AutoFilter */
18662				case 'autofilter' /*case 'AutoFilter'*/: switch(Rn[3]) {
18663					case 'autofiltercolumn' /*case 'AutoFilterColumn'*/: break;
18664					case 'autofiltercondition' /*case 'AutoFilterCondition'*/: break;
18665					case 'autofilterand' /*case 'AutoFilterAnd'*/: break;
18666					case 'autofilteror' /*case 'AutoFilterOr'*/: break;
18667					default: seen = false;
18668				} break;
18669
18670				/* QueryTable */
18671				case 'querytable' /*case 'QueryTable'*/: switch(Rn[3]) {
18672					case 'id' /*case 'Id'*/: break;
18673					case 'autoformatfont' /*case 'AutoFormatFont'*/: break;
18674					case 'autoformatpattern' /*case 'AutoFormatPattern'*/: break;
18675					case 'querysource' /*case 'QuerySource'*/: break;
18676					case 'querytype' /*case 'QueryType'*/: break;
18677					case 'enableredirections' /*case 'EnableRedirections'*/: break;
18678					case 'refreshedinxl9' /*case 'RefreshedInXl9'*/: break;
18679					case 'urlstring' /*case 'URLString'*/: break;
18680					case 'htmltables' /*case 'HTMLTables'*/: break;
18681					case 'connection' /*case 'Connection'*/: break;
18682					case 'commandtext' /*case 'CommandText'*/: break;
18683					case 'refreshinfo' /*case 'RefreshInfo'*/: break;
18684					case 'notitles' /*case 'NoTitles'*/: break;
18685					case 'nextid' /*case 'NextId'*/: break;
18686					case 'columninfo' /*case 'ColumnInfo'*/: break;
18687					case 'overwritecells' /*case 'OverwriteCells'*/: break;
18688					case 'donotpromptforfile' /*case 'DoNotPromptForFile'*/: break;
18689					case 'textwizardsettings' /*case 'TextWizardSettings'*/: break;
18690					case 'source' /*case 'Source'*/: break;
18691					case 'number' /*case 'Number'*/: break;
18692					case 'decimal' /*case 'Decimal'*/: break;
18693					case 'thousandseparator' /*case 'ThousandSeparator'*/: break;
18694					case 'trailingminusnumbers' /*case 'TrailingMinusNumbers'*/: break;
18695					case 'formatsettings' /*case 'FormatSettings'*/: break;
18696					case 'fieldtype' /*case 'FieldType'*/: break;
18697					case 'delimiters' /*case 'Delimiters'*/: break;
18698					case 'tab' /*case 'Tab'*/: break;
18699					case 'comma' /*case 'Comma'*/: break;
18700					case 'autoformatname' /*case 'AutoFormatName'*/: break;
18701					case 'versionlastedit' /*case 'VersionLastEdit'*/: break;
18702					case 'versionlastrefresh' /*case 'VersionLastRefresh'*/: break;
18703					default: seen = false;
18704				} break;
18705
18706				case 'datavalidation' /*case 'DataValidation'*/:
18707				switch(Rn[3]) {
18708					case 'range' /*case 'Range'*/: break;
18709
18710					case 'type' /*case 'Type'*/: break;
18711					case 'min' /*case 'Min'*/: break;
18712					case 'max' /*case 'Max'*/: break;
18713					case 'sort' /*case 'Sort'*/: break;
18714					case 'descending' /*case 'Descending'*/: break;
18715					case 'order' /*case 'Order'*/: break;
18716					case 'casesensitive' /*case 'CaseSensitive'*/: break;
18717					case 'value' /*case 'Value'*/: break;
18718					case 'errorstyle' /*case 'ErrorStyle'*/: break;
18719					case 'errormessage' /*case 'ErrorMessage'*/: break;
18720					case 'errortitle' /*case 'ErrorTitle'*/: break;
18721					case 'inputmessage' /*case 'InputMessage'*/: break;
18722					case 'inputtitle' /*case 'InputTitle'*/: break;
18723					case 'combohide' /*case 'ComboHide'*/: break;
18724					case 'inputhide' /*case 'InputHide'*/: break;
18725					case 'condition' /*case 'Condition'*/: break;
18726					case 'qualifier' /*case 'Qualifier'*/: break;
18727					case 'useblank' /*case 'UseBlank'*/: break;
18728					case 'value1' /*case 'Value1'*/: break;
18729					case 'value2' /*case 'Value2'*/: break;
18730					case 'format' /*case 'Format'*/: break;
18731
18732					case 'cellrangelist' /*case 'CellRangeList'*/: break;
18733					default: seen = false;
18734				} break;
18735
18736				case 'sorting' /*case 'Sorting'*/:
18737				case 'conditionalformatting' /*case 'ConditionalFormatting'*/:
18738				switch(Rn[3]) {
18739					case 'range' /*case 'Range'*/: break;
18740					case 'type' /*case 'Type'*/: break;
18741					case 'min' /*case 'Min'*/: break;
18742					case 'max' /*case 'Max'*/: break;
18743					case 'sort' /*case 'Sort'*/: break;
18744					case 'descending' /*case 'Descending'*/: break;
18745					case 'order' /*case 'Order'*/: break;
18746					case 'casesensitive' /*case 'CaseSensitive'*/: break;
18747					case 'value' /*case 'Value'*/: break;
18748					case 'errorstyle' /*case 'ErrorStyle'*/: break;
18749					case 'errormessage' /*case 'ErrorMessage'*/: break;
18750					case 'errortitle' /*case 'ErrorTitle'*/: break;
18751					case 'cellrangelist' /*case 'CellRangeList'*/: break;
18752					case 'inputmessage' /*case 'InputMessage'*/: break;
18753					case 'inputtitle' /*case 'InputTitle'*/: break;
18754					case 'combohide' /*case 'ComboHide'*/: break;
18755					case 'inputhide' /*case 'InputHide'*/: break;
18756					case 'condition' /*case 'Condition'*/: break;
18757					case 'qualifier' /*case 'Qualifier'*/: break;
18758					case 'useblank' /*case 'UseBlank'*/: break;
18759					case 'value1' /*case 'Value1'*/: break;
18760					case 'value2' /*case 'Value2'*/: break;
18761					case 'format' /*case 'Format'*/: break;
18762					default: seen = false;
18763				} break;
18764
18765				/* MapInfo (schema) */
18766				case 'mapinfo' /*case 'MapInfo'*/: case 'schema' /*case 'Schema'*/: case 'data' /*case 'data'*/: switch(Rn[3]) {
18767					case 'map' /*case 'Map'*/: break;
18768					case 'entry' /*case 'Entry'*/: break;
18769					case 'range' /*case 'Range'*/: break;
18770					case 'xpath' /*case 'XPath'*/: break;
18771					case 'field' /*case 'Field'*/: break;
18772					case 'xsdtype' /*case 'XSDType'*/: break;
18773					case 'filteron' /*case 'FilterOn'*/: break;
18774					case 'aggregate' /*case 'Aggregate'*/: break;
18775					case 'elementtype' /*case 'ElementType'*/: break;
18776					case 'attributetype' /*case 'AttributeType'*/: break;
18777				/* These are from xsd (XML Schema Definition) */
18778					case 'schema' /*case 'schema'*/:
18779					case 'element' /*case 'element'*/:
18780					case 'complextype' /*case 'complexType'*/:
18781					case 'datatype' /*case 'datatype'*/:
18782					case 'all' /*case 'all'*/:
18783					case 'attribute' /*case 'attribute'*/:
18784					case 'extends' /*case 'extends'*/: break;
18785
18786					case 'row' /*case 'row'*/: break;
18787					default: seen = false;
18788				} break;
18789
18790				/* SmartTags (can be anything) */
18791				case 'smarttags' /*case 'SmartTags'*/: break;
18792
18793				default: seen = false; break;
18794			}
18795			if(seen) break;
18796			/* CustomDocumentProperties */
18797			if(Rn[3].match(/!\[CDATA/)) break;
18798			if(!state[state.length-1][1]) throw 'Unrecognized tag: ' + Rn[3] + "|" + state.join("|");
18799			if(state[state.length-1][0]===/*'CustomDocumentProperties'*/'customdocumentproperties') {
18800				if(Rn[0].slice(-2) === "/>") break;
18801				else if(Rn[1]==="/") xlml_set_custprop(Custprops, raw_Rn3, cp, str.slice(pidx, Rn.index));
18802				else { cp = Rn; pidx = Rn.index + Rn[0].length; }
18803				break;
18804			}
18805			if(opts.WTF) throw 'Unrecognized tag: ' + Rn[3] + "|" + state.join("|");
18806	}
18807	var out = ({}/*:any*/);
18808	if(!opts.bookSheets && !opts.bookProps) out.Sheets = sheets;
18809	out.SheetNames = sheetnames;
18810	out.Workbook = Workbook;
18811	out.SSF = dup(table_fmt);
18812	out.Props = Props;
18813	out.Custprops = Custprops;
18814	out.bookType = "xlml";
18815	return out;
18816}
18817
18818function parse_xlml(data/*:RawBytes|string*/, opts)/*:Workbook*/ {
18819	fix_read_opts(opts=opts||{});
18820	switch(opts.type||"base64") {
18821		case "base64": return parse_xlml_xml(Base64_decode(data), opts);
18822		case "binary": case "buffer": case "file": return parse_xlml_xml(data, opts);
18823		case "array": return parse_xlml_xml(a2s(data), opts);
18824	}
18825	/*:: throw new Error("unsupported type " + opts.type); */
18826}
18827
18828/* TODO */
18829function write_props_xlml(wb/*:Workbook*/, opts)/*:string*/ {
18830	var o/*:Array<string>*/ = [];
18831	/* DocumentProperties */
18832	if(wb.Props) o.push(xlml_write_docprops(wb.Props, opts));
18833	/* CustomDocumentProperties */
18834	if(wb.Custprops) o.push(xlml_write_custprops(wb.Props, wb.Custprops, opts));
18835	return o.join("");
18836}
18837/* TODO */
18838function write_wb_xlml(wb/*::, opts*/)/*:string*/ {
18839	/* OfficeDocumentSettings */
18840	/* ExcelWorkbook */
18841	if((((wb||{}).Workbook||{}).WBProps||{}).date1904) return '<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"><Date1904/></ExcelWorkbook>';
18842	return "";
18843}
18844/* TODO */
18845function write_sty_xlml(wb, opts)/*:string*/ {
18846	/* Styles */
18847	var styles/*:Array<string>*/ = ['<Style ss:ID="Default" ss:Name="Normal"><NumberFormat/></Style>'];
18848	opts.cellXfs.forEach(function(xf, id) {
18849		var payload/*:Array<string>*/ = [];
18850		payload.push(writextag('NumberFormat', null, {"ss:Format": escapexml(table_fmt[xf.numFmtId])}));
18851
18852		var o = /*::(*/{"ss:ID": "s" + (21+id)}/*:: :any)*/;
18853		styles.push(writextag('Style', payload.join(""), o));
18854	});
18855	return writextag("Styles", styles.join(""));
18856}
18857function write_name_xlml(n) { return writextag("NamedRange", null, {"ss:Name": n.Name.slice(0,6) == "_xlnm." ? n.Name.slice(6) : n.Name, "ss:RefersTo":"=" + a1_to_rc(n.Ref, {r:0,c:0})}); }
18858function write_names_xlml(wb/*::, opts*/)/*:string*/ {
18859	if(!((wb||{}).Workbook||{}).Names) return "";
18860	/*:: if(!wb || !wb.Workbook || !wb.Workbook.Names) throw new Error("unreachable"); */
18861	var names/*:Array<any>*/ = wb.Workbook.Names;
18862	var out/*:Array<string>*/ = [];
18863	for(var i = 0; i < names.length; ++i) {
18864		var n = names[i];
18865		if(n.Sheet != null) continue;
18866		if(n.Name.match(/^_xlfn\./)) continue;
18867		out.push(write_name_xlml(n));
18868	}
18869	return writextag("Names", out.join(""));
18870}
18871function write_ws_xlml_names(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ {
18872	if(!ws) return "";
18873	if(!((wb||{}).Workbook||{}).Names) return "";
18874	/*:: if(!wb || !wb.Workbook || !wb.Workbook.Names) throw new Error("unreachable"); */
18875	var names/*:Array<any>*/ = wb.Workbook.Names;
18876	var out/*:Array<string>*/ = [];
18877	for(var i = 0; i < names.length; ++i) {
18878		var n = names[i];
18879		if(n.Sheet != idx) continue;
18880		/*switch(n.Name) {
18881			case "_": continue;
18882		}*/
18883		if(n.Name.match(/^_xlfn\./)) continue;
18884		out.push(write_name_xlml(n));
18885	}
18886	return out.join("");
18887}
18888/* WorksheetOptions */
18889function write_ws_xlml_wsopts(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ {
18890	if(!ws) return "";
18891	var o/*:Array<string>*/ = [];
18892	/* NOTE: spec technically allows any order, but stick with implied order */
18893
18894	/* FitToPage */
18895	/* DoNotDisplayColHeaders */
18896	/* DoNotDisplayRowHeaders */
18897	/* ViewableRange */
18898	/* Selection */
18899	/* GridlineColor */
18900	/* Name */
18901	/* ExcelWorksheetType */
18902	/* IntlMacro */
18903	/* Unsynced */
18904	/* Selected */
18905	/* CodeName */
18906
18907	if(ws['!margins']) {
18908		o.push("<PageSetup>");
18909		if(ws['!margins'].header) o.push(writextag("Header", null, {'x:Margin':ws['!margins'].header}));
18910		if(ws['!margins'].footer) o.push(writextag("Footer", null, {'x:Margin':ws['!margins'].footer}));
18911		o.push(writextag("PageMargins", null, {
18912			'x:Bottom': ws['!margins'].bottom || "0.75",
18913			'x:Left': ws['!margins'].left || "0.7",
18914			'x:Right': ws['!margins'].right || "0.7",
18915			'x:Top': ws['!margins'].top || "0.75"
18916		}));
18917		o.push("</PageSetup>");
18918	}
18919
18920	/* PageSetup */
18921	/* DisplayPageBreak */
18922	/* TransitionExpressionEvaluation */
18923	/* TransitionFormulaEntry */
18924	/* Print */
18925	/* Zoom */
18926	/* PageLayoutZoom */
18927	/* PageBreakZoom */
18928	/* ShowPageBreakZoom */
18929	/* DefaultRowHeight */
18930	/* DefaultColumnWidth */
18931	/* StandardWidth */
18932
18933	if(wb && wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx]) {
18934		/* Visible */
18935		if(wb.Workbook.Sheets[idx].Hidden) o.push(writextag("Visible", (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden"), {}));
18936		else {
18937			/* Selected */
18938			for(var i = 0; i < idx; ++i) if(wb.Workbook.Sheets[i] && !wb.Workbook.Sheets[i].Hidden) break;
18939			if(i == idx) o.push("<Selected/>");
18940		}
18941	}
18942
18943	/* LeftColumnVisible */
18944
18945	if(((((wb||{}).Workbook||{}).Views||[])[0]||{}).RTL) o.push("<DisplayRightToLeft/>");
18946
18947	/* GridlineColorIndex */
18948	/* DisplayFormulas */
18949	/* DoNotDisplayGridlines */
18950	/* DoNotDisplayHeadings */
18951	/* DoNotDisplayOutline */
18952	/* ApplyAutomaticOutlineStyles */
18953	/* NoSummaryRowsBelowDetail */
18954	/* NoSummaryColumnsRightDetail */
18955	/* DoNotDisplayZeros */
18956	/* ActiveRow */
18957	/* ActiveColumn */
18958	/* FilterOn */
18959	/* RangeSelection */
18960	/* TopRowVisible */
18961	/* TopRowBottomPane */
18962	/* LeftColumnRightPane */
18963	/* ActivePane */
18964	/* SplitHorizontal */
18965	/* SplitVertical */
18966	/* FreezePanes */
18967	/* FrozenNoSplit */
18968	/* TabColorIndex */
18969	/* Panes */
18970
18971	/* NOTE: Password not supported in XLML Format */
18972	if(ws['!protect']) {
18973		o.push(writetag("ProtectContents", "True"));
18974		if(ws['!protect'].objects) o.push(writetag("ProtectObjects", "True"));
18975		if(ws['!protect'].scenarios) o.push(writetag("ProtectScenarios", "True"));
18976		if(ws['!protect'].selectLockedCells != null && !ws['!protect'].selectLockedCells) o.push(writetag("EnableSelection", "NoSelection"));
18977		else if(ws['!protect'].selectUnlockedCells != null && !ws['!protect'].selectUnlockedCells) o.push(writetag("EnableSelection", "UnlockedCells"));
18978	[
18979		[ "formatCells", "AllowFormatCells" ],
18980		[ "formatColumns", "AllowSizeCols" ],
18981		[ "formatRows", "AllowSizeRows" ],
18982		[ "insertColumns", "AllowInsertCols" ],
18983		[ "insertRows", "AllowInsertRows" ],
18984		[ "insertHyperlinks", "AllowInsertHyperlinks" ],
18985		[ "deleteColumns", "AllowDeleteCols" ],
18986		[ "deleteRows", "AllowDeleteRows" ],
18987		[ "sort", "AllowSort" ],
18988		[ "autoFilter", "AllowFilter" ],
18989		[ "pivotTables", "AllowUsePivotTables" ]
18990	].forEach(function(x) { if(ws['!protect'][x[0]]) o.push("<"+x[1]+"/>"); });
18991	}
18992
18993	if(o.length == 0) return "";
18994	return writextag("WorksheetOptions", o.join(""), {xmlns:XLMLNS.x});
18995}
18996function write_ws_xlml_comment(comments/*:Array<any>*/)/*:string*/ {
18997	return comments.map(function(c) {
18998		// TODO: formatted text
18999		var t = xlml_unfixstr(c.t||"");
19000		var d =writextag("ss:Data", t, {"xmlns":"http://www.w3.org/TR/REC-html40"});
19001		return writextag("Comment", d, {"ss:Author":c.a});
19002	}).join("");
19003}
19004function write_ws_xlml_cell(cell, ref/*:string*/, ws, opts, idx/*:number*/, wb, addr)/*:string*/{
19005	if(!cell || (cell.v == undefined && cell.f == undefined)) return "";
19006
19007	var attr = {};
19008	if(cell.f) attr["ss:Formula"] = "=" + escapexml(a1_to_rc(cell.f, addr));
19009	if(cell.F && cell.F.slice(0, ref.length) == ref) {
19010		var end = decode_cell(cell.F.slice(ref.length + 1));
19011		attr["ss:ArrayRange"] = "RC:R" + (end.r == addr.r ? "" : "[" + (end.r - addr.r) + "]") + "C" + (end.c == addr.c ? "" : "[" + (end.c - addr.c) + "]");
19012	}
19013
19014	if(cell.l && cell.l.Target) {
19015		attr["ss:HRef"] = escapexml(cell.l.Target);
19016		if(cell.l.Tooltip) attr["x:HRefScreenTip"] = escapexml(cell.l.Tooltip);
19017	}
19018
19019	if(ws['!merges']) {
19020		var marr = ws['!merges'];
19021		for(var mi = 0; mi != marr.length; ++mi) {
19022			if(marr[mi].s.c != addr.c || marr[mi].s.r != addr.r) continue;
19023			if(marr[mi].e.c > marr[mi].s.c) attr['ss:MergeAcross'] = marr[mi].e.c - marr[mi].s.c;
19024			if(marr[mi].e.r > marr[mi].s.r) attr['ss:MergeDown'] = marr[mi].e.r - marr[mi].s.r;
19025		}
19026	}
19027
19028	var t = "", p = "";
19029	switch(cell.t) {
19030		case 'z': if(!opts.sheetStubs) return ""; break;
19031		case 'n': t = 'Number'; p = String(cell.v); break;
19032		case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break;
19033		case 'e': t = 'Error'; p = BErr[cell.v]; break;
19034		case 'd': t = 'DateTime'; p = new Date(cell.v).toISOString(); if(cell.z == null) cell.z = cell.z || table_fmt[14]; break;
19035		case 's': t = 'String'; p = escapexlml(cell.v||""); break;
19036	}
19037	/* TODO: cell style */
19038	var os = get_cell_style(opts.cellXfs, cell, opts);
19039	attr["ss:StyleID"] = "s" + (21+os);
19040	attr["ss:Index"] = addr.c + 1;
19041	var _v = (cell.v != null ? p : "");
19042	var m = cell.t == 'z' ? "" : ('<Data ss:Type="' + t + '">' + _v + '</Data>');
19043
19044	if((cell.c||[]).length > 0) m += write_ws_xlml_comment(cell.c);
19045
19046	return writextag("Cell", m, attr);
19047}
19048function write_ws_xlml_row(R/*:number*/, row)/*:string*/ {
19049	var o = '<Row ss:Index="' + (R+1) + '"';
19050	if(row) {
19051		if(row.hpt && !row.hpx) row.hpx = pt2px(row.hpt);
19052		if(row.hpx) o += ' ss:AutoFitHeight="0" ss:Height="' + row.hpx + '"';
19053		if(row.hidden) o += ' ss:Hidden="1"';
19054	}
19055	return o + '>';
19056}
19057/* TODO */
19058function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ {
19059	if(!ws['!ref']) return "";
19060	var range/*:Range*/ = safe_decode_range(ws['!ref']);
19061	var marr/*:Array<Range>*/ = ws['!merges'] || [], mi = 0;
19062	var o/*:Array<string>*/ = [];
19063	if(ws['!cols']) ws['!cols'].forEach(function(n, i) {
19064		process_col(n);
19065		var w = !!n.width;
19066		var p = col_obj_w(i, n);
19067		var k/*:any*/ = {"ss:Index":i+1};
19068		if(w) k['ss:Width'] = width2px(p.width);
19069		if(n.hidden) k['ss:Hidden']="1";
19070		o.push(writextag("Column",null,k));
19071	});
19072	var dense = Array.isArray(ws);
19073	for(var R = range.s.r; R <= range.e.r; ++R) {
19074		var row = [write_ws_xlml_row(R, (ws['!rows']||[])[R])];
19075		for(var C = range.s.c; C <= range.e.c; ++C) {
19076			var skip = false;
19077			for(mi = 0; mi != marr.length; ++mi) {
19078				if(marr[mi].s.c > C) continue;
19079				if(marr[mi].s.r > R) continue;
19080				if(marr[mi].e.c < C) continue;
19081				if(marr[mi].e.r < R) continue;
19082				if(marr[mi].s.c != C || marr[mi].s.r != R) skip = true;
19083				break;
19084			}
19085			if(skip) continue;
19086			var addr = {r:R,c:C};
19087			var ref = encode_cell(addr), cell = dense ? (ws[R]||[])[C] : ws[ref];
19088			row.push(write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr));
19089		}
19090		row.push("</Row>");
19091		if(row.length > 2) o.push(row.join(""));
19092	}
19093	return o.join("");
19094}
19095function write_ws_xlml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ {
19096	var o/*:Array<string>*/ = [];
19097	var s = wb.SheetNames[idx];
19098	var ws = wb.Sheets[s];
19099
19100	var t/*:string*/ = ws ? write_ws_xlml_names(ws, opts, idx, wb) : "";
19101	if(t.length > 0) o.push("<Names>" + t + "</Names>");
19102
19103	/* Table */
19104	t = ws ? write_ws_xlml_table(ws, opts, idx, wb) : "";
19105	if(t.length > 0) o.push("<Table>" + t + "</Table>");
19106
19107	/* WorksheetOptions */
19108	o.push(write_ws_xlml_wsopts(ws, opts, idx, wb));
19109
19110	if(ws["!autofilter"]) o.push('<AutoFilter x:Range="' + a1_to_rc(fix_range(ws["!autofilter"].ref), {r:0,c:0}) + '" xmlns="urn:schemas-microsoft-com:office:excel"></AutoFilter>');
19111
19112	return o.join("");
19113}
19114function write_xlml(wb, opts)/*:string*/ {
19115	if(!opts) opts = {};
19116	if(!wb.SSF) wb.SSF = dup(table_fmt);
19117	if(wb.SSF) {
19118		make_ssf(); SSF_load_table(wb.SSF);
19119		// $FlowIgnore
19120		opts.revssf = evert_num(wb.SSF); opts.revssf[wb.SSF[65535]] = 0;
19121		opts.ssf = wb.SSF;
19122		opts.cellXfs = [];
19123		get_cell_style(opts.cellXfs, {}, {revssf:{"General":0}});
19124	}
19125	var d/*:Array<string>*/ = [];
19126	d.push(write_props_xlml(wb, opts));
19127	d.push(write_wb_xlml(wb, opts));
19128	d.push("");
19129	d.push("");
19130	for(var i = 0; i < wb.SheetNames.length; ++i)
19131		d.push(writextag("Worksheet", write_ws_xlml(i, opts, wb), {"ss:Name":escapexml(wb.SheetNames[i])}));
19132	d[2] = write_sty_xlml(wb, opts);
19133	d[3] = write_names_xlml(wb, opts);
19134	return XML_HEADER + writextag("Workbook", d.join(""), {
19135		'xmlns':      XLMLNS.ss,
19136		'xmlns:o':    XLMLNS.o,
19137		'xmlns:x':    XLMLNS.x,
19138		'xmlns:ss':   XLMLNS.ss,
19139		'xmlns:dt':   XLMLNS.dt,
19140		'xmlns:html': XLMLNS.html
19141	});
19142}
19143/* [MS-OLEDS] 2.3.8 CompObjStream */
19144function parse_compobj(obj/*:CFBEntry*/) {
19145	var v = {};
19146	var o = obj.content;
19147	/*:: if(o == null) return; */
19148
19149	/* [MS-OLEDS] 2.3.7 CompObjHeader -- All fields MUST be ignored */
19150	o.l = 28;
19151
19152	v.AnsiUserType = o.read_shift(0, "lpstr-ansi");
19153	v.AnsiClipboardFormat = parse_ClipboardFormatOrAnsiString(o);
19154
19155	if(o.length - o.l <= 4) return v;
19156
19157	var m/*:number*/ = o.read_shift(4);
19158	if(m == 0 || m > 40) return v;
19159	o.l-=4; v.Reserved1 = o.read_shift(0, "lpstr-ansi");
19160
19161	if(o.length - o.l <= 4) return v;
19162	m = o.read_shift(4);
19163	if(m !== 0x71b239f4) return v;
19164	v.UnicodeClipboardFormat = parse_ClipboardFormatOrUnicodeString(o);
19165
19166	m = o.read_shift(4);
19167	if(m == 0 || m > 40) return v;
19168	o.l-=4; v.Reserved2 = o.read_shift(0, "lpwstr");
19169}
19170
19171/*
19172	Continue logic for:
19173	- 2.4.58 Continue          0x003c
19174	- 2.4.59 ContinueBigName   0x043c
19175	- 2.4.60 ContinueFrt       0x0812
19176	- 2.4.61 ContinueFrt11     0x0875
19177	- 2.4.62 ContinueFrt12     0x087f
19178*/
19179var CONTINUE_RT = [ 0x003c, 0x043c, 0x0812, 0x0875, 0x087f ];
19180function slurp(RecordType, R, blob, length/*:number*/, opts)/*:any*/ {
19181	var l = length;
19182	var bufs = [];
19183	var d = blob.slice(blob.l,blob.l+l);
19184	if(opts && opts.enc && opts.enc.insitu && d.length > 0) switch(RecordType) {
19185	case 0x0009: case 0x0209: case 0x0409: case 0x0809/* BOF */: case 0x002F /* FilePass */: case 0x0195 /* FileLock */: case 0x00E1 /* InterfaceHdr */: case 0x0196 /* RRDInfo */: case 0x0138 /* RRDHead */: case 0x0194 /* UsrExcl */: case 0x000a /* EOF */:
19186		break;
19187	case 0x0085 /* BoundSheet8 */:
19188		break;
19189	default:
19190		opts.enc.insitu(d);
19191	}
19192	bufs.push(d);
19193	blob.l += l;
19194	var nextrt = __readUInt16LE(blob,blob.l), next = XLSRecordEnum[nextrt];
19195	var start = 0;
19196	while(next != null && CONTINUE_RT.indexOf(nextrt) > -1) {
19197		l = __readUInt16LE(blob,blob.l+2);
19198		start = blob.l + 4;
19199		if(nextrt == 0x0812 /* ContinueFrt */) start += 4;
19200		else if(nextrt == 0x0875 || nextrt == 0x087f) {
19201			start += 12;
19202		}
19203		d = blob.slice(start,blob.l+4+l);
19204		bufs.push(d);
19205		blob.l += 4+l;
19206		next = (XLSRecordEnum[nextrt = __readUInt16LE(blob, blob.l)]);
19207	}
19208	var b = (bconcat(bufs)/*:any*/);
19209	prep_blob(b, 0);
19210	var ll = 0; b.lens = [];
19211	for(var j = 0; j < bufs.length; ++j) { b.lens.push(ll); ll += bufs[j].length; }
19212	if(b.length < length) throw "XLS Record 0x" + RecordType.toString(16) + " Truncated: " + b.length + " < " + length;
19213	return R.f(b, b.length, opts);
19214}
19215
19216function safe_format_xf(p/*:any*/, opts/*:ParseOpts*/, date1904/*:?boolean*/) {
19217	if(p.t === 'z') return;
19218	if(!p.XF) return;
19219	var fmtid = 0;
19220	try {
19221		fmtid = p.z || p.XF.numFmtId || 0;
19222		if(opts.cellNF) p.z = table_fmt[fmtid];
19223	} catch(e) { if(opts.WTF) throw e; }
19224	if(!opts || opts.cellText !== false) try {
19225		if(p.t === 'e') { p.w = p.w || BErr[p.v]; }
19226		else if(fmtid === 0 || fmtid == "General") {
19227			if(p.t === 'n') {
19228				if((p.v|0) === p.v) p.w = p.v.toString(10);
19229				else p.w = SSF_general_num(p.v);
19230			}
19231			else p.w = SSF_general(p.v);
19232		}
19233		else p.w = SSF_format(fmtid,p.v, {date1904:!!date1904, dateNF: opts && opts.dateNF});
19234	} catch(e) { if(opts.WTF) throw e; }
19235	if(opts.cellDates && fmtid && p.t == 'n' && fmt_is_date(table_fmt[fmtid] || String(fmtid))) {
19236		var _d = SSF_parse_date_code(p.v); if(_d) { p.t = 'd'; p.v = new Date(_d.y, _d.m-1,_d.d,_d.H,_d.M,_d.S,_d.u); }
19237	}
19238}
19239
19240function make_cell(val, ixfe, t)/*:Cell*/ {
19241	return ({v:val, ixfe:ixfe, t:t}/*:any*/);
19242}
19243
19244// 2.3.2
19245function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
19246	var wb = ({opts:{}}/*:any*/);
19247	var Sheets = {};
19248	if(DENSE != null && options.dense == null) options.dense = DENSE;
19249	var out/*:Worksheet*/ = ((options.dense ? [] : {})/*:any*/);
19250	var Directory = {};
19251	var range/*:Range*/ = ({}/*:any*/);
19252	var last_formula = null;
19253	var sst/*:SST*/ = ([]/*:any*/);
19254	var cur_sheet = "";
19255	var Preamble = {};
19256	var lastcell, last_cell = "", cc/*:Cell*/, cmnt, rngC, rngR;
19257	var sharedf = {};
19258	var arrayf/*:Array<[Range, string]>*/ = [];
19259	var temp_val/*:Cell*/;
19260	var country;
19261	var XFs = []; /* XF records */
19262	var palette/*:Array<[number, number, number]>*/ = [];
19263	var Workbook/*:WBWBProps*/ = ({ Sheets:[], WBProps:{date1904:false}, Views:[{}] }/*:any*/), wsprops = {};
19264	var get_rgb = function getrgb(icv/*:number*/)/*:[number, number, number]*/ {
19265		if(icv < 8) return XLSIcv[icv];
19266		if(icv < 64) return palette[icv-8] || XLSIcv[icv];
19267		return XLSIcv[icv];
19268	};
19269	var process_cell_style = function pcs(cell, line/*:any*/, options) {
19270		var xfd = line.XF.data;
19271		if(!xfd || !xfd.patternType || !options || !options.cellStyles) return;
19272		line.s = ({}/*:any*/);
19273		line.s.patternType = xfd.patternType;
19274		var t;
19275		if((t = rgb2Hex(get_rgb(xfd.icvFore)))) { line.s.fgColor = {rgb:t}; }
19276		if((t = rgb2Hex(get_rgb(xfd.icvBack)))) { line.s.bgColor = {rgb:t}; }
19277	};
19278	var addcell = function addcell(cell/*:any*/, line/*:any*/, options/*:any*/) {
19279		if(file_depth > 1) return;
19280		if(options.sheetRows && cell.r >= options.sheetRows) return;
19281		if(options.cellStyles && line.XF && line.XF.data) process_cell_style(cell, line, options);
19282		delete line.ixfe; delete line.XF;
19283		lastcell = cell;
19284		last_cell = encode_cell(cell);
19285		if(!range || !range.s || !range.e) range = {s:{r:0,c:0},e:{r:0,c:0}};
19286		if(cell.r < range.s.r) range.s.r = cell.r;
19287		if(cell.c < range.s.c) range.s.c = cell.c;
19288		if(cell.r + 1 > range.e.r) range.e.r = cell.r + 1;
19289		if(cell.c + 1 > range.e.c) range.e.c = cell.c + 1;
19290		if(options.cellFormula && line.f) {
19291			for(var afi = 0; afi < arrayf.length; ++afi) {
19292				if(arrayf[afi][0].s.c > cell.c || arrayf[afi][0].s.r > cell.r) continue;
19293				if(arrayf[afi][0].e.c < cell.c || arrayf[afi][0].e.r < cell.r) continue;
19294				line.F = encode_range(arrayf[afi][0]);
19295				if(arrayf[afi][0].s.c != cell.c || arrayf[afi][0].s.r != cell.r) delete line.f;
19296				if(line.f) line.f = "" + stringify_formula(arrayf[afi][1], range, cell, supbooks, opts);
19297				break;
19298			}
19299		}
19300		{
19301			if(options.dense) {
19302				if(!out[cell.r]) out[cell.r] = [];
19303				out[cell.r][cell.c] = line;
19304			} else out[last_cell] = line;
19305		}
19306	};
19307	var opts = ({
19308		enc: false, // encrypted
19309		sbcch: 0, // cch in the preceding SupBook
19310		snames: [], // sheetnames
19311		sharedf: sharedf, // shared formulae by address
19312		arrayf: arrayf, // array formulae array
19313		rrtabid: [], // RRTabId
19314		lastuser: "", // Last User from WriteAccess
19315		biff: 8, // BIFF version
19316		codepage: 0, // CP from CodePage record
19317		winlocked: 0, // fLockWn from WinProtect
19318		cellStyles: !!options && !!options.cellStyles,
19319		WTF: !!options && !!options.wtf
19320	}/*:any*/);
19321	if(options.password) opts.password = options.password;
19322	var themes;
19323	var merges/*:Array<Range>*/ = [];
19324	var objects = [];
19325	var colinfo/*:Array<ColInfo>*/ = [], rowinfo/*:Array<RowInfo>*/ = [];
19326	var seencol = false;
19327	var supbooks = ([]/*:any*/); // 1-indexed, will hold extern names
19328	supbooks.SheetNames = opts.snames;
19329	supbooks.sharedf = opts.sharedf;
19330	supbooks.arrayf = opts.arrayf;
19331	supbooks.names = [];
19332	supbooks.XTI = [];
19333	var last_RT = 0;
19334	var file_depth = 0; /* TODO: make a real stack */
19335	var BIFF2Fmt = 0, BIFF2FmtTable/*:Array<string>*/ = [];
19336	var FilterDatabases = []; /* TODO: sort out supbooks and process elsewhere */
19337	var last_lbl/*:?DefinedName*/;
19338
19339	/* explicit override for some broken writers */
19340	opts.codepage = 1200;
19341	set_cp(1200);
19342	var seen_codepage = false;
19343	while(blob.l < blob.length - 1) {
19344		var s = blob.l;
19345		var RecordType = blob.read_shift(2);
19346		if(RecordType === 0 && last_RT === 0x000a /* EOF */) break;
19347		var length = (blob.l === blob.length ? 0 : blob.read_shift(2));
19348		var R = XLSRecordEnum[RecordType];
19349		if(file_depth == 0 && [0x0009, 0x0209, 0x0409, 0x0809].indexOf(RecordType) == -1 /* BOF */) break;
19350		//console.log(RecordType.toString(16), RecordType, R, blob.l, length, blob.length);
19351		//if(!R) console.log(blob.slice(blob.l, blob.l + length));
19352		if(R && R.f) {
19353			if(options.bookSheets) {
19354				if(last_RT === 0x0085 /* BoundSheet8 */ && RecordType !== 0x0085 /* R.n !== 'BoundSheet8' */) break;
19355			}
19356			last_RT = RecordType;
19357			if(R.r === 2 || R.r == 12) {
19358				var rt = blob.read_shift(2); length -= 2;
19359				if(!opts.enc && rt !== RecordType && (((rt&0xFF)<<8)|(rt>>8)) !== RecordType) throw new Error("rt mismatch: " + rt + "!=" + RecordType);
19360				if(R.r == 12){
19361					blob.l += 10; length -= 10;
19362				} // skip FRT
19363			}
19364			//console.error(R,blob.l,length,blob.length);
19365			var val/*:any*/ = ({}/*:any*/);
19366			if(RecordType === 0x000a /* EOF */) val = /*::(*/R.f(blob, length, opts)/*:: :any)*/;
19367			else val = /*::(*/slurp(RecordType, R, blob, length, opts)/*:: :any)*/;
19368			/*:: val = (val:any); */
19369			if(file_depth == 0 && [0x0009, 0x0209, 0x0409, 0x0809].indexOf(last_RT) === -1 /* BOF */) continue;
19370			switch(RecordType) {
19371				case 0x0022 /* Date1904 */:
19372					/*:: if(!Workbook.WBProps) Workbook.WBProps = {}; */
19373					wb.opts.Date1904 = Workbook.WBProps.date1904 = val; break;
19374				case 0x0086 /* WriteProtect */: wb.opts.WriteProtect = true; break;
19375				case 0x002f /* FilePass */:
19376					if(!opts.enc) blob.l = 0;
19377					opts.enc = val;
19378					if(!options.password) throw new Error("File is password-protected");
19379					if(val.valid == null) throw new Error("Encryption scheme unsupported");
19380					if(!val.valid) throw new Error("Password is incorrect");
19381					break;
19382				case 0x005c /* WriteAccess */: opts.lastuser = val; break;
19383				case 0x0042 /* CodePage */:
19384					var cpval = Number(val);
19385					/* overrides based on test cases */
19386					switch(cpval) {
19387						case 0x5212: cpval =  1200; break;
19388						case 0x8000: cpval = 10000; break;
19389						case 0x8001: cpval =  1252; break;
19390					}
19391					set_cp(opts.codepage = cpval);
19392					seen_codepage = true;
19393					break;
19394				case 0x013d /* RRTabId */: opts.rrtabid = val; break;
19395				case 0x0019 /* WinProtect */: opts.winlocked = val; break;
19396				case 0x01b7 /* RefreshAll */: wb.opts["RefreshAll"] = val; break;
19397				case 0x000c /* CalcCount */: wb.opts["CalcCount"] = val; break;
19398				case 0x0010 /* CalcDelta */: wb.opts["CalcDelta"] = val; break;
19399				case 0x0011 /* CalcIter */: wb.opts["CalcIter"] = val; break;
19400				case 0x000d /* CalcMode */: wb.opts["CalcMode"] = val; break;
19401				case 0x000e /* CalcPrecision */: wb.opts["CalcPrecision"] = val; break;
19402				case 0x005f /* CalcSaveRecalc */: wb.opts["CalcSaveRecalc"] = val; break;
19403				case 0x000f /* CalcRefMode */: opts.CalcRefMode = val; break; // TODO: implement R1C1
19404				case 0x08a3 /* ForceFullCalculation */: wb.opts.FullCalc = val; break;
19405				case 0x0081 /* WsBool */:
19406					if(val.fDialog) out["!type"] = "dialog";
19407					if(!val.fBelow) (out["!outline"] || (out["!outline"] = {})).above = true;
19408					if(!val.fRight) (out["!outline"] || (out["!outline"] = {})).left = true;
19409					break; // TODO
19410				case 0x00e0 /* XF */:
19411					XFs.push(val); break;
19412				case 0x01ae /* SupBook */:
19413					supbooks.push([val]);
19414					supbooks[supbooks.length-1].XTI = [];
19415					break;
19416				case 0x0023: case 0x0223 /* ExternName */:
19417					supbooks[supbooks.length-1].push(val);
19418					break;
19419				case 0x0018: case 0x0218 /* Lbl */:
19420					last_lbl = ({
19421						Name: val.Name,
19422						Ref: stringify_formula(val.rgce,range,null,supbooks,opts)
19423					}/*:DefinedName*/);
19424					if(val.itab > 0) last_lbl.Sheet = val.itab - 1;
19425					supbooks.names.push(last_lbl);
19426					if(!supbooks[0]) { supbooks[0] = []; supbooks[0].XTI = []; }
19427					supbooks[supbooks.length-1].push(val);
19428					if(val.Name == "_xlnm._FilterDatabase" && val.itab > 0)
19429						if(val.rgce && val.rgce[0] && val.rgce[0][0] && val.rgce[0][0][0] == 'PtgArea3d')
19430							FilterDatabases[val.itab - 1] = { ref: encode_range(val.rgce[0][0][1][2]) };
19431					break;
19432				case 0x0016 /* ExternCount */: opts.ExternCount = val; break;
19433				case 0x0017 /* ExternSheet */:
19434					if(supbooks.length == 0) { supbooks[0] = []; supbooks[0].XTI = []; }
19435					supbooks[supbooks.length - 1].XTI = supbooks[supbooks.length - 1].XTI.concat(val); supbooks.XTI = supbooks.XTI.concat(val); break;
19436				case 0x0894 /* NameCmt */:
19437					/* TODO: search for correct name */
19438					if(opts.biff < 8) break;
19439					if(last_lbl != null) last_lbl.Comment = val[1];
19440					break;
19441				case 0x0012 /* Protect */: out["!protect"] = val; break; /* for sheet or book */
19442				case 0x0013 /* Password */: if(val !== 0 && opts.WTF) console.error("Password verifier: " + val); break;
19443				case 0x0085 /* BoundSheet8 */: {
19444					Directory[val.pos] = val;
19445					opts.snames.push(val.name);
19446				} break;
19447				case 0x000a /* EOF */: {
19448					if(--file_depth) break;
19449					if(range.e) {
19450						if(range.e.r > 0 && range.e.c > 0) {
19451							range.e.r--; range.e.c--;
19452							out["!ref"] = encode_range(range);
19453							if(options.sheetRows && options.sheetRows <= range.e.r) {
19454								var tmpri = range.e.r;
19455								range.e.r = options.sheetRows - 1;
19456								out["!fullref"] = out["!ref"];
19457								out["!ref"] = encode_range(range);
19458								range.e.r = tmpri;
19459							}
19460							range.e.r++; range.e.c++;
19461						}
19462						if(merges.length > 0) out["!merges"] = merges;
19463						if(objects.length > 0) out["!objects"] = objects;
19464						if(colinfo.length > 0) out["!cols"] = colinfo;
19465						if(rowinfo.length > 0) out["!rows"] = rowinfo;
19466						Workbook.Sheets.push(wsprops);
19467					}
19468					if(cur_sheet === "") Preamble = out; else Sheets[cur_sheet] = out;
19469					out = ((options.dense ? [] : {})/*:any*/);
19470				} break;
19471				case 0x0009: case 0x0209: case 0x0409: case 0x0809 /* BOF */: {
19472					if(opts.biff === 8) opts.biff = {
19473						/*::[*/0x0009/*::]*/:2,
19474						/*::[*/0x0209/*::]*/:3,
19475						/*::[*/0x0409/*::]*/:4
19476					}[RecordType] || {
19477						/*::[*/0x0200/*::]*/:2,
19478						/*::[*/0x0300/*::]*/:3,
19479						/*::[*/0x0400/*::]*/:4,
19480						/*::[*/0x0500/*::]*/:5,
19481						/*::[*/0x0600/*::]*/:8,
19482						/*::[*/0x0002/*::]*/:2,
19483						/*::[*/0x0007/*::]*/:2
19484					}[val.BIFFVer] || 8;
19485					opts.biffguess = val.BIFFVer == 0;
19486					if(val.BIFFVer == 0 && val.dt == 0x1000) { opts.biff = 5; seen_codepage = true; set_cp(opts.codepage = 28591); }
19487					if(opts.biff == 8 && val.BIFFVer == 0 && val.dt == 16) opts.biff = 2;
19488					if(file_depth++) break;
19489					out = ((options.dense ? [] : {})/*:any*/);
19490
19491					if(opts.biff < 8 && !seen_codepage) { seen_codepage = true; set_cp(opts.codepage = options.codepage || 1252); }
19492
19493					if(opts.biff < 5 || val.BIFFVer == 0 && val.dt == 0x1000) {
19494						if(cur_sheet === "") cur_sheet = "Sheet1";
19495						range = {s:{r:0,c:0},e:{r:0,c:0}};
19496						/* fake BoundSheet8 */
19497						var fakebs8 = {pos: blob.l - length, name:cur_sheet};
19498						Directory[fakebs8.pos] = fakebs8;
19499						opts.snames.push(cur_sheet);
19500					}
19501					else cur_sheet = (Directory[s] || {name:""}).name;
19502					if(val.dt == 0x20) out["!type"] = "chart";
19503					if(val.dt == 0x40) out["!type"] = "macro";
19504					merges = [];
19505					objects = [];
19506					opts.arrayf = arrayf = [];
19507					colinfo = []; rowinfo = [];
19508					seencol = false;
19509					wsprops = {Hidden:(Directory[s]||{hs:0}).hs, name:cur_sheet };
19510				} break;
19511				case 0x0203 /* Number */: case 0x0003 /* BIFF2NUM */: case 0x0002 /* BIFF2INT */: {
19512					if(out["!type"] == "chart") if(options.dense ? (out[val.r]||[])[val.c]: out[encode_cell({c:val.c, r:val.r})]) ++val.c;
19513					temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe]||{}, v:val.val, t:'n'}/*:any*/);
19514					if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19515					safe_format_xf(temp_val, options, wb.opts.Date1904);
19516					addcell({c:val.c, r:val.r}, temp_val, options);
19517				} break;
19518				case 0x0005: case 0x0205 /* BoolErr */: {
19519					temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:val.t}/*:any*/);
19520					if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19521					safe_format_xf(temp_val, options, wb.opts.Date1904);
19522					addcell({c:val.c, r:val.r}, temp_val, options);
19523				} break;
19524				case 0x027e /* RK */: {
19525					temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.rknum, t:'n'}/*:any*/);
19526					if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19527					safe_format_xf(temp_val, options, wb.opts.Date1904);
19528					addcell({c:val.c, r:val.r}, temp_val, options);
19529				} break;
19530				case 0x00bd /* MulRk */: {
19531					for(var j = val.c; j <= val.C; ++j) {
19532						var ixfe = val.rkrec[j-val.c][0];
19533						temp_val= ({ixfe:ixfe, XF:XFs[ixfe], v:val.rkrec[j-val.c][1], t:'n'}/*:any*/);
19534						if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19535						safe_format_xf(temp_val, options, wb.opts.Date1904);
19536						addcell({c:j, r:val.r}, temp_val, options);
19537					}
19538				} break;
19539				case 0x0006: case 0x0206: case 0x0406 /* Formula */: {
19540					if(val.val == 'String') { last_formula = val; break; }
19541					temp_val = make_cell(val.val, val.cell.ixfe, val.tt);
19542					temp_val.XF = XFs[temp_val.ixfe];
19543					if(options.cellFormula) {
19544						var _f = val.formula;
19545						if(_f && _f[0] && _f[0][0] && _f[0][0][0] == 'PtgExp') {
19546							var _fr = _f[0][0][1][0], _fc = _f[0][0][1][1];
19547							var _fe = encode_cell({r:_fr, c:_fc});
19548							if(sharedf[_fe]) temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
19549							else temp_val.F = ((options.dense ? (out[_fr]||[])[_fc]: out[_fe]) || {}).F;
19550						} else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts);
19551					}
19552					if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19553					safe_format_xf(temp_val, options, wb.opts.Date1904);
19554					addcell(val.cell, temp_val, options);
19555					last_formula = val;
19556				} break;
19557				case 0x0007: case 0x0207 /* String */: {
19558					if(last_formula) { /* technically always true */
19559						last_formula.val = val;
19560						temp_val = make_cell(val, last_formula.cell.ixfe, 's');
19561						temp_val.XF = XFs[temp_val.ixfe];
19562						if(options.cellFormula) {
19563							temp_val.f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts);
19564						}
19565						if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19566						safe_format_xf(temp_val, options, wb.opts.Date1904);
19567						addcell(last_formula.cell, temp_val, options);
19568						last_formula = null;
19569					} else throw new Error("String record expects Formula");
19570				} break;
19571				case 0x0021: case 0x0221 /* Array */: {
19572					arrayf.push(val);
19573					var _arraystart = encode_cell(val[0].s);
19574					cc = options.dense ? (out[val[0].s.r]||[])[val[0].s.c] : out[_arraystart];
19575					if(options.cellFormula && cc) {
19576						if(!last_formula) break; /* technically unreachable */
19577						if(!_arraystart || !cc) break;
19578						cc.f = ""+stringify_formula(val[1], range, val[0], supbooks, opts);
19579						cc.F = encode_range(val[0]);
19580					}
19581				} break;
19582				case 0x04bc /* ShrFmla */: {
19583					if(!options.cellFormula) break;
19584					if(last_cell) {
19585						/* TODO: capture range */
19586						if(!last_formula) break; /* technically unreachable */
19587						sharedf[encode_cell(last_formula.cell)]= val[0];
19588						cc = options.dense ? (out[last_formula.cell.r]||[])[last_formula.cell.c] : out[encode_cell(last_formula.cell)];
19589						(cc||{}).f = ""+stringify_formula(val[0], range, lastcell, supbooks, opts);
19590					}
19591				} break;
19592				case 0x00fd /* LabelSst */:
19593					temp_val=make_cell(sst[val.isst].t, val.ixfe, 's');
19594					if(sst[val.isst].h) temp_val.h = sst[val.isst].h;
19595					temp_val.XF = XFs[temp_val.ixfe];
19596					if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19597					safe_format_xf(temp_val, options, wb.opts.Date1904);
19598					addcell({c:val.c, r:val.r}, temp_val, options);
19599					break;
19600				case 0x0201 /* Blank */: if(options.sheetStubs) {
19601					temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], t:'z'}/*:any*/);
19602					if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19603					safe_format_xf(temp_val, options, wb.opts.Date1904);
19604					addcell({c:val.c, r:val.r}, temp_val, options);
19605				} break;
19606				case 0x00be /* MulBlank */: if(options.sheetStubs) {
19607					for(var _j = val.c; _j <= val.C; ++_j) {
19608						var _ixfe = val.ixfe[_j-val.c];
19609						temp_val= ({ixfe:_ixfe, XF:XFs[_ixfe], t:'z'}/*:any*/);
19610						if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19611						safe_format_xf(temp_val, options, wb.opts.Date1904);
19612						addcell({c:_j, r:val.r}, temp_val, options);
19613					}
19614				} break;
19615				case 0x00d6 /* RString */:
19616				case 0x0204 /* Label */: case 0x0004 /* BIFF2STR */:
19617					temp_val=make_cell(val.val, val.ixfe, 's');
19618					temp_val.XF = XFs[temp_val.ixfe];
19619					if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F];
19620					safe_format_xf(temp_val, options, wb.opts.Date1904);
19621					addcell({c:val.c, r:val.r}, temp_val, options);
19622					break;
19623
19624				case 0x0000: case 0x0200 /* Dimensions */: {
19625					if(file_depth === 1) range = val; /* TODO: stack */
19626				} break;
19627				case 0x00fc /* SST */: {
19628					sst = val;
19629				} break;
19630				case 0x041e /* Format */: { /* val = [id, fmt] */
19631					if(opts.biff == 4) {
19632						BIFF2FmtTable[BIFF2Fmt++] = val[1];
19633						for(var b4idx = 0; b4idx < BIFF2Fmt + 163; ++b4idx) if(table_fmt[b4idx] == val[1]) break;
19634						if(b4idx >= 163) SSF__load(val[1], BIFF2Fmt + 163);
19635					}
19636					else SSF__load(val[1], val[0]);
19637				} break;
19638				case 0x001e /* BIFF2FORMAT */: {
19639					BIFF2FmtTable[BIFF2Fmt++] = val;
19640					for(var b2idx = 0; b2idx < BIFF2Fmt + 163; ++b2idx) if(table_fmt[b2idx] == val) break;
19641					if(b2idx >= 163) SSF__load(val, BIFF2Fmt + 163);
19642				} break;
19643
19644				case 0x00e5 /* MergeCells */: merges = merges.concat(val); break;
19645
19646				case 0x005d /* Obj */: objects[val.cmo[0]] = opts.lastobj = val; break;
19647				case 0x01b6 /* TxO */: opts.lastobj.TxO = val; break;
19648				case 0x007f /* ImData */: opts.lastobj.ImData = val; break;
19649
19650				case 0x01b8 /* HLink */: {
19651					for(rngR = val[0].s.r; rngR <= val[0].e.r; ++rngR)
19652						for(rngC = val[0].s.c; rngC <= val[0].e.c; ++rngC) {
19653							cc = options.dense ? (out[rngR]||[])[rngC] : out[encode_cell({c:rngC,r:rngR})];
19654							if(cc) cc.l = val[1];
19655						}
19656				} break;
19657				case 0x0800 /* HLinkTooltip */: {
19658					for(rngR = val[0].s.r; rngR <= val[0].e.r; ++rngR)
19659						for(rngC = val[0].s.c; rngC <= val[0].e.c; ++rngC) {
19660							cc = options.dense ? (out[rngR]||[])[rngC] : out[encode_cell({c:rngC,r:rngR})];
19661							if(cc && cc.l) cc.l.Tooltip = val[1];
19662							}
19663				} break;
19664				case 0x001c /* Note */: {
19665					if(opts.biff <= 5 && opts.biff >= 2) break; /* TODO: BIFF5 */
19666					cc = options.dense ? (out[val[0].r]||[])[val[0].c] : out[encode_cell(val[0])];
19667					var noteobj = objects[val[2]];
19668					if(!cc) {
19669						if(options.dense) {
19670							if(!out[val[0].r]) out[val[0].r] = [];
19671							cc = out[val[0].r][val[0].c] = ({t:"z"}/*:any*/);
19672						} else {
19673							cc = out[encode_cell(val[0])] = ({t:"z"}/*:any*/);
19674						}
19675						range.e.r = Math.max(range.e.r, val[0].r);
19676						range.s.r = Math.min(range.s.r, val[0].r);
19677						range.e.c = Math.max(range.e.c, val[0].c);
19678						range.s.c = Math.min(range.s.c, val[0].c);
19679					}
19680					if(!cc.c) cc.c = [];
19681					cmnt = {a:val[1],t:noteobj.TxO.t};
19682					cc.c.push(cmnt);
19683				} break;
19684				case 0x087d /* XFExt */: update_xfext(XFs[val.ixfe], val.ext); break;
19685				case 0x007d /* ColInfo */: {
19686					if(!opts.cellStyles) break;
19687					while(val.e >= val.s) {
19688						colinfo[val.e--] = { width: val.w/256, level: (val.level || 0), hidden: !!(val.flags & 1) };
19689						if(!seencol) { seencol = true; find_mdw_colw(val.w/256); }
19690						process_col(colinfo[val.e+1]);
19691					}
19692				} break;
19693				case 0x0208 /* Row */: {
19694					var rowobj = {};
19695					if(val.level != null) { rowinfo[val.r] = rowobj; rowobj.level = val.level; }
19696					if(val.hidden) { rowinfo[val.r] = rowobj; rowobj.hidden = true; }
19697					if(val.hpt) {
19698						rowinfo[val.r] = rowobj;
19699						rowobj.hpt = val.hpt; rowobj.hpx = pt2px(val.hpt);
19700					}
19701				} break;
19702				case 0x0026 /* LeftMargin */:
19703				case 0x0027 /* RightMargin */:
19704				case 0x0028 /* TopMargin */:
19705				case 0x0029 /* BottomMargin */:
19706					if(!out['!margins']) default_margins(out['!margins'] = {});
19707					out['!margins'][({0x26: "left", 0x27:"right", 0x28:"top", 0x29:"bottom"})[RecordType]] = val;
19708					break;
19709				case 0x00a1 /* Setup */: // TODO
19710					if(!out['!margins']) default_margins(out['!margins'] = {});
19711					out['!margins'].header = val.header;
19712					out['!margins'].footer = val.footer;
19713					break;
19714				case 0x023e /* Window2 */: // TODO
19715					// $FlowIgnore
19716					if(val.RTL) Workbook.Views[0].RTL = true;
19717					break;
19718				case 0x0092 /* Palette */: palette = val; break;
19719				case 0x0896 /* Theme */: themes = val; break;
19720				case 0x008c /* Country */: country = val; break;
19721				case 0x01ba /* CodeName */: {
19722					/*:: if(!Workbook.WBProps) Workbook.WBProps = {}; */
19723					if(!cur_sheet) Workbook.WBProps.CodeName = val || "ThisWorkbook";
19724					else wsprops.CodeName = val || wsprops.name;
19725				} break;
19726			}
19727		} else {
19728			if(!R) console.error("Missing Info for XLS Record 0x" + RecordType.toString(16));
19729			blob.l += length;
19730		}
19731	}
19732	wb.SheetNames=keys(Directory).sort(function(a,b) { return Number(a) - Number(b); }).map(function(x){return Directory[x].name;});
19733	if(!options.bookSheets) wb.Sheets=Sheets;
19734	if(!wb.SheetNames.length && Preamble["!ref"]) {
19735		wb.SheetNames.push("Sheet1");
19736		/*jshint -W069 */
19737		if(wb.Sheets) wb.Sheets["Sheet1"] = Preamble;
19738		/*jshint +W069 */
19739	} else wb.Preamble=Preamble;
19740	if(wb.Sheets) FilterDatabases.forEach(function(r,i) { wb.Sheets[wb.SheetNames[i]]['!autofilter'] = r; });
19741	wb.Strings = sst;
19742	wb.SSF = dup(table_fmt);
19743	if(opts.enc) wb.Encryption = opts.enc;
19744	if(themes) wb.Themes = themes;
19745	wb.Metadata = {};
19746	if(country !== undefined) wb.Metadata.Country = country;
19747	if(supbooks.names.length > 0) Workbook.Names = supbooks.names;
19748	wb.Workbook = Workbook;
19749	return wb;
19750}
19751
19752/* TODO: split props*/
19753var PSCLSID = {
19754	SI: "e0859ff2f94f6810ab9108002b27b3d9",
19755	DSI: "02d5cdd59c2e1b10939708002b2cf9ae",
19756	UDI: "05d5cdd59c2e1b10939708002b2cf9ae"
19757};
19758function parse_xls_props(cfb/*:CFBContainer*/, props, o) {
19759	/* [MS-OSHARED] 2.3.3.2.2 Document Summary Information Property Set */
19760	var DSI = CFB.find(cfb, '/!DocumentSummaryInformation');
19761	if(DSI && DSI.size > 0) try {
19762		var DocSummary = parse_PropertySetStream(DSI, DocSummaryPIDDSI, PSCLSID.DSI);
19763		for(var d in DocSummary) props[d] = DocSummary[d];
19764	} catch(e) {if(o.WTF) throw e;/* empty */}
19765
19766	/* [MS-OSHARED] 2.3.3.2.1 Summary Information Property Set*/
19767	var SI = CFB.find(cfb, '/!SummaryInformation');
19768	if(SI && SI.size > 0) try {
19769		var Summary = parse_PropertySetStream(SI, SummaryPIDSI, PSCLSID.SI);
19770		for(var s in Summary) if(props[s] == null) props[s] = Summary[s];
19771	} catch(e) {if(o.WTF) throw e;/* empty */}
19772
19773	if(props.HeadingPairs && props.TitlesOfParts) {
19774		load_props_pairs(props.HeadingPairs, props.TitlesOfParts, props, o);
19775		delete props.HeadingPairs; delete props.TitlesOfParts;
19776	}
19777}
19778function write_xls_props(wb/*:Workbook*/, cfb/*:CFBContainer*/) {
19779	var DSEntries = [], SEntries = [], CEntries = [];
19780	var i = 0, Keys;
19781	var DocSummaryRE/*:{[key:string]:string}*/ = evert_key(DocSummaryPIDDSI, "n");
19782	var SummaryRE/*:{[key:string]:string}*/ = evert_key(SummaryPIDSI, "n");
19783	if(wb.Props) {
19784		Keys = keys(wb.Props);
19785		// $FlowIgnore
19786		for(i = 0; i < Keys.length; ++i) (Object.prototype.hasOwnProperty.call(DocSummaryRE, Keys[i]) ? DSEntries : Object.prototype.hasOwnProperty.call(SummaryRE, Keys[i]) ? SEntries : CEntries).push([Keys[i], wb.Props[Keys[i]]]);
19787	}
19788	if(wb.Custprops) {
19789		Keys = keys(wb.Custprops);
19790		// $FlowIgnore
19791		for(i = 0; i < Keys.length; ++i) if(!Object.prototype.hasOwnProperty.call((wb.Props||{}), Keys[i])) (Object.prototype.hasOwnProperty.call(DocSummaryRE, Keys[i]) ? DSEntries : Object.prototype.hasOwnProperty.call(SummaryRE, Keys[i]) ? SEntries : CEntries).push([Keys[i], wb.Custprops[Keys[i]]]);
19792	}
19793	var CEntries2 = [];
19794	for(i = 0; i < CEntries.length; ++i) {
19795		if(XLSPSSkip.indexOf(CEntries[i][0]) > -1 || PseudoPropsPairs.indexOf(CEntries[i][0]) > -1) continue;
19796		if(CEntries[i][1] == null) continue;
19797		CEntries2.push(CEntries[i]);
19798	}
19799	if(SEntries.length) CFB.utils.cfb_add(cfb, "/\u0005SummaryInformation", write_PropertySetStream(SEntries, PSCLSID.SI, SummaryRE, SummaryPIDSI));
19800	if(DSEntries.length || CEntries2.length) CFB.utils.cfb_add(cfb, "/\u0005DocumentSummaryInformation", write_PropertySetStream(DSEntries, PSCLSID.DSI, DocSummaryRE, DocSummaryPIDDSI, CEntries2.length ? CEntries2 : null, PSCLSID.UDI));
19801}
19802
19803function parse_xlscfb(cfb/*:any*/, options/*:?ParseOpts*/)/*:Workbook*/ {
19804if(!options) options = {};
19805fix_read_opts(options);
19806reset_cp();
19807if(options.codepage) set_ansi(options.codepage);
19808var CompObj/*:?CFBEntry*/, WB/*:?any*/;
19809if(cfb.FullPaths) {
19810	if(CFB.find(cfb, '/encryption')) throw new Error("File is password-protected");
19811	CompObj = CFB.find(cfb, '!CompObj');
19812	WB = CFB.find(cfb, '/Workbook') || CFB.find(cfb, '/Book');
19813} else {
19814	switch(options.type) {
19815		case 'base64': cfb = s2a(Base64_decode(cfb)); break;
19816		case 'binary': cfb = s2a(cfb); break;
19817		case 'buffer': break;
19818		case 'array': if(!Array.isArray(cfb)) cfb = Array.prototype.slice.call(cfb); break;
19819	}
19820	prep_blob(cfb, 0);
19821	WB = ({content: cfb}/*:any*/);
19822}
19823var WorkbookP = XLSX.utils.book_new();
19824
19825var _data/*:?any*/;
19826if(CompObj) /*::CompObjP = */parse_compobj(CompObj);
19827if(options.bookProps && !options.bookSheets) WorkbookP = ({}/*:any*/);
19828else/*:: if(cfb instanceof CFBContainer) */ {
19829	var T = has_buf ? 'buffer' : 'array';
19830	if(WB && WB.content) WorkbookP = parse_workbook(WB.content, options);
19831	/* Quattro Pro 7-8 */
19832	else if((_data=CFB.find(cfb, 'PerfectOffice_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options));
19833	/* Quattro Pro 9 */
19834	else if((_data=CFB.find(cfb, 'NativeContent_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options));
19835	/* Works 4 for Mac */
19836	else if((_data=CFB.find(cfb, 'MN0')) && _data.content) throw new Error("Unsupported Works 4 for Mac file");
19837	else throw new Error("Cannot find Workbook stream");
19838	if(options.bookVBA && cfb.FullPaths && CFB.find(cfb, '/_VBA_PROJECT_CUR/VBA/dir')) WorkbookP.vbaraw = make_vba_xls(cfb);
19839}
19840
19841var props = {};
19842if(cfb.FullPaths) parse_xls_props(/*::((*/cfb/*:: :any):CFBContainer)*/, props, options);
19843
19844WorkbookP.Props = WorkbookP.Custprops = props; /* TODO: split up properties */
19845if(options.bookFiles) WorkbookP.cfb = cfb;
19846/*WorkbookP.CompObjP = CompObjP; // TODO: storage? */
19847return WorkbookP;
19848}
19849
19850
19851function write_xlscfb(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:CFBContainer*/ {
19852	var o = opts || {};
19853	var cfb = CFB.utils.cfb_new({root:"R"});
19854	var wbpath = "/Workbook";
19855	switch(o.bookType || "xls") {
19856		case "xls": o.bookType = "biff8";
19857		/* falls through */
19858		case "xla": if(!o.bookType) o.bookType = "xla";
19859		/* falls through */
19860		case "biff8": wbpath = "/Workbook"; o.biff = 8; break;
19861		case "biff5": wbpath = "/Book"; o.biff = 5; break;
19862		default: throw new Error("invalid type " + o.bookType + " for XLS CFB");
19863	}
19864	CFB.utils.cfb_add(cfb, wbpath, write_biff_buf(wb, o));
19865	if(o.biff == 8 && (wb.Props || wb.Custprops)) write_xls_props(wb, cfb);
19866	// TODO: SI, DSI, CO
19867	if(o.biff == 8 && wb.vbaraw) fill_vba_xls(cfb, CFB.read(wb.vbaraw, {type: typeof wb.vbaraw == "string" ? "binary" : "buffer"}));
19868	return cfb;
19869}
19870/* [MS-XLSB] 2.3 Record Enumeration */
19871var XLSBRecordEnum = {
19872	/*::[*/0x0000/*::]*/: { /* n:"BrtRowHdr", */ f:parse_BrtRowHdr },
19873	/*::[*/0x0001/*::]*/: { /* n:"BrtCellBlank", */ f:parse_BrtCellBlank },
19874	/*::[*/0x0002/*::]*/: { /* n:"BrtCellRk", */ f:parse_BrtCellRk },
19875	/*::[*/0x0003/*::]*/: { /* n:"BrtCellError", */ f:parse_BrtCellError },
19876	/*::[*/0x0004/*::]*/: { /* n:"BrtCellBool", */ f:parse_BrtCellBool },
19877	/*::[*/0x0005/*::]*/: { /* n:"BrtCellReal", */ f:parse_BrtCellReal },
19878	/*::[*/0x0006/*::]*/: { /* n:"BrtCellSt", */ f:parse_BrtCellSt },
19879	/*::[*/0x0007/*::]*/: { /* n:"BrtCellIsst", */ f:parse_BrtCellIsst },
19880	/*::[*/0x0008/*::]*/: { /* n:"BrtFmlaString", */ f:parse_BrtFmlaString },
19881	/*::[*/0x0009/*::]*/: { /* n:"BrtFmlaNum", */ f:parse_BrtFmlaNum },
19882	/*::[*/0x000A/*::]*/: { /* n:"BrtFmlaBool", */ f:parse_BrtFmlaBool },
19883	/*::[*/0x000B/*::]*/: { /* n:"BrtFmlaError", */ f:parse_BrtFmlaError },
19884	/*::[*/0x000C/*::]*/: { /* n:"BrtShortBlank", */ f:parse_BrtShortBlank },
19885	/*::[*/0x000D/*::]*/: { /* n:"BrtShortRk", */ f:parse_BrtShortRk },
19886	/*::[*/0x000E/*::]*/: { /* n:"BrtShortError", */ f:parse_BrtShortError },
19887	/*::[*/0x000F/*::]*/: { /* n:"BrtShortBool", */ f:parse_BrtShortBool },
19888	/*::[*/0x0010/*::]*/: { /* n:"BrtShortReal", */ f:parse_BrtShortReal },
19889	/*::[*/0x0011/*::]*/: { /* n:"BrtShortSt", */ f:parse_BrtShortSt },
19890	/*::[*/0x0012/*::]*/: { /* n:"BrtShortIsst", */ f:parse_BrtShortIsst },
19891	/*::[*/0x0013/*::]*/: { /* n:"BrtSSTItem", */ f:parse_RichStr },
19892	/*::[*/0x0014/*::]*/: { /* n:"BrtPCDIMissing" */ },
19893	/*::[*/0x0015/*::]*/: { /* n:"BrtPCDINumber" */ },
19894	/*::[*/0x0016/*::]*/: { /* n:"BrtPCDIBoolean" */ },
19895	/*::[*/0x0017/*::]*/: { /* n:"BrtPCDIError" */ },
19896	/*::[*/0x0018/*::]*/: { /* n:"BrtPCDIString" */ },
19897	/*::[*/0x0019/*::]*/: { /* n:"BrtPCDIDatetime" */ },
19898	/*::[*/0x001A/*::]*/: { /* n:"BrtPCDIIndex" */ },
19899	/*::[*/0x001B/*::]*/: { /* n:"BrtPCDIAMissing" */ },
19900	/*::[*/0x001C/*::]*/: { /* n:"BrtPCDIANumber" */ },
19901	/*::[*/0x001D/*::]*/: { /* n:"BrtPCDIABoolean" */ },
19902	/*::[*/0x001E/*::]*/: { /* n:"BrtPCDIAError" */ },
19903	/*::[*/0x001F/*::]*/: { /* n:"BrtPCDIAString" */ },
19904	/*::[*/0x0020/*::]*/: { /* n:"BrtPCDIADatetime" */ },
19905	/*::[*/0x0021/*::]*/: { /* n:"BrtPCRRecord" */ },
19906	/*::[*/0x0022/*::]*/: { /* n:"BrtPCRRecordDt" */ },
19907	/*::[*/0x0023/*::]*/: { /* n:"BrtFRTBegin", */ T:1 },
19908	/*::[*/0x0024/*::]*/: { /* n:"BrtFRTEnd", */ T:-1 },
19909	/*::[*/0x0025/*::]*/: { /* n:"BrtACBegin", */ T:1 },
19910	/*::[*/0x0026/*::]*/: { /* n:"BrtACEnd", */ T:-1 },
19911	/*::[*/0x0027/*::]*/: { /* n:"BrtName", */ f:parse_BrtName },
19912	/*::[*/0x0028/*::]*/: { /* n:"BrtIndexRowBlock" */ },
19913	/*::[*/0x002A/*::]*/: { /* n:"BrtIndexBlock" */ },
19914	/*::[*/0x002B/*::]*/: { /* n:"BrtFont", */ f:parse_BrtFont },
19915	/*::[*/0x002C/*::]*/: { /* n:"BrtFmt", */ f:parse_BrtFmt },
19916	/*::[*/0x002D/*::]*/: { /* n:"BrtFill", */ f:parse_BrtFill },
19917	/*::[*/0x002E/*::]*/: { /* n:"BrtBorder", */ f:parse_BrtBorder },
19918	/*::[*/0x002F/*::]*/: { /* n:"BrtXF", */ f:parse_BrtXF },
19919	/*::[*/0x0030/*::]*/: { /* n:"BrtStyle" */ },
19920	/*::[*/0x0031/*::]*/: { /* n:"BrtCellMeta", */ f:parse_Int32LE },
19921	/*::[*/0x0032/*::]*/: { /* n:"BrtValueMeta" */ },
19922	/*::[*/0x0033/*::]*/: { /* n:"BrtMdb" */ f:parse_BrtMdb },
19923	/*::[*/0x0034/*::]*/: { /* n:"BrtBeginFmd", */ T:1 },
19924	/*::[*/0x0035/*::]*/: { /* n:"BrtEndFmd", */ T:-1 },
19925	/*::[*/0x0036/*::]*/: { /* n:"BrtBeginMdx", */ T:1 },
19926	/*::[*/0x0037/*::]*/: { /* n:"BrtEndMdx", */ T:-1 },
19927	/*::[*/0x0038/*::]*/: { /* n:"BrtBeginMdxTuple", */ T:1 },
19928	/*::[*/0x0039/*::]*/: { /* n:"BrtEndMdxTuple", */ T:-1 },
19929	/*::[*/0x003A/*::]*/: { /* n:"BrtMdxMbrIstr" */ },
19930	/*::[*/0x003B/*::]*/: { /* n:"BrtStr" */ },
19931	/*::[*/0x003C/*::]*/: { /* n:"BrtColInfo", */ f:parse_ColInfo },
19932	/*::[*/0x003E/*::]*/: { /* n:"BrtCellRString", */ f:parse_BrtCellRString },
19933	/*::[*/0x003F/*::]*/: { /* n:"BrtCalcChainItem$", */ f:parse_BrtCalcChainItem$ },
19934	/*::[*/0x0040/*::]*/: { /* n:"BrtDVal", */ f:parse_BrtDVal },
19935	/*::[*/0x0041/*::]*/: { /* n:"BrtSxvcellNum" */ },
19936	/*::[*/0x0042/*::]*/: { /* n:"BrtSxvcellStr" */ },
19937	/*::[*/0x0043/*::]*/: { /* n:"BrtSxvcellBool" */ },
19938	/*::[*/0x0044/*::]*/: { /* n:"BrtSxvcellErr" */ },
19939	/*::[*/0x0045/*::]*/: { /* n:"BrtSxvcellDate" */ },
19940	/*::[*/0x0046/*::]*/: { /* n:"BrtSxvcellNil" */ },
19941	/*::[*/0x0080/*::]*/: { /* n:"BrtFileVersion" */ },
19942	/*::[*/0x0081/*::]*/: { /* n:"BrtBeginSheet", */ T:1 },
19943	/*::[*/0x0082/*::]*/: { /* n:"BrtEndSheet", */ T:-1 },
19944	/*::[*/0x0083/*::]*/: { /* n:"BrtBeginBook", */ T:1, f:parsenoop, p:0 },
19945	/*::[*/0x0084/*::]*/: { /* n:"BrtEndBook", */ T:-1 },
19946	/*::[*/0x0085/*::]*/: { /* n:"BrtBeginWsViews", */ T:1 },
19947	/*::[*/0x0086/*::]*/: { /* n:"BrtEndWsViews", */ T:-1 },
19948	/*::[*/0x0087/*::]*/: { /* n:"BrtBeginBookViews", */ T:1 },
19949	/*::[*/0x0088/*::]*/: { /* n:"BrtEndBookViews", */ T:-1 },
19950	/*::[*/0x0089/*::]*/: { /* n:"BrtBeginWsView", */ T:1, f:parse_BrtBeginWsView },
19951	/*::[*/0x008A/*::]*/: { /* n:"BrtEndWsView", */ T:-1 },
19952	/*::[*/0x008B/*::]*/: { /* n:"BrtBeginCsViews", */ T:1 },
19953	/*::[*/0x008C/*::]*/: { /* n:"BrtEndCsViews", */ T:-1 },
19954	/*::[*/0x008D/*::]*/: { /* n:"BrtBeginCsView", */ T:1 },
19955	/*::[*/0x008E/*::]*/: { /* n:"BrtEndCsView", */ T:-1 },
19956	/*::[*/0x008F/*::]*/: { /* n:"BrtBeginBundleShs", */ T:1 },
19957	/*::[*/0x0090/*::]*/: { /* n:"BrtEndBundleShs", */ T:-1 },
19958	/*::[*/0x0091/*::]*/: { /* n:"BrtBeginSheetData", */ T:1 },
19959	/*::[*/0x0092/*::]*/: { /* n:"BrtEndSheetData", */ T:-1 },
19960	/*::[*/0x0093/*::]*/: { /* n:"BrtWsProp", */ f:parse_BrtWsProp },
19961	/*::[*/0x0094/*::]*/: { /* n:"BrtWsDim", */ f:parse_BrtWsDim, p:16 },
19962	/*::[*/0x0097/*::]*/: { /* n:"BrtPane", */ f:parse_BrtPane },
19963	/*::[*/0x0098/*::]*/: { /* n:"BrtSel" */ },
19964	/*::[*/0x0099/*::]*/: { /* n:"BrtWbProp", */ f:parse_BrtWbProp },
19965	/*::[*/0x009A/*::]*/: { /* n:"BrtWbFactoid" */ },
19966	/*::[*/0x009B/*::]*/: { /* n:"BrtFileRecover" */ },
19967	/*::[*/0x009C/*::]*/: { /* n:"BrtBundleSh", */ f:parse_BrtBundleSh },
19968	/*::[*/0x009D/*::]*/: { /* n:"BrtCalcProp" */ },
19969	/*::[*/0x009E/*::]*/: { /* n:"BrtBookView" */ },
19970	/*::[*/0x009F/*::]*/: { /* n:"BrtBeginSst", */ T:1, f:parse_BrtBeginSst },
19971	/*::[*/0x00A0/*::]*/: { /* n:"BrtEndSst", */ T:-1 },
19972	/*::[*/0x00A1/*::]*/: { /* n:"BrtBeginAFilter", */ T:1, f:parse_UncheckedRfX },
19973	/*::[*/0x00A2/*::]*/: { /* n:"BrtEndAFilter", */ T:-1 },
19974	/*::[*/0x00A3/*::]*/: { /* n:"BrtBeginFilterColumn", */ T:1 },
19975	/*::[*/0x00A4/*::]*/: { /* n:"BrtEndFilterColumn", */ T:-1 },
19976	/*::[*/0x00A5/*::]*/: { /* n:"BrtBeginFilters", */ T:1 },
19977	/*::[*/0x00A6/*::]*/: { /* n:"BrtEndFilters", */ T:-1 },
19978	/*::[*/0x00A7/*::]*/: { /* n:"BrtFilter" */ },
19979	/*::[*/0x00A8/*::]*/: { /* n:"BrtColorFilter" */ },
19980	/*::[*/0x00A9/*::]*/: { /* n:"BrtIconFilter" */ },
19981	/*::[*/0x00AA/*::]*/: { /* n:"BrtTop10Filter" */ },
19982	/*::[*/0x00AB/*::]*/: { /* n:"BrtDynamicFilter" */ },
19983	/*::[*/0x00AC/*::]*/: { /* n:"BrtBeginCustomFilters", */ T:1 },
19984	/*::[*/0x00AD/*::]*/: { /* n:"BrtEndCustomFilters", */ T:-1 },
19985	/*::[*/0x00AE/*::]*/: { /* n:"BrtCustomFilter" */ },
19986	/*::[*/0x00AF/*::]*/: { /* n:"BrtAFilterDateGroupItem" */ },
19987	/*::[*/0x00B0/*::]*/: { /* n:"BrtMergeCell", */ f:parse_BrtMergeCell },
19988	/*::[*/0x00B1/*::]*/: { /* n:"BrtBeginMergeCells", */ T:1 },
19989	/*::[*/0x00B2/*::]*/: { /* n:"BrtEndMergeCells", */ T:-1 },
19990	/*::[*/0x00B3/*::]*/: { /* n:"BrtBeginPivotCacheDef", */ T:1 },
19991	/*::[*/0x00B4/*::]*/: { /* n:"BrtEndPivotCacheDef", */ T:-1 },
19992	/*::[*/0x00B5/*::]*/: { /* n:"BrtBeginPCDFields", */ T:1 },
19993	/*::[*/0x00B6/*::]*/: { /* n:"BrtEndPCDFields", */ T:-1 },
19994	/*::[*/0x00B7/*::]*/: { /* n:"BrtBeginPCDField", */ T:1 },
19995	/*::[*/0x00B8/*::]*/: { /* n:"BrtEndPCDField", */ T:-1 },
19996	/*::[*/0x00B9/*::]*/: { /* n:"BrtBeginPCDSource", */ T:1 },
19997	/*::[*/0x00BA/*::]*/: { /* n:"BrtEndPCDSource", */ T:-1 },
19998	/*::[*/0x00BB/*::]*/: { /* n:"BrtBeginPCDSRange", */ T:1 },
19999	/*::[*/0x00BC/*::]*/: { /* n:"BrtEndPCDSRange", */ T:-1 },
20000	/*::[*/0x00BD/*::]*/: { /* n:"BrtBeginPCDFAtbl", */ T:1 },
20001	/*::[*/0x00BE/*::]*/: { /* n:"BrtEndPCDFAtbl", */ T:-1 },
20002	/*::[*/0x00BF/*::]*/: { /* n:"BrtBeginPCDIRun", */ T:1 },
20003	/*::[*/0x00C0/*::]*/: { /* n:"BrtEndPCDIRun", */ T:-1 },
20004	/*::[*/0x00C1/*::]*/: { /* n:"BrtBeginPivotCacheRecords", */ T:1 },
20005	/*::[*/0x00C2/*::]*/: { /* n:"BrtEndPivotCacheRecords", */ T:-1 },
20006	/*::[*/0x00C3/*::]*/: { /* n:"BrtBeginPCDHierarchies", */ T:1 },
20007	/*::[*/0x00C4/*::]*/: { /* n:"BrtEndPCDHierarchies", */ T:-1 },
20008	/*::[*/0x00C5/*::]*/: { /* n:"BrtBeginPCDHierarchy", */ T:1 },
20009	/*::[*/0x00C6/*::]*/: { /* n:"BrtEndPCDHierarchy", */ T:-1 },
20010	/*::[*/0x00C7/*::]*/: { /* n:"BrtBeginPCDHFieldsUsage", */ T:1 },
20011	/*::[*/0x00C8/*::]*/: { /* n:"BrtEndPCDHFieldsUsage", */ T:-1 },
20012	/*::[*/0x00C9/*::]*/: { /* n:"BrtBeginExtConnection", */ T:1 },
20013	/*::[*/0x00CA/*::]*/: { /* n:"BrtEndExtConnection", */ T:-1 },
20014	/*::[*/0x00CB/*::]*/: { /* n:"BrtBeginECDbProps", */ T:1 },
20015	/*::[*/0x00CC/*::]*/: { /* n:"BrtEndECDbProps", */ T:-1 },
20016	/*::[*/0x00CD/*::]*/: { /* n:"BrtBeginECOlapProps", */ T:1 },
20017	/*::[*/0x00CE/*::]*/: { /* n:"BrtEndECOlapProps", */ T:-1 },
20018	/*::[*/0x00CF/*::]*/: { /* n:"BrtBeginPCDSConsol", */ T:1 },
20019	/*::[*/0x00D0/*::]*/: { /* n:"BrtEndPCDSConsol", */ T:-1 },
20020	/*::[*/0x00D1/*::]*/: { /* n:"BrtBeginPCDSCPages", */ T:1 },
20021	/*::[*/0x00D2/*::]*/: { /* n:"BrtEndPCDSCPages", */ T:-1 },
20022	/*::[*/0x00D3/*::]*/: { /* n:"BrtBeginPCDSCPage", */ T:1 },
20023	/*::[*/0x00D4/*::]*/: { /* n:"BrtEndPCDSCPage", */ T:-1 },
20024	/*::[*/0x00D5/*::]*/: { /* n:"BrtBeginPCDSCPItem", */ T:1 },
20025	/*::[*/0x00D6/*::]*/: { /* n:"BrtEndPCDSCPItem", */ T:-1 },
20026	/*::[*/0x00D7/*::]*/: { /* n:"BrtBeginPCDSCSets", */ T:1 },
20027	/*::[*/0x00D8/*::]*/: { /* n:"BrtEndPCDSCSets", */ T:-1 },
20028	/*::[*/0x00D9/*::]*/: { /* n:"BrtBeginPCDSCSet", */ T:1 },
20029	/*::[*/0x00DA/*::]*/: { /* n:"BrtEndPCDSCSet", */ T:-1 },
20030	/*::[*/0x00DB/*::]*/: { /* n:"BrtBeginPCDFGroup", */ T:1 },
20031	/*::[*/0x00DC/*::]*/: { /* n:"BrtEndPCDFGroup", */ T:-1 },
20032	/*::[*/0x00DD/*::]*/: { /* n:"BrtBeginPCDFGItems", */ T:1 },
20033	/*::[*/0x00DE/*::]*/: { /* n:"BrtEndPCDFGItems", */ T:-1 },
20034	/*::[*/0x00DF/*::]*/: { /* n:"BrtBeginPCDFGRange", */ T:1 },
20035	/*::[*/0x00E0/*::]*/: { /* n:"BrtEndPCDFGRange", */ T:-1 },
20036	/*::[*/0x00E1/*::]*/: { /* n:"BrtBeginPCDFGDiscrete", */ T:1 },
20037	/*::[*/0x00E2/*::]*/: { /* n:"BrtEndPCDFGDiscrete", */ T:-1 },
20038	/*::[*/0x00E3/*::]*/: { /* n:"BrtBeginPCDSDTupleCache", */ T:1 },
20039	/*::[*/0x00E4/*::]*/: { /* n:"BrtEndPCDSDTupleCache", */ T:-1 },
20040	/*::[*/0x00E5/*::]*/: { /* n:"BrtBeginPCDSDTCEntries", */ T:1 },
20041	/*::[*/0x00E6/*::]*/: { /* n:"BrtEndPCDSDTCEntries", */ T:-1 },
20042	/*::[*/0x00E7/*::]*/: { /* n:"BrtBeginPCDSDTCEMembers", */ T:1 },
20043	/*::[*/0x00E8/*::]*/: { /* n:"BrtEndPCDSDTCEMembers", */ T:-1 },
20044	/*::[*/0x00E9/*::]*/: { /* n:"BrtBeginPCDSDTCEMember", */ T:1 },
20045	/*::[*/0x00EA/*::]*/: { /* n:"BrtEndPCDSDTCEMember", */ T:-1 },
20046	/*::[*/0x00EB/*::]*/: { /* n:"BrtBeginPCDSDTCQueries", */ T:1 },
20047	/*::[*/0x00EC/*::]*/: { /* n:"BrtEndPCDSDTCQueries", */ T:-1 },
20048	/*::[*/0x00ED/*::]*/: { /* n:"BrtBeginPCDSDTCQuery", */ T:1 },
20049	/*::[*/0x00EE/*::]*/: { /* n:"BrtEndPCDSDTCQuery", */ T:-1 },
20050	/*::[*/0x00EF/*::]*/: { /* n:"BrtBeginPCDSDTCSets", */ T:1 },
20051	/*::[*/0x00F0/*::]*/: { /* n:"BrtEndPCDSDTCSets", */ T:-1 },
20052	/*::[*/0x00F1/*::]*/: { /* n:"BrtBeginPCDSDTCSet", */ T:1 },
20053	/*::[*/0x00F2/*::]*/: { /* n:"BrtEndPCDSDTCSet", */ T:-1 },
20054	/*::[*/0x00F3/*::]*/: { /* n:"BrtBeginPCDCalcItems", */ T:1 },
20055	/*::[*/0x00F4/*::]*/: { /* n:"BrtEndPCDCalcItems", */ T:-1 },
20056	/*::[*/0x00F5/*::]*/: { /* n:"BrtBeginPCDCalcItem", */ T:1 },
20057	/*::[*/0x00F6/*::]*/: { /* n:"BrtEndPCDCalcItem", */ T:-1 },
20058	/*::[*/0x00F7/*::]*/: { /* n:"BrtBeginPRule", */ T:1 },
20059	/*::[*/0x00F8/*::]*/: { /* n:"BrtEndPRule", */ T:-1 },
20060	/*::[*/0x00F9/*::]*/: { /* n:"BrtBeginPRFilters", */ T:1 },
20061	/*::[*/0x00FA/*::]*/: { /* n:"BrtEndPRFilters", */ T:-1 },
20062	/*::[*/0x00FB/*::]*/: { /* n:"BrtBeginPRFilter", */ T:1 },
20063	/*::[*/0x00FC/*::]*/: { /* n:"BrtEndPRFilter", */ T:-1 },
20064	/*::[*/0x00FD/*::]*/: { /* n:"BrtBeginPNames", */ T:1 },
20065	/*::[*/0x00FE/*::]*/: { /* n:"BrtEndPNames", */ T:-1 },
20066	/*::[*/0x00FF/*::]*/: { /* n:"BrtBeginPName", */ T:1 },
20067	/*::[*/0x0100/*::]*/: { /* n:"BrtEndPName", */ T:-1 },
20068	/*::[*/0x0101/*::]*/: { /* n:"BrtBeginPNPairs", */ T:1 },
20069	/*::[*/0x0102/*::]*/: { /* n:"BrtEndPNPairs", */ T:-1 },
20070	/*::[*/0x0103/*::]*/: { /* n:"BrtBeginPNPair", */ T:1 },
20071	/*::[*/0x0104/*::]*/: { /* n:"BrtEndPNPair", */ T:-1 },
20072	/*::[*/0x0105/*::]*/: { /* n:"BrtBeginECWebProps", */ T:1 },
20073	/*::[*/0x0106/*::]*/: { /* n:"BrtEndECWebProps", */ T:-1 },
20074	/*::[*/0x0107/*::]*/: { /* n:"BrtBeginEcWpTables", */ T:1 },
20075	/*::[*/0x0108/*::]*/: { /* n:"BrtEndECWPTables", */ T:-1 },
20076	/*::[*/0x0109/*::]*/: { /* n:"BrtBeginECParams", */ T:1 },
20077	/*::[*/0x010A/*::]*/: { /* n:"BrtEndECParams", */ T:-1 },
20078	/*::[*/0x010B/*::]*/: { /* n:"BrtBeginECParam", */ T:1 },
20079	/*::[*/0x010C/*::]*/: { /* n:"BrtEndECParam", */ T:-1 },
20080	/*::[*/0x010D/*::]*/: { /* n:"BrtBeginPCDKPIs", */ T:1 },
20081	/*::[*/0x010E/*::]*/: { /* n:"BrtEndPCDKPIs", */ T:-1 },
20082	/*::[*/0x010F/*::]*/: { /* n:"BrtBeginPCDKPI", */ T:1 },
20083	/*::[*/0x0110/*::]*/: { /* n:"BrtEndPCDKPI", */ T:-1 },
20084	/*::[*/0x0111/*::]*/: { /* n:"BrtBeginDims", */ T:1 },
20085	/*::[*/0x0112/*::]*/: { /* n:"BrtEndDims", */ T:-1 },
20086	/*::[*/0x0113/*::]*/: { /* n:"BrtBeginDim", */ T:1 },
20087	/*::[*/0x0114/*::]*/: { /* n:"BrtEndDim", */ T:-1 },
20088	/*::[*/0x0115/*::]*/: { /* n:"BrtIndexPartEnd" */ },
20089	/*::[*/0x0116/*::]*/: { /* n:"BrtBeginStyleSheet", */ T:1 },
20090	/*::[*/0x0117/*::]*/: { /* n:"BrtEndStyleSheet", */ T:-1 },
20091	/*::[*/0x0118/*::]*/: { /* n:"BrtBeginSXView", */ T:1 },
20092	/*::[*/0x0119/*::]*/: { /* n:"BrtEndSXVI", */ T:-1 },
20093	/*::[*/0x011A/*::]*/: { /* n:"BrtBeginSXVI", */ T:1 },
20094	/*::[*/0x011B/*::]*/: { /* n:"BrtBeginSXVIs", */ T:1 },
20095	/*::[*/0x011C/*::]*/: { /* n:"BrtEndSXVIs", */ T:-1 },
20096	/*::[*/0x011D/*::]*/: { /* n:"BrtBeginSXVD", */ T:1 },
20097	/*::[*/0x011E/*::]*/: { /* n:"BrtEndSXVD", */ T:-1 },
20098	/*::[*/0x011F/*::]*/: { /* n:"BrtBeginSXVDs", */ T:1 },
20099	/*::[*/0x0120/*::]*/: { /* n:"BrtEndSXVDs", */ T:-1 },
20100	/*::[*/0x0121/*::]*/: { /* n:"BrtBeginSXPI", */ T:1 },
20101	/*::[*/0x0122/*::]*/: { /* n:"BrtEndSXPI", */ T:-1 },
20102	/*::[*/0x0123/*::]*/: { /* n:"BrtBeginSXPIs", */ T:1 },
20103	/*::[*/0x0124/*::]*/: { /* n:"BrtEndSXPIs", */ T:-1 },
20104	/*::[*/0x0125/*::]*/: { /* n:"BrtBeginSXDI", */ T:1 },
20105	/*::[*/0x0126/*::]*/: { /* n:"BrtEndSXDI", */ T:-1 },
20106	/*::[*/0x0127/*::]*/: { /* n:"BrtBeginSXDIs", */ T:1 },
20107	/*::[*/0x0128/*::]*/: { /* n:"BrtEndSXDIs", */ T:-1 },
20108	/*::[*/0x0129/*::]*/: { /* n:"BrtBeginSXLI", */ T:1 },
20109	/*::[*/0x012A/*::]*/: { /* n:"BrtEndSXLI", */ T:-1 },
20110	/*::[*/0x012B/*::]*/: { /* n:"BrtBeginSXLIRws", */ T:1 },
20111	/*::[*/0x012C/*::]*/: { /* n:"BrtEndSXLIRws", */ T:-1 },
20112	/*::[*/0x012D/*::]*/: { /* n:"BrtBeginSXLICols", */ T:1 },
20113	/*::[*/0x012E/*::]*/: { /* n:"BrtEndSXLICols", */ T:-1 },
20114	/*::[*/0x012F/*::]*/: { /* n:"BrtBeginSXFormat", */ T:1 },
20115	/*::[*/0x0130/*::]*/: { /* n:"BrtEndSXFormat", */ T:-1 },
20116	/*::[*/0x0131/*::]*/: { /* n:"BrtBeginSXFormats", */ T:1 },
20117	/*::[*/0x0132/*::]*/: { /* n:"BrtEndSxFormats", */ T:-1 },
20118	/*::[*/0x0133/*::]*/: { /* n:"BrtBeginSxSelect", */ T:1 },
20119	/*::[*/0x0134/*::]*/: { /* n:"BrtEndSxSelect", */ T:-1 },
20120	/*::[*/0x0135/*::]*/: { /* n:"BrtBeginISXVDRws", */ T:1 },
20121	/*::[*/0x0136/*::]*/: { /* n:"BrtEndISXVDRws", */ T:-1 },
20122	/*::[*/0x0137/*::]*/: { /* n:"BrtBeginISXVDCols", */ T:1 },
20123	/*::[*/0x0138/*::]*/: { /* n:"BrtEndISXVDCols", */ T:-1 },
20124	/*::[*/0x0139/*::]*/: { /* n:"BrtEndSXLocation", */ T:-1 },
20125	/*::[*/0x013A/*::]*/: { /* n:"BrtBeginSXLocation", */ T:1 },
20126	/*::[*/0x013B/*::]*/: { /* n:"BrtEndSXView", */ T:-1 },
20127	/*::[*/0x013C/*::]*/: { /* n:"BrtBeginSXTHs", */ T:1 },
20128	/*::[*/0x013D/*::]*/: { /* n:"BrtEndSXTHs", */ T:-1 },
20129	/*::[*/0x013E/*::]*/: { /* n:"BrtBeginSXTH", */ T:1 },
20130	/*::[*/0x013F/*::]*/: { /* n:"BrtEndSXTH", */ T:-1 },
20131	/*::[*/0x0140/*::]*/: { /* n:"BrtBeginISXTHRws", */ T:1 },
20132	/*::[*/0x0141/*::]*/: { /* n:"BrtEndISXTHRws", */ T:-1 },
20133	/*::[*/0x0142/*::]*/: { /* n:"BrtBeginISXTHCols", */ T:1 },
20134	/*::[*/0x0143/*::]*/: { /* n:"BrtEndISXTHCols", */ T:-1 },
20135	/*::[*/0x0144/*::]*/: { /* n:"BrtBeginSXTDMPS", */ T:1 },
20136	/*::[*/0x0145/*::]*/: { /* n:"BrtEndSXTDMPs", */ T:-1 },
20137	/*::[*/0x0146/*::]*/: { /* n:"BrtBeginSXTDMP", */ T:1 },
20138	/*::[*/0x0147/*::]*/: { /* n:"BrtEndSXTDMP", */ T:-1 },
20139	/*::[*/0x0148/*::]*/: { /* n:"BrtBeginSXTHItems", */ T:1 },
20140	/*::[*/0x0149/*::]*/: { /* n:"BrtEndSXTHItems", */ T:-1 },
20141	/*::[*/0x014A/*::]*/: { /* n:"BrtBeginSXTHItem", */ T:1 },
20142	/*::[*/0x014B/*::]*/: { /* n:"BrtEndSXTHItem", */ T:-1 },
20143	/*::[*/0x014C/*::]*/: { /* n:"BrtBeginMetadata", */ T:1 },
20144	/*::[*/0x014D/*::]*/: { /* n:"BrtEndMetadata", */ T:-1 },
20145	/*::[*/0x014E/*::]*/: { /* n:"BrtBeginEsmdtinfo", */ T:1 },
20146	/*::[*/0x014F/*::]*/: { /* n:"BrtMdtinfo", */ f:parse_BrtMdtinfo },
20147	/*::[*/0x0150/*::]*/: { /* n:"BrtEndEsmdtinfo", */ T:-1 },
20148	/*::[*/0x0151/*::]*/: { /* n:"BrtBeginEsmdb", */ f:parse_BrtBeginEsmdb, T:1 },
20149	/*::[*/0x0152/*::]*/: { /* n:"BrtEndEsmdb", */ T:-1 },
20150	/*::[*/0x0153/*::]*/: { /* n:"BrtBeginEsfmd", */ T:1 },
20151	/*::[*/0x0154/*::]*/: { /* n:"BrtEndEsfmd", */ T:-1 },
20152	/*::[*/0x0155/*::]*/: { /* n:"BrtBeginSingleCells", */ T:1 },
20153	/*::[*/0x0156/*::]*/: { /* n:"BrtEndSingleCells", */ T:-1 },
20154	/*::[*/0x0157/*::]*/: { /* n:"BrtBeginList", */ T:1 },
20155	/*::[*/0x0158/*::]*/: { /* n:"BrtEndList", */ T:-1 },
20156	/*::[*/0x0159/*::]*/: { /* n:"BrtBeginListCols", */ T:1 },
20157	/*::[*/0x015A/*::]*/: { /* n:"BrtEndListCols", */ T:-1 },
20158	/*::[*/0x015B/*::]*/: { /* n:"BrtBeginListCol", */ T:1 },
20159	/*::[*/0x015C/*::]*/: { /* n:"BrtEndListCol", */ T:-1 },
20160	/*::[*/0x015D/*::]*/: { /* n:"BrtBeginListXmlCPr", */ T:1 },
20161	/*::[*/0x015E/*::]*/: { /* n:"BrtEndListXmlCPr", */ T:-1 },
20162	/*::[*/0x015F/*::]*/: { /* n:"BrtListCCFmla" */ },
20163	/*::[*/0x0160/*::]*/: { /* n:"BrtListTrFmla" */ },
20164	/*::[*/0x0161/*::]*/: { /* n:"BrtBeginExternals", */ T:1 },
20165	/*::[*/0x0162/*::]*/: { /* n:"BrtEndExternals", */ T:-1 },
20166	/*::[*/0x0163/*::]*/: { /* n:"BrtSupBookSrc", */ f:parse_RelID},
20167	/*::[*/0x0165/*::]*/: { /* n:"BrtSupSelf" */ },
20168	/*::[*/0x0166/*::]*/: { /* n:"BrtSupSame" */ },
20169	/*::[*/0x0167/*::]*/: { /* n:"BrtSupTabs" */ },
20170	/*::[*/0x0168/*::]*/: { /* n:"BrtBeginSupBook", */ T:1 },
20171	/*::[*/0x0169/*::]*/: { /* n:"BrtPlaceholderName" */ },
20172	/*::[*/0x016A/*::]*/: { /* n:"BrtExternSheet", */ f:parse_ExternSheet },
20173	/*::[*/0x016B/*::]*/: { /* n:"BrtExternTableStart" */ },
20174	/*::[*/0x016C/*::]*/: { /* n:"BrtExternTableEnd" */ },
20175	/*::[*/0x016E/*::]*/: { /* n:"BrtExternRowHdr" */ },
20176	/*::[*/0x016F/*::]*/: { /* n:"BrtExternCellBlank" */ },
20177	/*::[*/0x0170/*::]*/: { /* n:"BrtExternCellReal" */ },
20178	/*::[*/0x0171/*::]*/: { /* n:"BrtExternCellBool" */ },
20179	/*::[*/0x0172/*::]*/: { /* n:"BrtExternCellError" */ },
20180	/*::[*/0x0173/*::]*/: { /* n:"BrtExternCellString" */ },
20181	/*::[*/0x0174/*::]*/: { /* n:"BrtBeginEsmdx", */ T:1 },
20182	/*::[*/0x0175/*::]*/: { /* n:"BrtEndEsmdx", */ T:-1 },
20183	/*::[*/0x0176/*::]*/: { /* n:"BrtBeginMdxSet", */ T:1 },
20184	/*::[*/0x0177/*::]*/: { /* n:"BrtEndMdxSet", */ T:-1 },
20185	/*::[*/0x0178/*::]*/: { /* n:"BrtBeginMdxMbrProp", */ T:1 },
20186	/*::[*/0x0179/*::]*/: { /* n:"BrtEndMdxMbrProp", */ T:-1 },
20187	/*::[*/0x017A/*::]*/: { /* n:"BrtBeginMdxKPI", */ T:1 },
20188	/*::[*/0x017B/*::]*/: { /* n:"BrtEndMdxKPI", */ T:-1 },
20189	/*::[*/0x017C/*::]*/: { /* n:"BrtBeginEsstr", */ T:1 },
20190	/*::[*/0x017D/*::]*/: { /* n:"BrtEndEsstr", */ T:-1 },
20191	/*::[*/0x017E/*::]*/: { /* n:"BrtBeginPRFItem", */ T:1 },
20192	/*::[*/0x017F/*::]*/: { /* n:"BrtEndPRFItem", */ T:-1 },
20193	/*::[*/0x0180/*::]*/: { /* n:"BrtBeginPivotCacheIDs", */ T:1 },
20194	/*::[*/0x0181/*::]*/: { /* n:"BrtEndPivotCacheIDs", */ T:-1 },
20195	/*::[*/0x0182/*::]*/: { /* n:"BrtBeginPivotCacheID", */ T:1 },
20196	/*::[*/0x0183/*::]*/: { /* n:"BrtEndPivotCacheID", */ T:-1 },
20197	/*::[*/0x0184/*::]*/: { /* n:"BrtBeginISXVIs", */ T:1 },
20198	/*::[*/0x0185/*::]*/: { /* n:"BrtEndISXVIs", */ T:-1 },
20199	/*::[*/0x0186/*::]*/: { /* n:"BrtBeginColInfos", */ T:1 },
20200	/*::[*/0x0187/*::]*/: { /* n:"BrtEndColInfos", */ T:-1 },
20201	/*::[*/0x0188/*::]*/: { /* n:"BrtBeginRwBrk", */ T:1 },
20202	/*::[*/0x0189/*::]*/: { /* n:"BrtEndRwBrk", */ T:-1 },
20203	/*::[*/0x018A/*::]*/: { /* n:"BrtBeginColBrk", */ T:1 },
20204	/*::[*/0x018B/*::]*/: { /* n:"BrtEndColBrk", */ T:-1 },
20205	/*::[*/0x018C/*::]*/: { /* n:"BrtBrk" */ },
20206	/*::[*/0x018D/*::]*/: { /* n:"BrtUserBookView" */ },
20207	/*::[*/0x018E/*::]*/: { /* n:"BrtInfo" */ },
20208	/*::[*/0x018F/*::]*/: { /* n:"BrtCUsr" */ },
20209	/*::[*/0x0190/*::]*/: { /* n:"BrtUsr" */ },
20210	/*::[*/0x0191/*::]*/: { /* n:"BrtBeginUsers", */ T:1 },
20211	/*::[*/0x0193/*::]*/: { /* n:"BrtEOF" */ },
20212	/*::[*/0x0194/*::]*/: { /* n:"BrtUCR" */ },
20213	/*::[*/0x0195/*::]*/: { /* n:"BrtRRInsDel" */ },
20214	/*::[*/0x0196/*::]*/: { /* n:"BrtRREndInsDel" */ },
20215	/*::[*/0x0197/*::]*/: { /* n:"BrtRRMove" */ },
20216	/*::[*/0x0198/*::]*/: { /* n:"BrtRREndMove" */ },
20217	/*::[*/0x0199/*::]*/: { /* n:"BrtRRChgCell" */ },
20218	/*::[*/0x019A/*::]*/: { /* n:"BrtRREndChgCell" */ },
20219	/*::[*/0x019B/*::]*/: { /* n:"BrtRRHeader" */ },
20220	/*::[*/0x019C/*::]*/: { /* n:"BrtRRUserView" */ },
20221	/*::[*/0x019D/*::]*/: { /* n:"BrtRRRenSheet" */ },
20222	/*::[*/0x019E/*::]*/: { /* n:"BrtRRInsertSh" */ },
20223	/*::[*/0x019F/*::]*/: { /* n:"BrtRRDefName" */ },
20224	/*::[*/0x01A0/*::]*/: { /* n:"BrtRRNote" */ },
20225	/*::[*/0x01A1/*::]*/: { /* n:"BrtRRConflict" */ },
20226	/*::[*/0x01A2/*::]*/: { /* n:"BrtRRTQSIF" */ },
20227	/*::[*/0x01A3/*::]*/: { /* n:"BrtRRFormat" */ },
20228	/*::[*/0x01A4/*::]*/: { /* n:"BrtRREndFormat" */ },
20229	/*::[*/0x01A5/*::]*/: { /* n:"BrtRRAutoFmt" */ },
20230	/*::[*/0x01A6/*::]*/: { /* n:"BrtBeginUserShViews", */ T:1 },
20231	/*::[*/0x01A7/*::]*/: { /* n:"BrtBeginUserShView", */ T:1 },
20232	/*::[*/0x01A8/*::]*/: { /* n:"BrtEndUserShView", */ T:-1 },
20233	/*::[*/0x01A9/*::]*/: { /* n:"BrtEndUserShViews", */ T:-1 },
20234	/*::[*/0x01AA/*::]*/: { /* n:"BrtArrFmla", */ f:parse_BrtArrFmla },
20235	/*::[*/0x01AB/*::]*/: { /* n:"BrtShrFmla", */ f:parse_BrtShrFmla },
20236	/*::[*/0x01AC/*::]*/: { /* n:"BrtTable" */ },
20237	/*::[*/0x01AD/*::]*/: { /* n:"BrtBeginExtConnections", */ T:1 },
20238	/*::[*/0x01AE/*::]*/: { /* n:"BrtEndExtConnections", */ T:-1 },
20239	/*::[*/0x01AF/*::]*/: { /* n:"BrtBeginPCDCalcMems", */ T:1 },
20240	/*::[*/0x01B0/*::]*/: { /* n:"BrtEndPCDCalcMems", */ T:-1 },
20241	/*::[*/0x01B1/*::]*/: { /* n:"BrtBeginPCDCalcMem", */ T:1 },
20242	/*::[*/0x01B2/*::]*/: { /* n:"BrtEndPCDCalcMem", */ T:-1 },
20243	/*::[*/0x01B3/*::]*/: { /* n:"BrtBeginPCDHGLevels", */ T:1 },
20244	/*::[*/0x01B4/*::]*/: { /* n:"BrtEndPCDHGLevels", */ T:-1 },
20245	/*::[*/0x01B5/*::]*/: { /* n:"BrtBeginPCDHGLevel", */ T:1 },
20246	/*::[*/0x01B6/*::]*/: { /* n:"BrtEndPCDHGLevel", */ T:-1 },
20247	/*::[*/0x01B7/*::]*/: { /* n:"BrtBeginPCDHGLGroups", */ T:1 },
20248	/*::[*/0x01B8/*::]*/: { /* n:"BrtEndPCDHGLGroups", */ T:-1 },
20249	/*::[*/0x01B9/*::]*/: { /* n:"BrtBeginPCDHGLGroup", */ T:1 },
20250	/*::[*/0x01BA/*::]*/: { /* n:"BrtEndPCDHGLGroup", */ T:-1 },
20251	/*::[*/0x01BB/*::]*/: { /* n:"BrtBeginPCDHGLGMembers", */ T:1 },
20252	/*::[*/0x01BC/*::]*/: { /* n:"BrtEndPCDHGLGMembers", */ T:-1 },
20253	/*::[*/0x01BD/*::]*/: { /* n:"BrtBeginPCDHGLGMember", */ T:1 },
20254	/*::[*/0x01BE/*::]*/: { /* n:"BrtEndPCDHGLGMember", */ T:-1 },
20255	/*::[*/0x01BF/*::]*/: { /* n:"BrtBeginQSI", */ T:1 },
20256	/*::[*/0x01C0/*::]*/: { /* n:"BrtEndQSI", */ T:-1 },
20257	/*::[*/0x01C1/*::]*/: { /* n:"BrtBeginQSIR", */ T:1 },
20258	/*::[*/0x01C2/*::]*/: { /* n:"BrtEndQSIR", */ T:-1 },
20259	/*::[*/0x01C3/*::]*/: { /* n:"BrtBeginDeletedNames", */ T:1 },
20260	/*::[*/0x01C4/*::]*/: { /* n:"BrtEndDeletedNames", */ T:-1 },
20261	/*::[*/0x01C5/*::]*/: { /* n:"BrtBeginDeletedName", */ T:1 },
20262	/*::[*/0x01C6/*::]*/: { /* n:"BrtEndDeletedName", */ T:-1 },
20263	/*::[*/0x01C7/*::]*/: { /* n:"BrtBeginQSIFs", */ T:1 },
20264	/*::[*/0x01C8/*::]*/: { /* n:"BrtEndQSIFs", */ T:-1 },
20265	/*::[*/0x01C9/*::]*/: { /* n:"BrtBeginQSIF", */ T:1 },
20266	/*::[*/0x01CA/*::]*/: { /* n:"BrtEndQSIF", */ T:-1 },
20267	/*::[*/0x01CB/*::]*/: { /* n:"BrtBeginAutoSortScope", */ T:1 },
20268	/*::[*/0x01CC/*::]*/: { /* n:"BrtEndAutoSortScope", */ T:-1 },
20269	/*::[*/0x01CD/*::]*/: { /* n:"BrtBeginConditionalFormatting", */ T:1 },
20270	/*::[*/0x01CE/*::]*/: { /* n:"BrtEndConditionalFormatting", */ T:-1 },
20271	/*::[*/0x01CF/*::]*/: { /* n:"BrtBeginCFRule", */ T:1 },
20272	/*::[*/0x01D0/*::]*/: { /* n:"BrtEndCFRule", */ T:-1 },
20273	/*::[*/0x01D1/*::]*/: { /* n:"BrtBeginIconSet", */ T:1 },
20274	/*::[*/0x01D2/*::]*/: { /* n:"BrtEndIconSet", */ T:-1 },
20275	/*::[*/0x01D3/*::]*/: { /* n:"BrtBeginDatabar", */ T:1 },
20276	/*::[*/0x01D4/*::]*/: { /* n:"BrtEndDatabar", */ T:-1 },
20277	/*::[*/0x01D5/*::]*/: { /* n:"BrtBeginColorScale", */ T:1 },
20278	/*::[*/0x01D6/*::]*/: { /* n:"BrtEndColorScale", */ T:-1 },
20279	/*::[*/0x01D7/*::]*/: { /* n:"BrtCFVO" */ },
20280	/*::[*/0x01D8/*::]*/: { /* n:"BrtExternValueMeta" */ },
20281	/*::[*/0x01D9/*::]*/: { /* n:"BrtBeginColorPalette", */ T:1 },
20282	/*::[*/0x01DA/*::]*/: { /* n:"BrtEndColorPalette", */ T:-1 },
20283	/*::[*/0x01DB/*::]*/: { /* n:"BrtIndexedColor" */ },
20284	/*::[*/0x01DC/*::]*/: { /* n:"BrtMargins", */ f:parse_BrtMargins },
20285	/*::[*/0x01DD/*::]*/: { /* n:"BrtPrintOptions" */ },
20286	/*::[*/0x01DE/*::]*/: { /* n:"BrtPageSetup" */ },
20287	/*::[*/0x01DF/*::]*/: { /* n:"BrtBeginHeaderFooter", */ T:1 },
20288	/*::[*/0x01E0/*::]*/: { /* n:"BrtEndHeaderFooter", */ T:-1 },
20289	/*::[*/0x01E1/*::]*/: { /* n:"BrtBeginSXCrtFormat", */ T:1 },
20290	/*::[*/0x01E2/*::]*/: { /* n:"BrtEndSXCrtFormat", */ T:-1 },
20291	/*::[*/0x01E3/*::]*/: { /* n:"BrtBeginSXCrtFormats", */ T:1 },
20292	/*::[*/0x01E4/*::]*/: { /* n:"BrtEndSXCrtFormats", */ T:-1 },
20293	/*::[*/0x01E5/*::]*/: { /* n:"BrtWsFmtInfo", */ f:parse_BrtWsFmtInfo },
20294	/*::[*/0x01E6/*::]*/: { /* n:"BrtBeginMgs", */ T:1 },
20295	/*::[*/0x01E7/*::]*/: { /* n:"BrtEndMGs", */ T:-1 },
20296	/*::[*/0x01E8/*::]*/: { /* n:"BrtBeginMGMaps", */ T:1 },
20297	/*::[*/0x01E9/*::]*/: { /* n:"BrtEndMGMaps", */ T:-1 },
20298	/*::[*/0x01EA/*::]*/: { /* n:"BrtBeginMG", */ T:1 },
20299	/*::[*/0x01EB/*::]*/: { /* n:"BrtEndMG", */ T:-1 },
20300	/*::[*/0x01EC/*::]*/: { /* n:"BrtBeginMap", */ T:1 },
20301	/*::[*/0x01ED/*::]*/: { /* n:"BrtEndMap", */ T:-1 },
20302	/*::[*/0x01EE/*::]*/: { /* n:"BrtHLink", */ f:parse_BrtHLink },
20303	/*::[*/0x01EF/*::]*/: { /* n:"BrtBeginDCon", */ T:1 },
20304	/*::[*/0x01F0/*::]*/: { /* n:"BrtEndDCon", */ T:-1 },
20305	/*::[*/0x01F1/*::]*/: { /* n:"BrtBeginDRefs", */ T:1 },
20306	/*::[*/0x01F2/*::]*/: { /* n:"BrtEndDRefs", */ T:-1 },
20307	/*::[*/0x01F3/*::]*/: { /* n:"BrtDRef" */ },
20308	/*::[*/0x01F4/*::]*/: { /* n:"BrtBeginScenMan", */ T:1 },
20309	/*::[*/0x01F5/*::]*/: { /* n:"BrtEndScenMan", */ T:-1 },
20310	/*::[*/0x01F6/*::]*/: { /* n:"BrtBeginSct", */ T:1 },
20311	/*::[*/0x01F7/*::]*/: { /* n:"BrtEndSct", */ T:-1 },
20312	/*::[*/0x01F8/*::]*/: { /* n:"BrtSlc" */ },
20313	/*::[*/0x01F9/*::]*/: { /* n:"BrtBeginDXFs", */ T:1 },
20314	/*::[*/0x01FA/*::]*/: { /* n:"BrtEndDXFs", */ T:-1 },
20315	/*::[*/0x01FB/*::]*/: { /* n:"BrtDXF" */ },
20316	/*::[*/0x01FC/*::]*/: { /* n:"BrtBeginTableStyles", */ T:1 },
20317	/*::[*/0x01FD/*::]*/: { /* n:"BrtEndTableStyles", */ T:-1 },
20318	/*::[*/0x01FE/*::]*/: { /* n:"BrtBeginTableStyle", */ T:1 },
20319	/*::[*/0x01FF/*::]*/: { /* n:"BrtEndTableStyle", */ T:-1 },
20320	/*::[*/0x0200/*::]*/: { /* n:"BrtTableStyleElement" */ },
20321	/*::[*/0x0201/*::]*/: { /* n:"BrtTableStyleClient" */ },
20322	/*::[*/0x0202/*::]*/: { /* n:"BrtBeginVolDeps", */ T:1 },
20323	/*::[*/0x0203/*::]*/: { /* n:"BrtEndVolDeps", */ T:-1 },
20324	/*::[*/0x0204/*::]*/: { /* n:"BrtBeginVolType", */ T:1 },
20325	/*::[*/0x0205/*::]*/: { /* n:"BrtEndVolType", */ T:-1 },
20326	/*::[*/0x0206/*::]*/: { /* n:"BrtBeginVolMain", */ T:1 },
20327	/*::[*/0x0207/*::]*/: { /* n:"BrtEndVolMain", */ T:-1 },
20328	/*::[*/0x0208/*::]*/: { /* n:"BrtBeginVolTopic", */ T:1 },
20329	/*::[*/0x0209/*::]*/: { /* n:"BrtEndVolTopic", */ T:-1 },
20330	/*::[*/0x020A/*::]*/: { /* n:"BrtVolSubtopic" */ },
20331	/*::[*/0x020B/*::]*/: { /* n:"BrtVolRef" */ },
20332	/*::[*/0x020C/*::]*/: { /* n:"BrtVolNum" */ },
20333	/*::[*/0x020D/*::]*/: { /* n:"BrtVolErr" */ },
20334	/*::[*/0x020E/*::]*/: { /* n:"BrtVolStr" */ },
20335	/*::[*/0x020F/*::]*/: { /* n:"BrtVolBool" */ },
20336	/*::[*/0x0210/*::]*/: { /* n:"BrtBeginCalcChain$", */ T:1 },
20337	/*::[*/0x0211/*::]*/: { /* n:"BrtEndCalcChain$", */ T:-1 },
20338	/*::[*/0x0212/*::]*/: { /* n:"BrtBeginSortState", */ T:1 },
20339	/*::[*/0x0213/*::]*/: { /* n:"BrtEndSortState", */ T:-1 },
20340	/*::[*/0x0214/*::]*/: { /* n:"BrtBeginSortCond", */ T:1 },
20341	/*::[*/0x0215/*::]*/: { /* n:"BrtEndSortCond", */ T:-1 },
20342	/*::[*/0x0216/*::]*/: { /* n:"BrtBookProtection" */ },
20343	/*::[*/0x0217/*::]*/: { /* n:"BrtSheetProtection" */ },
20344	/*::[*/0x0218/*::]*/: { /* n:"BrtRangeProtection" */ },
20345	/*::[*/0x0219/*::]*/: { /* n:"BrtPhoneticInfo" */ },
20346	/*::[*/0x021A/*::]*/: { /* n:"BrtBeginECTxtWiz", */ T:1 },
20347	/*::[*/0x021B/*::]*/: { /* n:"BrtEndECTxtWiz", */ T:-1 },
20348	/*::[*/0x021C/*::]*/: { /* n:"BrtBeginECTWFldInfoLst", */ T:1 },
20349	/*::[*/0x021D/*::]*/: { /* n:"BrtEndECTWFldInfoLst", */ T:-1 },
20350	/*::[*/0x021E/*::]*/: { /* n:"BrtBeginECTwFldInfo", */ T:1 },
20351	/*::[*/0x0224/*::]*/: { /* n:"BrtFileSharing" */ },
20352	/*::[*/0x0225/*::]*/: { /* n:"BrtOleSize" */ },
20353	/*::[*/0x0226/*::]*/: { /* n:"BrtDrawing", */ f:parse_RelID },
20354	/*::[*/0x0227/*::]*/: { /* n:"BrtLegacyDrawing" */ },
20355	/*::[*/0x0228/*::]*/: { /* n:"BrtLegacyDrawingHF" */ },
20356	/*::[*/0x0229/*::]*/: { /* n:"BrtWebOpt" */ },
20357	/*::[*/0x022A/*::]*/: { /* n:"BrtBeginWebPubItems", */ T:1 },
20358	/*::[*/0x022B/*::]*/: { /* n:"BrtEndWebPubItems", */ T:-1 },
20359	/*::[*/0x022C/*::]*/: { /* n:"BrtBeginWebPubItem", */ T:1 },
20360	/*::[*/0x022D/*::]*/: { /* n:"BrtEndWebPubItem", */ T:-1 },
20361	/*::[*/0x022E/*::]*/: { /* n:"BrtBeginSXCondFmt", */ T:1 },
20362	/*::[*/0x022F/*::]*/: { /* n:"BrtEndSXCondFmt", */ T:-1 },
20363	/*::[*/0x0230/*::]*/: { /* n:"BrtBeginSXCondFmts", */ T:1 },
20364	/*::[*/0x0231/*::]*/: { /* n:"BrtEndSXCondFmts", */ T:-1 },
20365	/*::[*/0x0232/*::]*/: { /* n:"BrtBkHim" */ },
20366	/*::[*/0x0234/*::]*/: { /* n:"BrtColor" */ },
20367	/*::[*/0x0235/*::]*/: { /* n:"BrtBeginIndexedColors", */ T:1 },
20368	/*::[*/0x0236/*::]*/: { /* n:"BrtEndIndexedColors", */ T:-1 },
20369	/*::[*/0x0239/*::]*/: { /* n:"BrtBeginMRUColors", */ T:1 },
20370	/*::[*/0x023A/*::]*/: { /* n:"BrtEndMRUColors", */ T:-1 },
20371	/*::[*/0x023C/*::]*/: { /* n:"BrtMRUColor" */ },
20372	/*::[*/0x023D/*::]*/: { /* n:"BrtBeginDVals", */ T:1 },
20373	/*::[*/0x023E/*::]*/: { /* n:"BrtEndDVals", */ T:-1 },
20374	/*::[*/0x0241/*::]*/: { /* n:"BrtSupNameStart" */ },
20375	/*::[*/0x0242/*::]*/: { /* n:"BrtSupNameValueStart" */ },
20376	/*::[*/0x0243/*::]*/: { /* n:"BrtSupNameValueEnd" */ },
20377	/*::[*/0x0244/*::]*/: { /* n:"BrtSupNameNum" */ },
20378	/*::[*/0x0245/*::]*/: { /* n:"BrtSupNameErr" */ },
20379	/*::[*/0x0246/*::]*/: { /* n:"BrtSupNameSt" */ },
20380	/*::[*/0x0247/*::]*/: { /* n:"BrtSupNameNil" */ },
20381	/*::[*/0x0248/*::]*/: { /* n:"BrtSupNameBool" */ },
20382	/*::[*/0x0249/*::]*/: { /* n:"BrtSupNameFmla" */ },
20383	/*::[*/0x024A/*::]*/: { /* n:"BrtSupNameBits" */ },
20384	/*::[*/0x024B/*::]*/: { /* n:"BrtSupNameEnd" */ },
20385	/*::[*/0x024C/*::]*/: { /* n:"BrtEndSupBook", */ T:-1 },
20386	/*::[*/0x024D/*::]*/: { /* n:"BrtCellSmartTagProperty" */ },
20387	/*::[*/0x024E/*::]*/: { /* n:"BrtBeginCellSmartTag", */ T:1 },
20388	/*::[*/0x024F/*::]*/: { /* n:"BrtEndCellSmartTag", */ T:-1 },
20389	/*::[*/0x0250/*::]*/: { /* n:"BrtBeginCellSmartTags", */ T:1 },
20390	/*::[*/0x0251/*::]*/: { /* n:"BrtEndCellSmartTags", */ T:-1 },
20391	/*::[*/0x0252/*::]*/: { /* n:"BrtBeginSmartTags", */ T:1 },
20392	/*::[*/0x0253/*::]*/: { /* n:"BrtEndSmartTags", */ T:-1 },
20393	/*::[*/0x0254/*::]*/: { /* n:"BrtSmartTagType" */ },
20394	/*::[*/0x0255/*::]*/: { /* n:"BrtBeginSmartTagTypes", */ T:1 },
20395	/*::[*/0x0256/*::]*/: { /* n:"BrtEndSmartTagTypes", */ T:-1 },
20396	/*::[*/0x0257/*::]*/: { /* n:"BrtBeginSXFilters", */ T:1 },
20397	/*::[*/0x0258/*::]*/: { /* n:"BrtEndSXFilters", */ T:-1 },
20398	/*::[*/0x0259/*::]*/: { /* n:"BrtBeginSXFILTER", */ T:1 },
20399	/*::[*/0x025A/*::]*/: { /* n:"BrtEndSXFilter", */ T:-1 },
20400	/*::[*/0x025B/*::]*/: { /* n:"BrtBeginFills", */ T:1 },
20401	/*::[*/0x025C/*::]*/: { /* n:"BrtEndFills", */ T:-1 },
20402	/*::[*/0x025D/*::]*/: { /* n:"BrtBeginCellWatches", */ T:1 },
20403	/*::[*/0x025E/*::]*/: { /* n:"BrtEndCellWatches", */ T:-1 },
20404	/*::[*/0x025F/*::]*/: { /* n:"BrtCellWatch" */ },
20405	/*::[*/0x0260/*::]*/: { /* n:"BrtBeginCRErrs", */ T:1 },
20406	/*::[*/0x0261/*::]*/: { /* n:"BrtEndCRErrs", */ T:-1 },
20407	/*::[*/0x0262/*::]*/: { /* n:"BrtCrashRecErr" */ },
20408	/*::[*/0x0263/*::]*/: { /* n:"BrtBeginFonts", */ T:1 },
20409	/*::[*/0x0264/*::]*/: { /* n:"BrtEndFonts", */ T:-1 },
20410	/*::[*/0x0265/*::]*/: { /* n:"BrtBeginBorders", */ T:1 },
20411	/*::[*/0x0266/*::]*/: { /* n:"BrtEndBorders", */ T:-1 },
20412	/*::[*/0x0267/*::]*/: { /* n:"BrtBeginFmts", */ T:1 },
20413	/*::[*/0x0268/*::]*/: { /* n:"BrtEndFmts", */ T:-1 },
20414	/*::[*/0x0269/*::]*/: { /* n:"BrtBeginCellXFs", */ T:1 },
20415	/*::[*/0x026A/*::]*/: { /* n:"BrtEndCellXFs", */ T:-1 },
20416	/*::[*/0x026B/*::]*/: { /* n:"BrtBeginStyles", */ T:1 },
20417	/*::[*/0x026C/*::]*/: { /* n:"BrtEndStyles", */ T:-1 },
20418	/*::[*/0x0271/*::]*/: { /* n:"BrtBigName" */ },
20419	/*::[*/0x0272/*::]*/: { /* n:"BrtBeginCellStyleXFs", */ T:1 },
20420	/*::[*/0x0273/*::]*/: { /* n:"BrtEndCellStyleXFs", */ T:-1 },
20421	/*::[*/0x0274/*::]*/: { /* n:"BrtBeginComments", */ T:1 },
20422	/*::[*/0x0275/*::]*/: { /* n:"BrtEndComments", */ T:-1 },
20423	/*::[*/0x0276/*::]*/: { /* n:"BrtBeginCommentAuthors", */ T:1 },
20424	/*::[*/0x0277/*::]*/: { /* n:"BrtEndCommentAuthors", */ T:-1 },
20425	/*::[*/0x0278/*::]*/: { /* n:"BrtCommentAuthor", */ f:parse_BrtCommentAuthor },
20426	/*::[*/0x0279/*::]*/: { /* n:"BrtBeginCommentList", */ T:1 },
20427	/*::[*/0x027A/*::]*/: { /* n:"BrtEndCommentList", */ T:-1 },
20428	/*::[*/0x027B/*::]*/: { /* n:"BrtBeginComment", */ T:1, f:parse_BrtBeginComment},
20429	/*::[*/0x027C/*::]*/: { /* n:"BrtEndComment", */ T:-1 },
20430	/*::[*/0x027D/*::]*/: { /* n:"BrtCommentText", */ f:parse_BrtCommentText },
20431	/*::[*/0x027E/*::]*/: { /* n:"BrtBeginOleObjects", */ T:1 },
20432	/*::[*/0x027F/*::]*/: { /* n:"BrtOleObject" */ },
20433	/*::[*/0x0280/*::]*/: { /* n:"BrtEndOleObjects", */ T:-1 },
20434	/*::[*/0x0281/*::]*/: { /* n:"BrtBeginSxrules", */ T:1 },
20435	/*::[*/0x0282/*::]*/: { /* n:"BrtEndSxRules", */ T:-1 },
20436	/*::[*/0x0283/*::]*/: { /* n:"BrtBeginActiveXControls", */ T:1 },
20437	/*::[*/0x0284/*::]*/: { /* n:"BrtActiveX" */ },
20438	/*::[*/0x0285/*::]*/: { /* n:"BrtEndActiveXControls", */ T:-1 },
20439	/*::[*/0x0286/*::]*/: { /* n:"BrtBeginPCDSDTCEMembersSortBy", */ T:1 },
20440	/*::[*/0x0288/*::]*/: { /* n:"BrtBeginCellIgnoreECs", */ T:1 },
20441	/*::[*/0x0289/*::]*/: { /* n:"BrtCellIgnoreEC" */ },
20442	/*::[*/0x028A/*::]*/: { /* n:"BrtEndCellIgnoreECs", */ T:-1 },
20443	/*::[*/0x028B/*::]*/: { /* n:"BrtCsProp", */ f:parse_BrtCsProp },
20444	/*::[*/0x028C/*::]*/: { /* n:"BrtCsPageSetup" */ },
20445	/*::[*/0x028D/*::]*/: { /* n:"BrtBeginUserCsViews", */ T:1 },
20446	/*::[*/0x028E/*::]*/: { /* n:"BrtEndUserCsViews", */ T:-1 },
20447	/*::[*/0x028F/*::]*/: { /* n:"BrtBeginUserCsView", */ T:1 },
20448	/*::[*/0x0290/*::]*/: { /* n:"BrtEndUserCsView", */ T:-1 },
20449	/*::[*/0x0291/*::]*/: { /* n:"BrtBeginPcdSFCIEntries", */ T:1 },
20450	/*::[*/0x0292/*::]*/: { /* n:"BrtEndPCDSFCIEntries", */ T:-1 },
20451	/*::[*/0x0293/*::]*/: { /* n:"BrtPCDSFCIEntry" */ },
20452	/*::[*/0x0294/*::]*/: { /* n:"BrtBeginListParts", */ T:1 },
20453	/*::[*/0x0295/*::]*/: { /* n:"BrtListPart" */ },
20454	/*::[*/0x0296/*::]*/: { /* n:"BrtEndListParts", */ T:-1 },
20455	/*::[*/0x0297/*::]*/: { /* n:"BrtSheetCalcProp" */ },
20456	/*::[*/0x0298/*::]*/: { /* n:"BrtBeginFnGroup", */ T:1 },
20457	/*::[*/0x0299/*::]*/: { /* n:"BrtFnGroup" */ },
20458	/*::[*/0x029A/*::]*/: { /* n:"BrtEndFnGroup", */ T:-1 },
20459	/*::[*/0x029B/*::]*/: { /* n:"BrtSupAddin" */ },
20460	/*::[*/0x029C/*::]*/: { /* n:"BrtSXTDMPOrder" */ },
20461	/*::[*/0x029D/*::]*/: { /* n:"BrtCsProtection" */ },
20462	/*::[*/0x029F/*::]*/: { /* n:"BrtBeginWsSortMap", */ T:1 },
20463	/*::[*/0x02A0/*::]*/: { /* n:"BrtEndWsSortMap", */ T:-1 },
20464	/*::[*/0x02A1/*::]*/: { /* n:"BrtBeginRRSort", */ T:1 },
20465	/*::[*/0x02A2/*::]*/: { /* n:"BrtEndRRSort", */ T:-1 },
20466	/*::[*/0x02A3/*::]*/: { /* n:"BrtRRSortItem" */ },
20467	/*::[*/0x02A4/*::]*/: { /* n:"BrtFileSharingIso" */ },
20468	/*::[*/0x02A5/*::]*/: { /* n:"BrtBookProtectionIso" */ },
20469	/*::[*/0x02A6/*::]*/: { /* n:"BrtSheetProtectionIso" */ },
20470	/*::[*/0x02A7/*::]*/: { /* n:"BrtCsProtectionIso" */ },
20471	/*::[*/0x02A8/*::]*/: { /* n:"BrtRangeProtectionIso" */ },
20472	/*::[*/0x02A9/*::]*/: { /* n:"BrtDValList" */ },
20473	/*::[*/0x0400/*::]*/: { /* n:"BrtRwDescent" */ },
20474	/*::[*/0x0401/*::]*/: { /* n:"BrtKnownFonts" */ },
20475	/*::[*/0x0402/*::]*/: { /* n:"BrtBeginSXTupleSet", */ T:1 },
20476	/*::[*/0x0403/*::]*/: { /* n:"BrtEndSXTupleSet", */ T:-1 },
20477	/*::[*/0x0404/*::]*/: { /* n:"BrtBeginSXTupleSetHeader", */ T:1 },
20478	/*::[*/0x0405/*::]*/: { /* n:"BrtEndSXTupleSetHeader", */ T:-1 },
20479	/*::[*/0x0406/*::]*/: { /* n:"BrtSXTupleSetHeaderItem" */ },
20480	/*::[*/0x0407/*::]*/: { /* n:"BrtBeginSXTupleSetData", */ T:1 },
20481	/*::[*/0x0408/*::]*/: { /* n:"BrtEndSXTupleSetData", */ T:-1 },
20482	/*::[*/0x0409/*::]*/: { /* n:"BrtBeginSXTupleSetRow", */ T:1 },
20483	/*::[*/0x040A/*::]*/: { /* n:"BrtEndSXTupleSetRow", */ T:-1 },
20484	/*::[*/0x040B/*::]*/: { /* n:"BrtSXTupleSetRowItem" */ },
20485	/*::[*/0x040C/*::]*/: { /* n:"BrtNameExt" */ },
20486	/*::[*/0x040D/*::]*/: { /* n:"BrtPCDH14" */ },
20487	/*::[*/0x040E/*::]*/: { /* n:"BrtBeginPCDCalcMem14", */ T:1 },
20488	/*::[*/0x040F/*::]*/: { /* n:"BrtEndPCDCalcMem14", */ T:-1 },
20489	/*::[*/0x0410/*::]*/: { /* n:"BrtSXTH14" */ },
20490	/*::[*/0x0411/*::]*/: { /* n:"BrtBeginSparklineGroup", */ T:1 },
20491	/*::[*/0x0412/*::]*/: { /* n:"BrtEndSparklineGroup", */ T:-1 },
20492	/*::[*/0x0413/*::]*/: { /* n:"BrtSparkline" */ },
20493	/*::[*/0x0414/*::]*/: { /* n:"BrtSXDI14" */ },
20494	/*::[*/0x0415/*::]*/: { /* n:"BrtWsFmtInfoEx14" */ },
20495	/*::[*/0x0416/*::]*/: { /* n:"BrtBeginConditionalFormatting14", */ T:1 },
20496	/*::[*/0x0417/*::]*/: { /* n:"BrtEndConditionalFormatting14", */ T:-1 },
20497	/*::[*/0x0418/*::]*/: { /* n:"BrtBeginCFRule14", */ T:1 },
20498	/*::[*/0x0419/*::]*/: { /* n:"BrtEndCFRule14", */ T:-1 },
20499	/*::[*/0x041A/*::]*/: { /* n:"BrtCFVO14" */ },
20500	/*::[*/0x041B/*::]*/: { /* n:"BrtBeginDatabar14", */ T:1 },
20501	/*::[*/0x041C/*::]*/: { /* n:"BrtBeginIconSet14", */ T:1 },
20502	/*::[*/0x041D/*::]*/: { /* n:"BrtDVal14", */ f: parse_BrtDVal14 },
20503	/*::[*/0x041E/*::]*/: { /* n:"BrtBeginDVals14", */ T:1 },
20504	/*::[*/0x041F/*::]*/: { /* n:"BrtColor14" */ },
20505	/*::[*/0x0420/*::]*/: { /* n:"BrtBeginSparklines", */ T:1 },
20506	/*::[*/0x0421/*::]*/: { /* n:"BrtEndSparklines", */ T:-1 },
20507	/*::[*/0x0422/*::]*/: { /* n:"BrtBeginSparklineGroups", */ T:1 },
20508	/*::[*/0x0423/*::]*/: { /* n:"BrtEndSparklineGroups", */ T:-1 },
20509	/*::[*/0x0425/*::]*/: { /* n:"BrtSXVD14" */ },
20510	/*::[*/0x0426/*::]*/: { /* n:"BrtBeginSXView14", */ T:1 },
20511	/*::[*/0x0427/*::]*/: { /* n:"BrtEndSXView14", */ T:-1 },
20512	/*::[*/0x0428/*::]*/: { /* n:"BrtBeginSXView16", */ T:1 },
20513	/*::[*/0x0429/*::]*/: { /* n:"BrtEndSXView16", */ T:-1 },
20514	/*::[*/0x042A/*::]*/: { /* n:"BrtBeginPCD14", */ T:1 },
20515	/*::[*/0x042B/*::]*/: { /* n:"BrtEndPCD14", */ T:-1 },
20516	/*::[*/0x042C/*::]*/: { /* n:"BrtBeginExtConn14", */ T:1 },
20517	/*::[*/0x042D/*::]*/: { /* n:"BrtEndExtConn14", */ T:-1 },
20518	/*::[*/0x042E/*::]*/: { /* n:"BrtBeginSlicerCacheIDs", */ T:1 },
20519	/*::[*/0x042F/*::]*/: { /* n:"BrtEndSlicerCacheIDs", */ T:-1 },
20520	/*::[*/0x0430/*::]*/: { /* n:"BrtBeginSlicerCacheID", */ T:1 },
20521	/*::[*/0x0431/*::]*/: { /* n:"BrtEndSlicerCacheID", */ T:-1 },
20522	/*::[*/0x0433/*::]*/: { /* n:"BrtBeginSlicerCache", */ T:1 },
20523	/*::[*/0x0434/*::]*/: { /* n:"BrtEndSlicerCache", */ T:-1 },
20524	/*::[*/0x0435/*::]*/: { /* n:"BrtBeginSlicerCacheDef", */ T:1 },
20525	/*::[*/0x0436/*::]*/: { /* n:"BrtEndSlicerCacheDef", */ T:-1 },
20526	/*::[*/0x0437/*::]*/: { /* n:"BrtBeginSlicersEx", */ T:1 },
20527	/*::[*/0x0438/*::]*/: { /* n:"BrtEndSlicersEx", */ T:-1 },
20528	/*::[*/0x0439/*::]*/: { /* n:"BrtBeginSlicerEx", */ T:1 },
20529	/*::[*/0x043A/*::]*/: { /* n:"BrtEndSlicerEx", */ T:-1 },
20530	/*::[*/0x043B/*::]*/: { /* n:"BrtBeginSlicer", */ T:1 },
20531	/*::[*/0x043C/*::]*/: { /* n:"BrtEndSlicer", */ T:-1 },
20532	/*::[*/0x043D/*::]*/: { /* n:"BrtSlicerCachePivotTables" */ },
20533	/*::[*/0x043E/*::]*/: { /* n:"BrtBeginSlicerCacheOlapImpl", */ T:1 },
20534	/*::[*/0x043F/*::]*/: { /* n:"BrtEndSlicerCacheOlapImpl", */ T:-1 },
20535	/*::[*/0x0440/*::]*/: { /* n:"BrtBeginSlicerCacheLevelsData", */ T:1 },
20536	/*::[*/0x0441/*::]*/: { /* n:"BrtEndSlicerCacheLevelsData", */ T:-1 },
20537	/*::[*/0x0442/*::]*/: { /* n:"BrtBeginSlicerCacheLevelData", */ T:1 },
20538	/*::[*/0x0443/*::]*/: { /* n:"BrtEndSlicerCacheLevelData", */ T:-1 },
20539	/*::[*/0x0444/*::]*/: { /* n:"BrtBeginSlicerCacheSiRanges", */ T:1 },
20540	/*::[*/0x0445/*::]*/: { /* n:"BrtEndSlicerCacheSiRanges", */ T:-1 },
20541	/*::[*/0x0446/*::]*/: { /* n:"BrtBeginSlicerCacheSiRange", */ T:1 },
20542	/*::[*/0x0447/*::]*/: { /* n:"BrtEndSlicerCacheSiRange", */ T:-1 },
20543	/*::[*/0x0448/*::]*/: { /* n:"BrtSlicerCacheOlapItem" */ },
20544	/*::[*/0x0449/*::]*/: { /* n:"BrtBeginSlicerCacheSelections", */ T:1 },
20545	/*::[*/0x044A/*::]*/: { /* n:"BrtSlicerCacheSelection" */ },
20546	/*::[*/0x044B/*::]*/: { /* n:"BrtEndSlicerCacheSelections", */ T:-1 },
20547	/*::[*/0x044C/*::]*/: { /* n:"BrtBeginSlicerCacheNative", */ T:1 },
20548	/*::[*/0x044D/*::]*/: { /* n:"BrtEndSlicerCacheNative", */ T:-1 },
20549	/*::[*/0x044E/*::]*/: { /* n:"BrtSlicerCacheNativeItem" */ },
20550	/*::[*/0x044F/*::]*/: { /* n:"BrtRangeProtection14" */ },
20551	/*::[*/0x0450/*::]*/: { /* n:"BrtRangeProtectionIso14" */ },
20552	/*::[*/0x0451/*::]*/: { /* n:"BrtCellIgnoreEC14" */ },
20553	/*::[*/0x0457/*::]*/: { /* n:"BrtList14" */ },
20554	/*::[*/0x0458/*::]*/: { /* n:"BrtCFIcon" */ },
20555	/*::[*/0x0459/*::]*/: { /* n:"BrtBeginSlicerCachesPivotCacheIDs", */ T:1 },
20556	/*::[*/0x045A/*::]*/: { /* n:"BrtEndSlicerCachesPivotCacheIDs", */ T:-1 },
20557	/*::[*/0x045B/*::]*/: { /* n:"BrtBeginSlicers", */ T:1 },
20558	/*::[*/0x045C/*::]*/: { /* n:"BrtEndSlicers", */ T:-1 },
20559	/*::[*/0x045D/*::]*/: { /* n:"BrtWbProp14" */ },
20560	/*::[*/0x045E/*::]*/: { /* n:"BrtBeginSXEdit", */ T:1 },
20561	/*::[*/0x045F/*::]*/: { /* n:"BrtEndSXEdit", */ T:-1 },
20562	/*::[*/0x0460/*::]*/: { /* n:"BrtBeginSXEdits", */ T:1 },
20563	/*::[*/0x0461/*::]*/: { /* n:"BrtEndSXEdits", */ T:-1 },
20564	/*::[*/0x0462/*::]*/: { /* n:"BrtBeginSXChange", */ T:1 },
20565	/*::[*/0x0463/*::]*/: { /* n:"BrtEndSXChange", */ T:-1 },
20566	/*::[*/0x0464/*::]*/: { /* n:"BrtBeginSXChanges", */ T:1 },
20567	/*::[*/0x0465/*::]*/: { /* n:"BrtEndSXChanges", */ T:-1 },
20568	/*::[*/0x0466/*::]*/: { /* n:"BrtSXTupleItems" */ },
20569	/*::[*/0x0468/*::]*/: { /* n:"BrtBeginSlicerStyle", */ T:1 },
20570	/*::[*/0x0469/*::]*/: { /* n:"BrtEndSlicerStyle", */ T:-1 },
20571	/*::[*/0x046A/*::]*/: { /* n:"BrtSlicerStyleElement" */ },
20572	/*::[*/0x046B/*::]*/: { /* n:"BrtBeginStyleSheetExt14", */ T:1 },
20573	/*::[*/0x046C/*::]*/: { /* n:"BrtEndStyleSheetExt14", */ T:-1 },
20574	/*::[*/0x046D/*::]*/: { /* n:"BrtBeginSlicerCachesPivotCacheID", */ T:1 },
20575	/*::[*/0x046E/*::]*/: { /* n:"BrtEndSlicerCachesPivotCacheID", */ T:-1 },
20576	/*::[*/0x046F/*::]*/: { /* n:"BrtBeginConditionalFormattings", */ T:1 },
20577	/*::[*/0x0470/*::]*/: { /* n:"BrtEndConditionalFormattings", */ T:-1 },
20578	/*::[*/0x0471/*::]*/: { /* n:"BrtBeginPCDCalcMemExt", */ T:1 },
20579	/*::[*/0x0472/*::]*/: { /* n:"BrtEndPCDCalcMemExt", */ T:-1 },
20580	/*::[*/0x0473/*::]*/: { /* n:"BrtBeginPCDCalcMemsExt", */ T:1 },
20581	/*::[*/0x0474/*::]*/: { /* n:"BrtEndPCDCalcMemsExt", */ T:-1 },
20582	/*::[*/0x0475/*::]*/: { /* n:"BrtPCDField14" */ },
20583	/*::[*/0x0476/*::]*/: { /* n:"BrtBeginSlicerStyles", */ T:1 },
20584	/*::[*/0x0477/*::]*/: { /* n:"BrtEndSlicerStyles", */ T:-1 },
20585	/*::[*/0x0478/*::]*/: { /* n:"BrtBeginSlicerStyleElements", */ T:1 },
20586	/*::[*/0x0479/*::]*/: { /* n:"BrtEndSlicerStyleElements", */ T:-1 },
20587	/*::[*/0x047A/*::]*/: { /* n:"BrtCFRuleExt" */ },
20588	/*::[*/0x047B/*::]*/: { /* n:"BrtBeginSXCondFmt14", */ T:1 },
20589	/*::[*/0x047C/*::]*/: { /* n:"BrtEndSXCondFmt14", */ T:-1 },
20590	/*::[*/0x047D/*::]*/: { /* n:"BrtBeginSXCondFmts14", */ T:1 },
20591	/*::[*/0x047E/*::]*/: { /* n:"BrtEndSXCondFmts14", */ T:-1 },
20592	/*::[*/0x0480/*::]*/: { /* n:"BrtBeginSortCond14", */ T:1 },
20593	/*::[*/0x0481/*::]*/: { /* n:"BrtEndSortCond14", */ T:-1 },
20594	/*::[*/0x0482/*::]*/: { /* n:"BrtEndDVals14", */ T:-1 },
20595	/*::[*/0x0483/*::]*/: { /* n:"BrtEndIconSet14", */ T:-1 },
20596	/*::[*/0x0484/*::]*/: { /* n:"BrtEndDatabar14", */ T:-1 },
20597	/*::[*/0x0485/*::]*/: { /* n:"BrtBeginColorScale14", */ T:1 },
20598	/*::[*/0x0486/*::]*/: { /* n:"BrtEndColorScale14", */ T:-1 },
20599	/*::[*/0x0487/*::]*/: { /* n:"BrtBeginSxrules14", */ T:1 },
20600	/*::[*/0x0488/*::]*/: { /* n:"BrtEndSxrules14", */ T:-1 },
20601	/*::[*/0x0489/*::]*/: { /* n:"BrtBeginPRule14", */ T:1 },
20602	/*::[*/0x048A/*::]*/: { /* n:"BrtEndPRule14", */ T:-1 },
20603	/*::[*/0x048B/*::]*/: { /* n:"BrtBeginPRFilters14", */ T:1 },
20604	/*::[*/0x048C/*::]*/: { /* n:"BrtEndPRFilters14", */ T:-1 },
20605	/*::[*/0x048D/*::]*/: { /* n:"BrtBeginPRFilter14", */ T:1 },
20606	/*::[*/0x048E/*::]*/: { /* n:"BrtEndPRFilter14", */ T:-1 },
20607	/*::[*/0x048F/*::]*/: { /* n:"BrtBeginPRFItem14", */ T:1 },
20608	/*::[*/0x0490/*::]*/: { /* n:"BrtEndPRFItem14", */ T:-1 },
20609	/*::[*/0x0491/*::]*/: { /* n:"BrtBeginCellIgnoreECs14", */ T:1 },
20610	/*::[*/0x0492/*::]*/: { /* n:"BrtEndCellIgnoreECs14", */ T:-1 },
20611	/*::[*/0x0493/*::]*/: { /* n:"BrtDxf14" */ },
20612	/*::[*/0x0494/*::]*/: { /* n:"BrtBeginDxF14s", */ T:1 },
20613	/*::[*/0x0495/*::]*/: { /* n:"BrtEndDxf14s", */ T:-1 },
20614	/*::[*/0x0499/*::]*/: { /* n:"BrtFilter14" */ },
20615	/*::[*/0x049A/*::]*/: { /* n:"BrtBeginCustomFilters14", */ T:1 },
20616	/*::[*/0x049C/*::]*/: { /* n:"BrtCustomFilter14" */ },
20617	/*::[*/0x049D/*::]*/: { /* n:"BrtIconFilter14" */ },
20618	/*::[*/0x049E/*::]*/: { /* n:"BrtPivotCacheConnectionName" */ },
20619	/*::[*/0x0800/*::]*/: { /* n:"BrtBeginDecoupledPivotCacheIDs", */ T:1 },
20620	/*::[*/0x0801/*::]*/: { /* n:"BrtEndDecoupledPivotCacheIDs", */ T:-1 },
20621	/*::[*/0x0802/*::]*/: { /* n:"BrtDecoupledPivotCacheID" */ },
20622	/*::[*/0x0803/*::]*/: { /* n:"BrtBeginPivotTableRefs", */ T:1 },
20623	/*::[*/0x0804/*::]*/: { /* n:"BrtEndPivotTableRefs", */ T:-1 },
20624	/*::[*/0x0805/*::]*/: { /* n:"BrtPivotTableRef" */ },
20625	/*::[*/0x0806/*::]*/: { /* n:"BrtSlicerCacheBookPivotTables" */ },
20626	/*::[*/0x0807/*::]*/: { /* n:"BrtBeginSxvcells", */ T:1 },
20627	/*::[*/0x0808/*::]*/: { /* n:"BrtEndSxvcells", */ T:-1 },
20628	/*::[*/0x0809/*::]*/: { /* n:"BrtBeginSxRow", */ T:1 },
20629	/*::[*/0x080A/*::]*/: { /* n:"BrtEndSxRow", */ T:-1 },
20630	/*::[*/0x080C/*::]*/: { /* n:"BrtPcdCalcMem15" */ },
20631	/*::[*/0x0813/*::]*/: { /* n:"BrtQsi15" */ },
20632	/*::[*/0x0814/*::]*/: { /* n:"BrtBeginWebExtensions", */ T:1 },
20633	/*::[*/0x0815/*::]*/: { /* n:"BrtEndWebExtensions", */ T:-1 },
20634	/*::[*/0x0816/*::]*/: { /* n:"BrtWebExtension" */ },
20635	/*::[*/0x0817/*::]*/: { /* n:"BrtAbsPath15" */ },
20636	/*::[*/0x0818/*::]*/: { /* n:"BrtBeginPivotTableUISettings", */ T:1 },
20637	/*::[*/0x0819/*::]*/: { /* n:"BrtEndPivotTableUISettings", */ T:-1 },
20638	/*::[*/0x081B/*::]*/: { /* n:"BrtTableSlicerCacheIDs" */ },
20639	/*::[*/0x081C/*::]*/: { /* n:"BrtTableSlicerCacheID" */ },
20640	/*::[*/0x081D/*::]*/: { /* n:"BrtBeginTableSlicerCache", */ T:1 },
20641	/*::[*/0x081E/*::]*/: { /* n:"BrtEndTableSlicerCache", */ T:-1 },
20642	/*::[*/0x081F/*::]*/: { /* n:"BrtSxFilter15" */ },
20643	/*::[*/0x0820/*::]*/: { /* n:"BrtBeginTimelineCachePivotCacheIDs", */ T:1 },
20644	/*::[*/0x0821/*::]*/: { /* n:"BrtEndTimelineCachePivotCacheIDs", */ T:-1 },
20645	/*::[*/0x0822/*::]*/: { /* n:"BrtTimelineCachePivotCacheID" */ },
20646	/*::[*/0x0823/*::]*/: { /* n:"BrtBeginTimelineCacheIDs", */ T:1 },
20647	/*::[*/0x0824/*::]*/: { /* n:"BrtEndTimelineCacheIDs", */ T:-1 },
20648	/*::[*/0x0825/*::]*/: { /* n:"BrtBeginTimelineCacheID", */ T:1 },
20649	/*::[*/0x0826/*::]*/: { /* n:"BrtEndTimelineCacheID", */ T:-1 },
20650	/*::[*/0x0827/*::]*/: { /* n:"BrtBeginTimelinesEx", */ T:1 },
20651	/*::[*/0x0828/*::]*/: { /* n:"BrtEndTimelinesEx", */ T:-1 },
20652	/*::[*/0x0829/*::]*/: { /* n:"BrtBeginTimelineEx", */ T:1 },
20653	/*::[*/0x082A/*::]*/: { /* n:"BrtEndTimelineEx", */ T:-1 },
20654	/*::[*/0x082B/*::]*/: { /* n:"BrtWorkBookPr15" */ },
20655	/*::[*/0x082C/*::]*/: { /* n:"BrtPCDH15" */ },
20656	/*::[*/0x082D/*::]*/: { /* n:"BrtBeginTimelineStyle", */ T:1 },
20657	/*::[*/0x082E/*::]*/: { /* n:"BrtEndTimelineStyle", */ T:-1 },
20658	/*::[*/0x082F/*::]*/: { /* n:"BrtTimelineStyleElement" */ },
20659	/*::[*/0x0830/*::]*/: { /* n:"BrtBeginTimelineStylesheetExt15", */ T:1 },
20660	/*::[*/0x0831/*::]*/: { /* n:"BrtEndTimelineStylesheetExt15", */ T:-1 },
20661	/*::[*/0x0832/*::]*/: { /* n:"BrtBeginTimelineStyles", */ T:1 },
20662	/*::[*/0x0833/*::]*/: { /* n:"BrtEndTimelineStyles", */ T:-1 },
20663	/*::[*/0x0834/*::]*/: { /* n:"BrtBeginTimelineStyleElements", */ T:1 },
20664	/*::[*/0x0835/*::]*/: { /* n:"BrtEndTimelineStyleElements", */ T:-1 },
20665	/*::[*/0x0836/*::]*/: { /* n:"BrtDxf15" */ },
20666	/*::[*/0x0837/*::]*/: { /* n:"BrtBeginDxfs15", */ T:1 },
20667	/*::[*/0x0838/*::]*/: { /* n:"BrtEndDxfs15", */ T:-1 },
20668	/*::[*/0x0839/*::]*/: { /* n:"BrtSlicerCacheHideItemsWithNoData" */ },
20669	/*::[*/0x083A/*::]*/: { /* n:"BrtBeginItemUniqueNames", */ T:1 },
20670	/*::[*/0x083B/*::]*/: { /* n:"BrtEndItemUniqueNames", */ T:-1 },
20671	/*::[*/0x083C/*::]*/: { /* n:"BrtItemUniqueName" */ },
20672	/*::[*/0x083D/*::]*/: { /* n:"BrtBeginExtConn15", */ T:1 },
20673	/*::[*/0x083E/*::]*/: { /* n:"BrtEndExtConn15", */ T:-1 },
20674	/*::[*/0x083F/*::]*/: { /* n:"BrtBeginOledbPr15", */ T:1 },
20675	/*::[*/0x0840/*::]*/: { /* n:"BrtEndOledbPr15", */ T:-1 },
20676	/*::[*/0x0841/*::]*/: { /* n:"BrtBeginDataFeedPr15", */ T:1 },
20677	/*::[*/0x0842/*::]*/: { /* n:"BrtEndDataFeedPr15", */ T:-1 },
20678	/*::[*/0x0843/*::]*/: { /* n:"BrtTextPr15" */ },
20679	/*::[*/0x0844/*::]*/: { /* n:"BrtRangePr15" */ },
20680	/*::[*/0x0845/*::]*/: { /* n:"BrtDbCommand15" */ },
20681	/*::[*/0x0846/*::]*/: { /* n:"BrtBeginDbTables15", */ T:1 },
20682	/*::[*/0x0847/*::]*/: { /* n:"BrtEndDbTables15", */ T:-1 },
20683	/*::[*/0x0848/*::]*/: { /* n:"BrtDbTable15" */ },
20684	/*::[*/0x0849/*::]*/: { /* n:"BrtBeginDataModel", */ T:1 },
20685	/*::[*/0x084A/*::]*/: { /* n:"BrtEndDataModel", */ T:-1 },
20686	/*::[*/0x084B/*::]*/: { /* n:"BrtBeginModelTables", */ T:1 },
20687	/*::[*/0x084C/*::]*/: { /* n:"BrtEndModelTables", */ T:-1 },
20688	/*::[*/0x084D/*::]*/: { /* n:"BrtModelTable" */ },
20689	/*::[*/0x084E/*::]*/: { /* n:"BrtBeginModelRelationships", */ T:1 },
20690	/*::[*/0x084F/*::]*/: { /* n:"BrtEndModelRelationships", */ T:-1 },
20691	/*::[*/0x0850/*::]*/: { /* n:"BrtModelRelationship" */ },
20692	/*::[*/0x0851/*::]*/: { /* n:"BrtBeginECTxtWiz15", */ T:1 },
20693	/*::[*/0x0852/*::]*/: { /* n:"BrtEndECTxtWiz15", */ T:-1 },
20694	/*::[*/0x0853/*::]*/: { /* n:"BrtBeginECTWFldInfoLst15", */ T:1 },
20695	/*::[*/0x0854/*::]*/: { /* n:"BrtEndECTWFldInfoLst15", */ T:-1 },
20696	/*::[*/0x0855/*::]*/: { /* n:"BrtBeginECTWFldInfo15", */ T:1 },
20697	/*::[*/0x0856/*::]*/: { /* n:"BrtFieldListActiveItem" */ },
20698	/*::[*/0x0857/*::]*/: { /* n:"BrtPivotCacheIdVersion" */ },
20699	/*::[*/0x0858/*::]*/: { /* n:"BrtSXDI15" */ },
20700	/*::[*/0x0859/*::]*/: { /* n:"BrtBeginModelTimeGroupings", */ T:1 },
20701	/*::[*/0x085A/*::]*/: { /* n:"BrtEndModelTimeGroupings", */ T:-1 },
20702	/*::[*/0x085B/*::]*/: { /* n:"BrtBeginModelTimeGrouping", */ T:1 },
20703	/*::[*/0x085C/*::]*/: { /* n:"BrtEndModelTimeGrouping", */ T:-1 },
20704	/*::[*/0x085D/*::]*/: { /* n:"BrtModelTimeGroupingCalcCol" */ },
20705	/*::[*/0x0C00/*::]*/: { /* n:"BrtUid" */ },
20706	/*::[*/0x0C01/*::]*/: { /* n:"BrtRevisionPtr" */ },
20707	/*::[*/0x1000/*::]*/: { /* n:"BrtBeginDynamicArrayPr", */ T:1 },
20708	/*::[*/0x1001/*::]*/: { /* n:"BrtEndDynamicArrayPr", */ T:-1 },
20709	/*::[*/0x138A/*::]*/: { /* n:"BrtBeginRichValueBlock", */ T:1 },
20710	/*::[*/0x138B/*::]*/: { /* n:"BrtEndRichValueBlock", */ T:-1 },
20711	/*::[*/0x13D9/*::]*/: { /* n:"BrtBeginRichFilters", */ T:1 },
20712	/*::[*/0x13DA/*::]*/: { /* n:"BrtEndRichFilters", */ T:-1 },
20713	/*::[*/0x13DB/*::]*/: { /* n:"BrtRichFilter" */ },
20714	/*::[*/0x13DC/*::]*/: { /* n:"BrtBeginRichFilterColumn", */ T:1 },
20715	/*::[*/0x13DD/*::]*/: { /* n:"BrtEndRichFilterColumn", */ T:-1 },
20716	/*::[*/0x13DE/*::]*/: { /* n:"BrtBeginCustomRichFilters", */ T:1 },
20717	/*::[*/0x13DF/*::]*/: { /* n:"BrtEndCustomRichFilters", */ T:-1 },
20718	/*::[*/0x13E0/*::]*/: { /* n:"BrtCustomRichFilter" */ },
20719	/*::[*/0x13E1/*::]*/: { /* n:"BrtTop10RichFilter" */ },
20720	/*::[*/0x13E2/*::]*/: { /* n:"BrtDynamicRichFilter" */ },
20721	/*::[*/0x13E4/*::]*/: { /* n:"BrtBeginRichSortCondition", */ T:1 },
20722	/*::[*/0x13E5/*::]*/: { /* n:"BrtEndRichSortCondition", */ T:-1 },
20723	/*::[*/0x13E6/*::]*/: { /* n:"BrtRichFilterDateGroupItem" */ },
20724	/*::[*/0x13E7/*::]*/: { /* n:"BrtBeginCalcFeatures", */ T:1 },
20725	/*::[*/0x13E8/*::]*/: { /* n:"BrtEndCalcFeatures", */ T:-1 },
20726	/*::[*/0x13E9/*::]*/: { /* n:"BrtCalcFeature" */ },
20727	/*::[*/0x13EB/*::]*/: { /* n:"BrtExternalLinksPr" */ },
20728	/*::[*/0xFFFF/*::]*/: { n:"" }
20729};
20730
20731/* [MS-XLS] 2.3 Record Enumeration (and other sources) */
20732var XLSRecordEnum = {
20733	/* [MS-XLS] 2.3 Record Enumeration 2021-08-17 */
20734	/*::[*/0x0006/*::]*/: { /* n:"Formula", */ f:parse_Formula },
20735	/*::[*/0x000a/*::]*/: { /* n:"EOF", */ f:parsenoop2 },
20736	/*::[*/0x000c/*::]*/: { /* n:"CalcCount", */ f:parseuint16 }, //
20737	/*::[*/0x000d/*::]*/: { /* n:"CalcMode", */ f:parseuint16 }, //
20738	/*::[*/0x000e/*::]*/: { /* n:"CalcPrecision", */ f:parsebool }, //
20739	/*::[*/0x000f/*::]*/: { /* n:"CalcRefMode", */ f:parsebool }, //
20740	/*::[*/0x0010/*::]*/: { /* n:"CalcDelta", */ f:parse_Xnum }, //
20741	/*::[*/0x0011/*::]*/: { /* n:"CalcIter", */ f:parsebool }, //
20742	/*::[*/0x0012/*::]*/: { /* n:"Protect", */ f:parsebool },
20743	/*::[*/0x0013/*::]*/: { /* n:"Password", */ f:parseuint16 },
20744	/*::[*/0x0014/*::]*/: { /* n:"Header", */ f:parse_XLHeaderFooter },
20745	/*::[*/0x0015/*::]*/: { /* n:"Footer", */ f:parse_XLHeaderFooter },
20746	/*::[*/0x0017/*::]*/: { /* n:"ExternSheet", */ f:parse_ExternSheet },
20747	/*::[*/0x0018/*::]*/: { /* n:"Lbl", */ f:parse_Lbl },
20748	/*::[*/0x0019/*::]*/: { /* n:"WinProtect", */ f:parsebool },
20749	/*::[*/0x001a/*::]*/: { /* n:"VerticalPageBreaks", */ },
20750	/*::[*/0x001b/*::]*/: { /* n:"HorizontalPageBreaks", */ },
20751	/*::[*/0x001c/*::]*/: { /* n:"Note", */ f:parse_Note },
20752	/*::[*/0x001d/*::]*/: { /* n:"Selection", */ },
20753	/*::[*/0x0022/*::]*/: { /* n:"Date1904", */ f:parsebool },
20754	/*::[*/0x0023/*::]*/: { /* n:"ExternName", */ f:parse_ExternName },
20755	/*::[*/0x0026/*::]*/: { /* n:"LeftMargin", */ f:parse_Xnum }, // *
20756	/*::[*/0x0027/*::]*/: { /* n:"RightMargin", */ f:parse_Xnum }, // *
20757	/*::[*/0x0028/*::]*/: { /* n:"TopMargin", */ f:parse_Xnum }, // *
20758	/*::[*/0x0029/*::]*/: { /* n:"BottomMargin", */ f:parse_Xnum }, // *
20759	/*::[*/0x002a/*::]*/: { /* n:"PrintRowCol", */ f:parsebool },
20760	/*::[*/0x002b/*::]*/: { /* n:"PrintGrid", */ f:parsebool },
20761	/*::[*/0x002f/*::]*/: { /* n:"FilePass", */ f:parse_FilePass },
20762	/*::[*/0x0031/*::]*/: { /* n:"Font", */ f:parse_Font },
20763	/*::[*/0x0033/*::]*/: { /* n:"PrintSize", */ f:parseuint16 },
20764	/*::[*/0x003c/*::]*/: { /* n:"Continue", */ },
20765	/*::[*/0x003d/*::]*/: { /* n:"Window1", */ f:parse_Window1 },
20766	/*::[*/0x0040/*::]*/: { /* n:"Backup", */ f:parsebool },
20767	/*::[*/0x0041/*::]*/: { /* n:"Pane", */ f:parse_Pane },
20768	/*::[*/0x0042/*::]*/: { /* n:"CodePage", */ f:parseuint16 },
20769	/*::[*/0x004d/*::]*/: { /* n:"Pls", */ },
20770	/*::[*/0x0050/*::]*/: { /* n:"DCon", */ },
20771	/*::[*/0x0051/*::]*/: { /* n:"DConRef", */ },
20772	/*::[*/0x0052/*::]*/: { /* n:"DConName", */ },
20773	/*::[*/0x0055/*::]*/: { /* n:"DefColWidth", */ f:parseuint16 },
20774	/*::[*/0x0059/*::]*/: { /* n:"XCT", */ },
20775	/*::[*/0x005a/*::]*/: { /* n:"CRN", */ },
20776	/*::[*/0x005b/*::]*/: { /* n:"FileSharing", */ },
20777	/*::[*/0x005c/*::]*/: { /* n:"WriteAccess", */ f:parse_WriteAccess },
20778	/*::[*/0x005d/*::]*/: { /* n:"Obj", */ f:parse_Obj },
20779	/*::[*/0x005e/*::]*/: { /* n:"Uncalced", */ },
20780	/*::[*/0x005f/*::]*/: { /* n:"CalcSaveRecalc", */ f:parsebool }, //
20781	/*::[*/0x0060/*::]*/: { /* n:"Template", */ },
20782	/*::[*/0x0061/*::]*/: { /* n:"Intl", */ },
20783	/*::[*/0x0063/*::]*/: { /* n:"ObjProtect", */ f:parsebool },
20784	/*::[*/0x007d/*::]*/: { /* n:"ColInfo", */ f:parse_ColInfo },
20785	/*::[*/0x0080/*::]*/: { /* n:"Guts", */ f:parse_Guts },
20786	/*::[*/0x0081/*::]*/: { /* n:"WsBool", */ f:parse_WsBool },
20787	/*::[*/0x0082/*::]*/: { /* n:"GridSet", */ f:parseuint16 },
20788	/*::[*/0x0083/*::]*/: { /* n:"HCenter", */ f:parsebool },
20789	/*::[*/0x0084/*::]*/: { /* n:"VCenter", */ f:parsebool },
20790	/*::[*/0x0085/*::]*/: { /* n:"BoundSheet8", */ f:parse_BoundSheet8 },
20791	/*::[*/0x0086/*::]*/: { /* n:"WriteProtect", */ },
20792	/*::[*/0x008c/*::]*/: { /* n:"Country", */ f:parse_Country },
20793	/*::[*/0x008d/*::]*/: { /* n:"HideObj", */ f:parseuint16 },
20794	/*::[*/0x0090/*::]*/: { /* n:"Sort", */ },
20795	/*::[*/0x0092/*::]*/: { /* n:"Palette", */ f:parse_Palette },
20796	/*::[*/0x0097/*::]*/: { /* n:"Sync", */ },
20797	/*::[*/0x0098/*::]*/: { /* n:"LPr", */ },
20798	/*::[*/0x0099/*::]*/: { /* n:"DxGCol", */ },
20799	/*::[*/0x009a/*::]*/: { /* n:"FnGroupName", */ },
20800	/*::[*/0x009b/*::]*/: { /* n:"FilterMode", */ },
20801	/*::[*/0x009c/*::]*/: { /* n:"BuiltInFnGroupCount", */ f:parseuint16 },
20802	/*::[*/0x009d/*::]*/: { /* n:"AutoFilterInfo", */ },
20803	/*::[*/0x009e/*::]*/: { /* n:"AutoFilter", */ },
20804	/*::[*/0x00a0/*::]*/: { /* n:"Scl", */ f:parse_Scl },
20805	/*::[*/0x00a1/*::]*/: { /* n:"Setup", */ f:parse_Setup },
20806	/*::[*/0x00ae/*::]*/: { /* n:"ScenMan", */ },
20807	/*::[*/0x00af/*::]*/: { /* n:"SCENARIO", */ },
20808	/*::[*/0x00b0/*::]*/: { /* n:"SxView", */ },
20809	/*::[*/0x00b1/*::]*/: { /* n:"Sxvd", */ },
20810	/*::[*/0x00b2/*::]*/: { /* n:"SXVI", */ },
20811	/*::[*/0x00b4/*::]*/: { /* n:"SxIvd", */ },
20812	/*::[*/0x00b5/*::]*/: { /* n:"SXLI", */ },
20813	/*::[*/0x00b6/*::]*/: { /* n:"SXPI", */ },
20814	/*::[*/0x00b8/*::]*/: { /* n:"DocRoute", */ },
20815	/*::[*/0x00b9/*::]*/: { /* n:"RecipName", */ },
20816	/*::[*/0x00bd/*::]*/: { /* n:"MulRk", */ f:parse_MulRk },
20817	/*::[*/0x00be/*::]*/: { /* n:"MulBlank", */ f:parse_MulBlank },
20818	/*::[*/0x00c1/*::]*/: { /* n:"Mms", */ f:parsenoop2 },
20819	/*::[*/0x00c5/*::]*/: { /* n:"SXDI", */ },
20820	/*::[*/0x00c6/*::]*/: { /* n:"SXDB", */ },
20821	/*::[*/0x00c7/*::]*/: { /* n:"SXFDB", */ },
20822	/*::[*/0x00c8/*::]*/: { /* n:"SXDBB", */ },
20823	/*::[*/0x00c9/*::]*/: { /* n:"SXNum", */ },
20824	/*::[*/0x00ca/*::]*/: { /* n:"SxBool", */ f:parsebool },
20825	/*::[*/0x00cb/*::]*/: { /* n:"SxErr", */ },
20826	/*::[*/0x00cc/*::]*/: { /* n:"SXInt", */ },
20827	/*::[*/0x00cd/*::]*/: { /* n:"SXString", */ },
20828	/*::[*/0x00ce/*::]*/: { /* n:"SXDtr", */ },
20829	/*::[*/0x00cf/*::]*/: { /* n:"SxNil", */ },
20830	/*::[*/0x00d0/*::]*/: { /* n:"SXTbl", */ },
20831	/*::[*/0x00d1/*::]*/: { /* n:"SXTBRGIITM", */ },
20832	/*::[*/0x00d2/*::]*/: { /* n:"SxTbpg", */ },
20833	/*::[*/0x00d3/*::]*/: { /* n:"ObProj", */ },
20834	/*::[*/0x00d5/*::]*/: { /* n:"SXStreamID", */ },
20835	/*::[*/0x00d7/*::]*/: { /* n:"DBCell", */ },
20836	/*::[*/0x00d8/*::]*/: { /* n:"SXRng", */ },
20837	/*::[*/0x00d9/*::]*/: { /* n:"SxIsxoper", */ },
20838	/*::[*/0x00da/*::]*/: { /* n:"BookBool", */ f:parseuint16 },
20839	/*::[*/0x00dc/*::]*/: { /* n:"DbOrParamQry", */ },
20840	/*::[*/0x00dd/*::]*/: { /* n:"ScenarioProtect", */ f:parsebool },
20841	/*::[*/0x00de/*::]*/: { /* n:"OleObjectSize", */ },
20842	/*::[*/0x00e0/*::]*/: { /* n:"XF", */ f:parse_XF },
20843	/*::[*/0x00e1/*::]*/: { /* n:"InterfaceHdr", */ f:parse_InterfaceHdr },
20844	/*::[*/0x00e2/*::]*/: { /* n:"InterfaceEnd", */ f:parsenoop2 },
20845	/*::[*/0x00e3/*::]*/: { /* n:"SXVS", */ },
20846	/*::[*/0x00e5/*::]*/: { /* n:"MergeCells", */ f:parse_MergeCells },
20847	/*::[*/0x00e9/*::]*/: { /* n:"BkHim", */ },
20848	/*::[*/0x00eb/*::]*/: { /* n:"MsoDrawingGroup", */ },
20849	/*::[*/0x00ec/*::]*/: { /* n:"MsoDrawing", */ },
20850	/*::[*/0x00ed/*::]*/: { /* n:"MsoDrawingSelection", */ },
20851	/*::[*/0x00ef/*::]*/: { /* n:"PhoneticInfo", */ },
20852	/*::[*/0x00f0/*::]*/: { /* n:"SxRule", */ },
20853	/*::[*/0x00f1/*::]*/: { /* n:"SXEx", */ },
20854	/*::[*/0x00f2/*::]*/: { /* n:"SxFilt", */ },
20855	/*::[*/0x00f4/*::]*/: { /* n:"SxDXF", */ },
20856	/*::[*/0x00f5/*::]*/: { /* n:"SxItm", */ },
20857	/*::[*/0x00f6/*::]*/: { /* n:"SxName", */ },
20858	/*::[*/0x00f7/*::]*/: { /* n:"SxSelect", */ },
20859	/*::[*/0x00f8/*::]*/: { /* n:"SXPair", */ },
20860	/*::[*/0x00f9/*::]*/: { /* n:"SxFmla", */ },
20861	/*::[*/0x00fb/*::]*/: { /* n:"SxFormat", */ },
20862	/*::[*/0x00fc/*::]*/: { /* n:"SST", */ f:parse_SST },
20863	/*::[*/0x00fd/*::]*/: { /* n:"LabelSst", */ f:parse_LabelSst },
20864	/*::[*/0x00ff/*::]*/: { /* n:"ExtSST", */ f:parse_ExtSST },
20865	/*::[*/0x0100/*::]*/: { /* n:"SXVDEx", */ },
20866	/*::[*/0x0103/*::]*/: { /* n:"SXFormula", */ },
20867	/*::[*/0x0122/*::]*/: { /* n:"SXDBEx", */ },
20868	/*::[*/0x0137/*::]*/: { /* n:"RRDInsDel", */ },
20869	/*::[*/0x0138/*::]*/: { /* n:"RRDHead", */ },
20870	/*::[*/0x013b/*::]*/: { /* n:"RRDChgCell", */ },
20871	/*::[*/0x013d/*::]*/: { /* n:"RRTabId", */ f:parseuint16a },
20872	/*::[*/0x013e/*::]*/: { /* n:"RRDRenSheet", */ },
20873	/*::[*/0x013f/*::]*/: { /* n:"RRSort", */ },
20874	/*::[*/0x0140/*::]*/: { /* n:"RRDMove", */ },
20875	/*::[*/0x014a/*::]*/: { /* n:"RRFormat", */ },
20876	/*::[*/0x014b/*::]*/: { /* n:"RRAutoFmt", */ },
20877	/*::[*/0x014d/*::]*/: { /* n:"RRInsertSh", */ },
20878	/*::[*/0x014e/*::]*/: { /* n:"RRDMoveBegin", */ },
20879	/*::[*/0x014f/*::]*/: { /* n:"RRDMoveEnd", */ },
20880	/*::[*/0x0150/*::]*/: { /* n:"RRDInsDelBegin", */ },
20881	/*::[*/0x0151/*::]*/: { /* n:"RRDInsDelEnd", */ },
20882	/*::[*/0x0152/*::]*/: { /* n:"RRDConflict", */ },
20883	/*::[*/0x0153/*::]*/: { /* n:"RRDDefName", */ },
20884	/*::[*/0x0154/*::]*/: { /* n:"RRDRstEtxp", */ },
20885	/*::[*/0x015f/*::]*/: { /* n:"LRng", */ },
20886	/*::[*/0x0160/*::]*/: { /* n:"UsesELFs", */ f:parsebool },
20887	/*::[*/0x0161/*::]*/: { /* n:"DSF", */ f:parsenoop2 },
20888	/*::[*/0x0191/*::]*/: { /* n:"CUsr", */ },
20889	/*::[*/0x0192/*::]*/: { /* n:"CbUsr", */ },
20890	/*::[*/0x0193/*::]*/: { /* n:"UsrInfo", */ },
20891	/*::[*/0x0194/*::]*/: { /* n:"UsrExcl", */ },
20892	/*::[*/0x0195/*::]*/: { /* n:"FileLock", */ },
20893	/*::[*/0x0196/*::]*/: { /* n:"RRDInfo", */ },
20894	/*::[*/0x0197/*::]*/: { /* n:"BCUsrs", */ },
20895	/*::[*/0x0198/*::]*/: { /* n:"UsrChk", */ },
20896	/*::[*/0x01a9/*::]*/: { /* n:"UserBView", */ },
20897	/*::[*/0x01aa/*::]*/: { /* n:"UserSViewBegin", */ },
20898	/*::[*/0x01ab/*::]*/: { /* n:"UserSViewEnd", */ },
20899	/*::[*/0x01ac/*::]*/: { /* n:"RRDUserView", */ },
20900	/*::[*/0x01ad/*::]*/: { /* n:"Qsi", */ },
20901	/*::[*/0x01ae/*::]*/: { /* n:"SupBook", */ f:parse_SupBook },
20902	/*::[*/0x01af/*::]*/: { /* n:"Prot4Rev", */ f:parsebool },
20903	/*::[*/0x01b0/*::]*/: { /* n:"CondFmt", */ },
20904	/*::[*/0x01b1/*::]*/: { /* n:"CF", */ },
20905	/*::[*/0x01b2/*::]*/: { /* n:"DVal", */ },
20906	/*::[*/0x01b5/*::]*/: { /* n:"DConBin", */ },
20907	/*::[*/0x01b6/*::]*/: { /* n:"TxO", */ f:parse_TxO },
20908	/*::[*/0x01b7/*::]*/: { /* n:"RefreshAll", */ f:parsebool }, //
20909	/*::[*/0x01b8/*::]*/: { /* n:"HLink", */ f:parse_HLink },
20910	/*::[*/0x01b9/*::]*/: { /* n:"Lel", */ },
20911	/*::[*/0x01ba/*::]*/: { /* n:"CodeName", */ f:parse_XLUnicodeString },
20912	/*::[*/0x01bb/*::]*/: { /* n:"SXFDBType", */ },
20913	/*::[*/0x01bc/*::]*/: { /* n:"Prot4RevPass", */ f:parseuint16 },
20914	/*::[*/0x01bd/*::]*/: { /* n:"ObNoMacros", */ },
20915	/*::[*/0x01be/*::]*/: { /* n:"Dv", */ },
20916	/*::[*/0x01c0/*::]*/: { /* n:"Excel9File", */ f:parsenoop2 },
20917	/*::[*/0x01c1/*::]*/: { /* n:"RecalcId", */ f:parse_RecalcId, r:2},
20918	/*::[*/0x01c2/*::]*/: { /* n:"EntExU2", */ f:parsenoop2 },
20919	/*::[*/0x0200/*::]*/: { /* n:"Dimensions", */ f:parse_Dimensions },
20920	/*::[*/0x0201/*::]*/: { /* n:"Blank", */ f:parse_Blank },
20921	/*::[*/0x0203/*::]*/: { /* n:"Number", */ f:parse_Number },
20922	/*::[*/0x0204/*::]*/: { /* n:"Label", */ f:parse_Label },
20923	/*::[*/0x0205/*::]*/: { /* n:"BoolErr", */ f:parse_BoolErr },
20924	/*::[*/0x0207/*::]*/: { /* n:"String", */ f:parse_String },
20925	/*::[*/0x0208/*::]*/: { /* n:"Row", */ f:parse_Row },
20926	/*::[*/0x020b/*::]*/: { /* n:"Index", */ },
20927	/*::[*/0x0221/*::]*/: { /* n:"Array", */ f:parse_Array },
20928	/*::[*/0x0225/*::]*/: { /* n:"DefaultRowHeight", */ f:parse_DefaultRowHeight },
20929	/*::[*/0x0236/*::]*/: { /* n:"Table", */ },
20930	/*::[*/0x023e/*::]*/: { /* n:"Window2", */ f:parse_Window2 },
20931	/*::[*/0x027e/*::]*/: { /* n:"RK", */ f:parse_RK },
20932	/*::[*/0x0293/*::]*/: { /* n:"Style", */ },
20933	/*::[*/0x0418/*::]*/: { /* n:"BigName", */ },
20934	/*::[*/0x041e/*::]*/: { /* n:"Format", */ f:parse_Format },
20935	/*::[*/0x043c/*::]*/: { /* n:"ContinueBigName", */ },
20936	/*::[*/0x04bc/*::]*/: { /* n:"ShrFmla", */ f:parse_ShrFmla },
20937	/*::[*/0x0800/*::]*/: { /* n:"HLinkTooltip", */ f:parse_HLinkTooltip },
20938	/*::[*/0x0801/*::]*/: { /* n:"WebPub", */ },
20939	/*::[*/0x0802/*::]*/: { /* n:"QsiSXTag", */ },
20940	/*::[*/0x0803/*::]*/: { /* n:"DBQueryExt", */ },
20941	/*::[*/0x0804/*::]*/: { /* n:"ExtString", */ },
20942	/*::[*/0x0805/*::]*/: { /* n:"TxtQry", */ },
20943	/*::[*/0x0806/*::]*/: { /* n:"Qsir", */ },
20944	/*::[*/0x0807/*::]*/: { /* n:"Qsif", */ },
20945	/*::[*/0x0808/*::]*/: { /* n:"RRDTQSIF", */ },
20946	/*::[*/0x0809/*::]*/: { /* n:"BOF", */ f:parse_BOF },
20947	/*::[*/0x080a/*::]*/: { /* n:"OleDbConn", */ },
20948	/*::[*/0x080b/*::]*/: { /* n:"WOpt", */ },
20949	/*::[*/0x080c/*::]*/: { /* n:"SXViewEx", */ },
20950	/*::[*/0x080d/*::]*/: { /* n:"SXTH", */ },
20951	/*::[*/0x080e/*::]*/: { /* n:"SXPIEx", */ },
20952	/*::[*/0x080f/*::]*/: { /* n:"SXVDTEx", */ },
20953	/*::[*/0x0810/*::]*/: { /* n:"SXViewEx9", */ },
20954	/*::[*/0x0812/*::]*/: { /* n:"ContinueFrt", */ },
20955	/*::[*/0x0813/*::]*/: { /* n:"RealTimeData", */ },
20956	/*::[*/0x0850/*::]*/: { /* n:"ChartFrtInfo", */ },
20957	/*::[*/0x0851/*::]*/: { /* n:"FrtWrapper", */ },
20958	/*::[*/0x0852/*::]*/: { /* n:"StartBlock", */ },
20959	/*::[*/0x0853/*::]*/: { /* n:"EndBlock", */ },
20960	/*::[*/0x0854/*::]*/: { /* n:"StartObject", */ },
20961	/*::[*/0x0855/*::]*/: { /* n:"EndObject", */ },
20962	/*::[*/0x0856/*::]*/: { /* n:"CatLab", */ },
20963	/*::[*/0x0857/*::]*/: { /* n:"YMult", */ },
20964	/*::[*/0x0858/*::]*/: { /* n:"SXViewLink", */ },
20965	/*::[*/0x0859/*::]*/: { /* n:"PivotChartBits", */ },
20966	/*::[*/0x085a/*::]*/: { /* n:"FrtFontList", */ },
20967	/*::[*/0x0862/*::]*/: { /* n:"SheetExt", */ },
20968	/*::[*/0x0863/*::]*/: { /* n:"BookExt", */ r:12},
20969	/*::[*/0x0864/*::]*/: { /* n:"SXAddl", */ },
20970	/*::[*/0x0865/*::]*/: { /* n:"CrErr", */ },
20971	/*::[*/0x0866/*::]*/: { /* n:"HFPicture", */ },
20972	/*::[*/0x0867/*::]*/: { /* n:"FeatHdr", */ f:parsenoop2 },
20973	/*::[*/0x0868/*::]*/: { /* n:"Feat", */ },
20974	/*::[*/0x086a/*::]*/: { /* n:"DataLabExt", */ },
20975	/*::[*/0x086b/*::]*/: { /* n:"DataLabExtContents", */ },
20976	/*::[*/0x086c/*::]*/: { /* n:"CellWatch", */ },
20977	/*::[*/0x0871/*::]*/: { /* n:"FeatHdr11", */ },
20978	/*::[*/0x0872/*::]*/: { /* n:"Feature11", */ },
20979	/*::[*/0x0874/*::]*/: { /* n:"DropDownObjIds", */ },
20980	/*::[*/0x0875/*::]*/: { /* n:"ContinueFrt11", */ },
20981	/*::[*/0x0876/*::]*/: { /* n:"DConn", */ },
20982	/*::[*/0x0877/*::]*/: { /* n:"List12", */ },
20983	/*::[*/0x0878/*::]*/: { /* n:"Feature12", */ },
20984	/*::[*/0x0879/*::]*/: { /* n:"CondFmt12", */ },
20985	/*::[*/0x087a/*::]*/: { /* n:"CF12", */ },
20986	/*::[*/0x087b/*::]*/: { /* n:"CFEx", */ },
20987	/*::[*/0x087c/*::]*/: { /* n:"XFCRC", */ f:parse_XFCRC, r:12 },
20988	/*::[*/0x087d/*::]*/: { /* n:"XFExt", */ f:parse_XFExt, r:12 },
20989	/*::[*/0x087e/*::]*/: { /* n:"AutoFilter12", */ },
20990	/*::[*/0x087f/*::]*/: { /* n:"ContinueFrt12", */ },
20991	/*::[*/0x0884/*::]*/: { /* n:"MDTInfo", */ },
20992	/*::[*/0x0885/*::]*/: { /* n:"MDXStr", */ },
20993	/*::[*/0x0886/*::]*/: { /* n:"MDXTuple", */ },
20994	/*::[*/0x0887/*::]*/: { /* n:"MDXSet", */ },
20995	/*::[*/0x0888/*::]*/: { /* n:"MDXProp", */ },
20996	/*::[*/0x0889/*::]*/: { /* n:"MDXKPI", */ },
20997	/*::[*/0x088a/*::]*/: { /* n:"MDB", */ },
20998	/*::[*/0x088b/*::]*/: { /* n:"PLV", */ },
20999	/*::[*/0x088c/*::]*/: { /* n:"Compat12", */ f:parsebool, r:12 },
21000	/*::[*/0x088d/*::]*/: { /* n:"DXF", */ },
21001	/*::[*/0x088e/*::]*/: { /* n:"TableStyles", */ r:12 },
21002	/*::[*/0x088f/*::]*/: { /* n:"TableStyle", */ },
21003	/*::[*/0x0890/*::]*/: { /* n:"TableStyleElement", */ },
21004	/*::[*/0x0892/*::]*/: { /* n:"StyleExt", */ },
21005	/*::[*/0x0893/*::]*/: { /* n:"NamePublish", */ },
21006	/*::[*/0x0894/*::]*/: { /* n:"NameCmt", */ f:parse_NameCmt, r:12 },
21007	/*::[*/0x0895/*::]*/: { /* n:"SortData", */ },
21008	/*::[*/0x0896/*::]*/: { /* n:"Theme", */ f:parse_Theme, r:12 },
21009	/*::[*/0x0897/*::]*/: { /* n:"GUIDTypeLib", */ },
21010	/*::[*/0x0898/*::]*/: { /* n:"FnGrp12", */ },
21011	/*::[*/0x0899/*::]*/: { /* n:"NameFnGrp12", */ },
21012	/*::[*/0x089a/*::]*/: { /* n:"MTRSettings", */ f:parse_MTRSettings, r:12 },
21013	/*::[*/0x089b/*::]*/: { /* n:"CompressPictures", */ f:parsenoop2 },
21014	/*::[*/0x089c/*::]*/: { /* n:"HeaderFooter", */ },
21015	/*::[*/0x089d/*::]*/: { /* n:"CrtLayout12", */ },
21016	/*::[*/0x089e/*::]*/: { /* n:"CrtMlFrt", */ },
21017	/*::[*/0x089f/*::]*/: { /* n:"CrtMlFrtContinue", */ },
21018	/*::[*/0x08a3/*::]*/: { /* n:"ForceFullCalculation", */ f:parse_ForceFullCalculation },
21019	/*::[*/0x08a4/*::]*/: { /* n:"ShapePropsStream", */ },
21020	/*::[*/0x08a5/*::]*/: { /* n:"TextPropsStream", */ },
21021	/*::[*/0x08a6/*::]*/: { /* n:"RichTextStream", */ },
21022	/*::[*/0x08a7/*::]*/: { /* n:"CrtLayout12A", */ },
21023	/*::[*/0x1001/*::]*/: { /* n:"Units", */ },
21024	/*::[*/0x1002/*::]*/: { /* n:"Chart", */ },
21025	/*::[*/0x1003/*::]*/: { /* n:"Series", */ },
21026	/*::[*/0x1006/*::]*/: { /* n:"DataFormat", */ },
21027	/*::[*/0x1007/*::]*/: { /* n:"LineFormat", */ },
21028	/*::[*/0x1009/*::]*/: { /* n:"MarkerFormat", */ },
21029	/*::[*/0x100a/*::]*/: { /* n:"AreaFormat", */ },
21030	/*::[*/0x100b/*::]*/: { /* n:"PieFormat", */ },
21031	/*::[*/0x100c/*::]*/: { /* n:"AttachedLabel", */ },
21032	/*::[*/0x100d/*::]*/: { /* n:"SeriesText", */ },
21033	/*::[*/0x1014/*::]*/: { /* n:"ChartFormat", */ },
21034	/*::[*/0x1015/*::]*/: { /* n:"Legend", */ },
21035	/*::[*/0x1016/*::]*/: { /* n:"SeriesList", */ },
21036	/*::[*/0x1017/*::]*/: { /* n:"Bar", */ },
21037	/*::[*/0x1018/*::]*/: { /* n:"Line", */ },
21038	/*::[*/0x1019/*::]*/: { /* n:"Pie", */ },
21039	/*::[*/0x101a/*::]*/: { /* n:"Area", */ },
21040	/*::[*/0x101b/*::]*/: { /* n:"Scatter", */ },
21041	/*::[*/0x101c/*::]*/: { /* n:"CrtLine", */ },
21042	/*::[*/0x101d/*::]*/: { /* n:"Axis", */ },
21043	/*::[*/0x101e/*::]*/: { /* n:"Tick", */ },
21044	/*::[*/0x101f/*::]*/: { /* n:"ValueRange", */ },
21045	/*::[*/0x1020/*::]*/: { /* n:"CatSerRange", */ },
21046	/*::[*/0x1021/*::]*/: { /* n:"AxisLine", */ },
21047	/*::[*/0x1022/*::]*/: { /* n:"CrtLink", */ },
21048	/*::[*/0x1024/*::]*/: { /* n:"DefaultText", */ },
21049	/*::[*/0x1025/*::]*/: { /* n:"Text", */ },
21050	/*::[*/0x1026/*::]*/: { /* n:"FontX", */ f:parseuint16 },
21051	/*::[*/0x1027/*::]*/: { /* n:"ObjectLink", */ },
21052	/*::[*/0x1032/*::]*/: { /* n:"Frame", */ },
21053	/*::[*/0x1033/*::]*/: { /* n:"Begin", */ },
21054	/*::[*/0x1034/*::]*/: { /* n:"End", */ },
21055	/*::[*/0x1035/*::]*/: { /* n:"PlotArea", */ },
21056	/*::[*/0x103a/*::]*/: { /* n:"Chart3d", */ },
21057	/*::[*/0x103c/*::]*/: { /* n:"PicF", */ },
21058	/*::[*/0x103d/*::]*/: { /* n:"DropBar", */ },
21059	/*::[*/0x103e/*::]*/: { /* n:"Radar", */ },
21060	/*::[*/0x103f/*::]*/: { /* n:"Surf", */ },
21061	/*::[*/0x1040/*::]*/: { /* n:"RadarArea", */ },
21062	/*::[*/0x1041/*::]*/: { /* n:"AxisParent", */ },
21063	/*::[*/0x1043/*::]*/: { /* n:"LegendException", */ },
21064	/*::[*/0x1044/*::]*/: { /* n:"ShtProps", */ f:parse_ShtProps },
21065	/*::[*/0x1045/*::]*/: { /* n:"SerToCrt", */ },
21066	/*::[*/0x1046/*::]*/: { /* n:"AxesUsed", */ },
21067	/*::[*/0x1048/*::]*/: { /* n:"SBaseRef", */ },
21068	/*::[*/0x104a/*::]*/: { /* n:"SerParent", */ },
21069	/*::[*/0x104b/*::]*/: { /* n:"SerAuxTrend", */ },
21070	/*::[*/0x104e/*::]*/: { /* n:"IFmtRecord", */ },
21071	/*::[*/0x104f/*::]*/: { /* n:"Pos", */ },
21072	/*::[*/0x1050/*::]*/: { /* n:"AlRuns", */ },
21073	/*::[*/0x1051/*::]*/: { /* n:"BRAI", */ },
21074	/*::[*/0x105b/*::]*/: { /* n:"SerAuxErrBar", */ },
21075	/*::[*/0x105c/*::]*/: { /* n:"ClrtClient", */ f:parse_ClrtClient },
21076	/*::[*/0x105d/*::]*/: { /* n:"SerFmt", */ },
21077	/*::[*/0x105f/*::]*/: { /* n:"Chart3DBarShape", */ },
21078	/*::[*/0x1060/*::]*/: { /* n:"Fbi", */ },
21079	/*::[*/0x1061/*::]*/: { /* n:"BopPop", */ },
21080	/*::[*/0x1062/*::]*/: { /* n:"AxcExt", */ },
21081	/*::[*/0x1063/*::]*/: { /* n:"Dat", */ },
21082	/*::[*/0x1064/*::]*/: { /* n:"PlotGrowth", */ },
21083	/*::[*/0x1065/*::]*/: { /* n:"SIIndex", */ },
21084	/*::[*/0x1066/*::]*/: { /* n:"GelFrame", */ },
21085	/*::[*/0x1067/*::]*/: { /* n:"BopPopCustom", */ },
21086	/*::[*/0x1068/*::]*/: { /* n:"Fbi2", */ },
21087
21088	/*::[*/0x0000/*::]*/: { /* n:"Dimensions", */ f:parse_Dimensions },
21089	/*::[*/0x0001/*::]*/: { /* n:"BIFF2BLANK", */ },
21090	/*::[*/0x0002/*::]*/: { /* n:"BIFF2INT", */ f:parse_BIFF2INT },
21091	/*::[*/0x0003/*::]*/: { /* n:"BIFF2NUM", */ f:parse_BIFF2NUM },
21092	/*::[*/0x0004/*::]*/: { /* n:"BIFF2STR", */ f:parse_BIFF2STR },
21093	/*::[*/0x0005/*::]*/: { /* n:"BoolErr", */ f:parse_BoolErr },
21094	/*::[*/0x0007/*::]*/: { /* n:"String", */ f:parse_BIFF2STRING },
21095	/*::[*/0x0008/*::]*/: { /* n:"BIFF2ROW", */ },
21096	/*::[*/0x0009/*::]*/: { /* n:"BOF", */ f:parse_BOF },
21097	/*::[*/0x000b/*::]*/: { /* n:"Index", */ },
21098	/*::[*/0x0016/*::]*/: { /* n:"ExternCount", */ f:parseuint16 },
21099	/*::[*/0x001e/*::]*/: { /* n:"BIFF2FORMAT", */ f:parse_BIFF2Format },
21100	/*::[*/0x001f/*::]*/: { /* n:"BIFF2FMTCNT", */ }, /* 16-bit cnt of BIFF2FORMAT records */
21101	/*::[*/0x0020/*::]*/: { /* n:"BIFF2COLINFO", */ },
21102	/*::[*/0x0021/*::]*/: { /* n:"Array", */ f:parse_Array },
21103	/*::[*/0x0024/*::]*/: { /* n:"COLWIDTH", */ },
21104	/*::[*/0x0025/*::]*/: { /* n:"DefaultRowHeight", */ f:parse_DefaultRowHeight },
21105	// 0x2c ??
21106	// 0x2d ??
21107	// 0x2e ??
21108	// 0x30 FONTCOUNT: number of fonts
21109	/*::[*/0x0032/*::]*/: { /* n:"BIFF2FONTXTRA", */ f:parse_BIFF2FONTXTRA },
21110	// 0x35: INFOOPTS
21111	// 0x36: TABLE (BIFF2 only)
21112	// 0x37: TABLE2 (BIFF2 only)
21113	// 0x38: WNDESK
21114	// 0x39 ??
21115	// 0x3a: BEGINPREF
21116	// 0x3b: ENDPREF
21117	/*::[*/0x003e/*::]*/: { /* n:"BIFF2WINDOW2", */ },
21118	// 0x3f ??
21119	// 0x46: SHOWSCROLL
21120	// 0x47: SHOWFORMULA
21121	// 0x48: STATUSBAR
21122	// 0x49: SHORTMENUS
21123	// 0x4A:
21124	// 0x4B:
21125	// 0x4C:
21126	// 0x4E:
21127	// 0x4F:
21128	// 0x58: TOOLBAR (BIFF3)
21129
21130	/* - - - */
21131	/*::[*/0x0034/*::]*/: { /* n:"DDEObjName", */ },
21132	/*::[*/0x0043/*::]*/: { /* n:"BIFF2XF", */ },
21133	/*::[*/0x0044/*::]*/: { /* n:"BIFF2XFINDEX", */ f:parseuint16 },
21134	/*::[*/0x0045/*::]*/: { /* n:"BIFF2FONTCLR", */ },
21135	/*::[*/0x0056/*::]*/: { /* n:"BIFF4FMTCNT", */ }, /* 16-bit cnt, similar to BIFF2 */
21136	/*::[*/0x007e/*::]*/: { /* n:"RK", */ }, /* Not necessarily same as 0x027e */
21137	/*::[*/0x007f/*::]*/: { /* n:"ImData", */ f:parse_ImData },
21138	/*::[*/0x0087/*::]*/: { /* n:"Addin", */ },
21139	/*::[*/0x0088/*::]*/: { /* n:"Edg", */ },
21140	/*::[*/0x0089/*::]*/: { /* n:"Pub", */ },
21141	// 0x8A
21142	// 0x8B LH: alternate menu key flag (BIFF3/4)
21143	// 0x8E
21144	// 0x8F
21145	/*::[*/0x0091/*::]*/: { /* n:"Sub", */ },
21146	// 0x93 STYLE
21147	/*::[*/0x0094/*::]*/: { /* n:"LHRecord", */ },
21148	/*::[*/0x0095/*::]*/: { /* n:"LHNGraph", */ },
21149	/*::[*/0x0096/*::]*/: { /* n:"Sound", */ },
21150	// 0xA2 FNPROTO: function prototypes (BIFF4)
21151	// 0xA3
21152	// 0xA8
21153	/*::[*/0x00a9/*::]*/: { /* n:"CoordList", */ },
21154	/*::[*/0x00ab/*::]*/: { /* n:"GCW", */ },
21155	/*::[*/0x00bc/*::]*/: { /* n:"ShrFmla", */ }, /* Not necessarily same as 0x04bc */
21156	/*::[*/0x00bf/*::]*/: { /* n:"ToolbarHdr", */ },
21157	/*::[*/0x00c0/*::]*/: { /* n:"ToolbarEnd", */ },
21158	/*::[*/0x00c2/*::]*/: { /* n:"AddMenu", */ },
21159	/*::[*/0x00c3/*::]*/: { /* n:"DelMenu", */ },
21160	/*::[*/0x00d6/*::]*/: { /* n:"RString", */ f:parse_RString },
21161	/*::[*/0x00df/*::]*/: { /* n:"UDDesc", */ },
21162	/*::[*/0x00ea/*::]*/: { /* n:"TabIdConf", */ },
21163	/*::[*/0x0162/*::]*/: { /* n:"XL5Modify", */ },
21164	/*::[*/0x01a5/*::]*/: { /* n:"FileSharing2", */ },
21165	/*::[*/0x0206/*::]*/: { /* n:"Formula", */ f:parse_Formula },
21166	/*::[*/0x0209/*::]*/: { /* n:"BOF", */ f:parse_BOF },
21167	/*::[*/0x0218/*::]*/: { /* n:"Lbl", */ f:parse_Lbl },
21168	/*::[*/0x0223/*::]*/: { /* n:"ExternName", */ f:parse_ExternName },
21169	/*::[*/0x0231/*::]*/: { /* n:"Font", */ },
21170	/*::[*/0x0243/*::]*/: { /* n:"BIFF3XF", */ },
21171	/*::[*/0x0406/*::]*/: { /* n:"Formula", */ f:parse_Formula },
21172	/*::[*/0x0409/*::]*/: { /* n:"BOF", */ f:parse_BOF },
21173	/*::[*/0x0443/*::]*/: { /* n:"BIFF4XF", */ },
21174	/*::[*/0x086d/*::]*/: { /* n:"FeatInfo", */ },
21175	/*::[*/0x0873/*::]*/: { /* n:"FeatInfo11", */ },
21176	/*::[*/0x0881/*::]*/: { /* n:"SXAddl12", */ },
21177	/*::[*/0x08c0/*::]*/: { /* n:"AutoWebPub", */ },
21178	/*::[*/0x08c1/*::]*/: { /* n:"ListObj", */ },
21179	/*::[*/0x08c2/*::]*/: { /* n:"ListField", */ },
21180	/*::[*/0x08c3/*::]*/: { /* n:"ListDV", */ },
21181	/*::[*/0x08c4/*::]*/: { /* n:"ListCondFmt", */ },
21182	/*::[*/0x08c5/*::]*/: { /* n:"ListCF", */ },
21183	/*::[*/0x08c6/*::]*/: { /* n:"FMQry", */ },
21184	/*::[*/0x08c7/*::]*/: { /* n:"FMSQry", */ },
21185	/*::[*/0x08c8/*::]*/: { /* n:"PLV", */ },
21186	/*::[*/0x08c9/*::]*/: { /* n:"LnExt", */ },
21187	/*::[*/0x08ca/*::]*/: { /* n:"MkrExt", */ },
21188	/*::[*/0x08cb/*::]*/: { /* n:"CrtCoopt", */ },
21189	/*::[*/0x08d6/*::]*/: { /* n:"FRTArchId$", */ r:12 },
21190
21191	/* --- multiplan 4 records --- */
21192	/*::[*/0x0065/*::]*/: { /* n:"", */ }, // one per window
21193	/*::[*/0x0066/*::]*/: { /* n:"", */ }, // calc settings
21194	/*::[*/0x0069/*::]*/: { /* n:"", */ }, // print header
21195	/*::[*/0x006a/*::]*/: { /* n:"", */ }, // print footer
21196	/*::[*/0x006b/*::]*/: { /* n:"", */ }, // print settings
21197	/*::[*/0x006d/*::]*/: { /* n:"", */ }, // one per window
21198	/*::[*/0x0070/*::]*/: { /* n:"", */ }, // includes default col width
21199	/*::[*/0x0072/*::]*/: { /* n:"", */ }, // includes selected cell
21200
21201	/*::[*/0x7262/*::]*/: {}
21202};
21203
21204function write_biff_rec(ba/*:BufArray*/, type/*:number*/, payload, length/*:?number*/)/*:void*/ {
21205	var t/*:number*/ = type;
21206	if(isNaN(t)) return;
21207	var len = length || (payload||[]).length || 0;
21208	var o = ba.next(4);
21209	o.write_shift(2, t);
21210	o.write_shift(2, len);
21211	if(/*:: len != null &&*/len > 0 && is_buf(payload)) ba.push(payload);
21212}
21213
21214function write_biff_continue(ba/*:BufArray*/, type/*:number*/, payload, length/*:?number*/)/*:void*/ {
21215	var len = length || (payload||[]).length || 0;
21216	if(len <= 8224) return write_biff_rec(ba, type, payload, len);
21217	var t = type;
21218	if(isNaN(t)) return;
21219	var parts = payload.parts || [], sidx = 0;
21220	var i = 0, w = 0;
21221	while(w + (parts[sidx] || 8224) <= 8224) { w+= (parts[sidx] || 8224); sidx++; }
21222	var o = ba.next(4);
21223	o.write_shift(2, t);
21224	o.write_shift(2, w);
21225	ba.push(payload.slice(i, i + w));
21226	i += w;
21227	while(i < len) {
21228		o = ba.next(4);
21229		o.write_shift(2, 0x3c); // TODO: figure out correct continue type
21230		w = 0;
21231		while(w + (parts[sidx] || 8224) <= 8224) { w+= (parts[sidx] || 8224); sidx++; }
21232		o.write_shift(2, w);
21233		ba.push(payload.slice(i, i+w)); i+= w;
21234	}
21235}
21236
21237function write_BIFF2Cell(out, r/*:number*/, c/*:number*/) {
21238	if(!out) out = new_buf(7);
21239	out.write_shift(2, r);
21240	out.write_shift(2, c);
21241	out.write_shift(2, 0);
21242	out.write_shift(1, 0);
21243	return out;
21244}
21245
21246function write_BIFF2BERR(r/*:number*/, c/*:number*/, val, t/*:?string*/) {
21247	var out = new_buf(9);
21248	write_BIFF2Cell(out, r, c);
21249	write_Bes(val, t || 'b', out);
21250	return out;
21251}
21252
21253/* TODO: codepage, large strings */
21254function write_BIFF2LABEL(r/*:number*/, c/*:number*/, val) {
21255	var out = new_buf(8 + 2*val.length);
21256	write_BIFF2Cell(out, r, c);
21257	out.write_shift(1, val.length);
21258	out.write_shift(val.length, val, 'sbcs');
21259	return out.l < out.length ? out.slice(0, out.l) : out;
21260}
21261
21262function write_ws_biff2_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*//*::, opts*/) {
21263	if(cell.v != null) switch(cell.t) {
21264		case 'd': case 'n':
21265			var v = cell.t == 'd' ? datenum(parseDate(cell.v)) : cell.v;
21266			if((v == (v|0)) && (v >= 0) && (v < 65536))
21267				write_biff_rec(ba, 0x0002, write_BIFF2INT(R, C, v));
21268			else
21269				write_biff_rec(ba, 0x0003, write_BIFF2NUM(R,C, v));
21270			return;
21271		case 'b': case 'e': write_biff_rec(ba, 0x0005, write_BIFF2BERR(R, C, cell.v, cell.t)); return;
21272		/* TODO: codepage, sst */
21273		case 's': case 'str':
21274			write_biff_rec(ba, 0x0004, write_BIFF2LABEL(R, C, cell.v == null ? "" : String(cell.v).slice(0,255)));
21275			return;
21276	}
21277	write_biff_rec(ba, 0x0001, write_BIFF2Cell(null, R, C));
21278}
21279
21280function write_ws_biff2(ba/*:BufArray*/, ws/*:Worksheet*/, idx/*:number*/, opts/*::, wb:Workbook*/) {
21281	var dense = Array.isArray(ws);
21282	var range = safe_decode_range(ws['!ref'] || "A1"), ref/*:string*/, rr = "", cols/*:Array<string>*/ = [];
21283	if(range.e.c > 0xFF || range.e.r > 0x3FFF) {
21284		if(opts.WTF) throw new Error("Range " + (ws['!ref'] || "A1") + " exceeds format limit A1:IV16384");
21285		range.e.c = Math.min(range.e.c, 0xFF);
21286		range.e.r = Math.min(range.e.c, 0x3FFF);
21287		ref = encode_range(range);
21288	}
21289	for(var R = range.s.r; R <= range.e.r; ++R) {
21290		rr = encode_row(R);
21291		for(var C = range.s.c; C <= range.e.c; ++C) {
21292			if(R === range.s.r) cols[C] = encode_col(C);
21293			ref = cols[C] + rr;
21294			var cell = dense ? (ws[R]||[])[C] : ws[ref];
21295			if(!cell) continue;
21296			/* write cell */
21297			write_ws_biff2_cell(ba, cell, R, C, opts);
21298		}
21299	}
21300}
21301
21302/* Based on test files */
21303function write_biff2_buf(wb/*:Workbook*/, opts/*:WriteOpts*/) {
21304	var o = opts || {};
21305	if(DENSE != null && o.dense == null) o.dense = DENSE;
21306	var ba = buf_array();
21307	var idx = 0;
21308	for(var i=0;i<wb.SheetNames.length;++i) if(wb.SheetNames[i] == o.sheet) idx=i;
21309	if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
21310	write_biff_rec(ba, (o.biff == 4 ? 0x0409 : (o.biff == 3 ? 0x0209 : 0x0009)), write_BOF(wb, 0x10, o));
21311	/* ... */
21312	write_ws_biff2(ba, wb.Sheets[wb.SheetNames[idx]], idx, o, wb);
21313	/* ... */
21314	write_biff_rec(ba, 0x000A);
21315	return ba.end();
21316}
21317
21318function write_FONTS_biff8(ba, data, opts) {
21319	write_biff_rec(ba, 0x0031 /* Font */, write_Font({
21320		sz:12,
21321		color: {theme:1},
21322		name: "Arial",
21323		family: 2,
21324		scheme: "minor"
21325	}, opts));
21326}
21327
21328
21329function write_FMTS_biff8(ba, NF/*:?SSFTable*/, opts) {
21330	if(!NF) return;
21331	[[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) {
21332		/*:: if(!NF) return; */
21333		for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) write_biff_rec(ba, 0x041E /* Format */, write_Format(i, NF[i], opts));
21334	});
21335}
21336
21337function write_FEAT(ba, ws) {
21338	/* [MS-XLS] 2.4.112 */
21339	var o = new_buf(19);
21340	o.write_shift(4, 0x867); o.write_shift(4, 0); o.write_shift(4, 0);
21341	o.write_shift(2, 3); o.write_shift(1, 1); o.write_shift(4, 0);
21342	write_biff_rec(ba, 0x0867 /* FeatHdr */, o);
21343	/* [MS-XLS] 2.4.111 */
21344	o = new_buf(39);
21345	o.write_shift(4, 0x868); o.write_shift(4, 0); o.write_shift(4, 0);
21346	o.write_shift(2, 3); o.write_shift(1, 0); o.write_shift(4, 0);
21347	o.write_shift(2, 1); o.write_shift(4, 4); o.write_shift(2, 0);
21348	write_Ref8U(safe_decode_range(ws['!ref']||"A1"), o);
21349	o.write_shift(4, 4);
21350	write_biff_rec(ba, 0x0868 /* Feat */, o);
21351}
21352
21353function write_CELLXFS_biff8(ba, opts) {
21354	for(var i = 0; i < 16; ++i) write_biff_rec(ba, 0x00e0 /* XF */, write_XF({numFmtId:0, style:true}, 0, opts));
21355	opts.cellXfs.forEach(function(c) {
21356		write_biff_rec(ba, 0x00e0 /* XF */, write_XF(c, 0, opts));
21357	});
21358}
21359
21360function write_ws_biff8_hlinks(ba/*:BufArray*/, ws) {
21361	for(var R=0; R<ws['!links'].length; ++R) {
21362		var HL = ws['!links'][R];
21363		write_biff_rec(ba, 0x01b8 /* HLink */, write_HLink(HL));
21364		if(HL[1].Tooltip) write_biff_rec(ba, 0x0800 /* HLinkTooltip */, write_HLinkTooltip(HL));
21365	}
21366	delete ws['!links'];
21367}
21368
21369function write_ws_cols_biff8(ba, cols) {
21370	if(!cols) return;
21371	var cnt = 0;
21372	cols.forEach(function(col, idx) {
21373		if(++cnt <= 256 && col) {
21374			write_biff_rec(ba, 0x007d /* ColInfo */, write_ColInfo(col_obj_w(idx, col), idx));
21375		}
21376	});
21377}
21378
21379function write_ws_biff8_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts) {
21380	var os = 16 + get_cell_style(opts.cellXfs, cell, opts);
21381	if(cell.v == null && !cell.bf) {
21382		write_biff_rec(ba, 0x0201 /* Blank */, write_XLSCell(R, C, os));
21383		return;
21384	}
21385	if(cell.bf) write_biff_rec(ba, 0x0006 /* Formula */, write_Formula(cell, R, C, opts, os));
21386	else switch(cell.t) {
21387		case 'd': case 'n':
21388			var v = cell.t == 'd' ? datenum(parseDate(cell.v)) : cell.v;
21389			/* TODO: emit RK as appropriate */
21390			write_biff_rec(ba, 0x0203 /* Number */, write_Number(R, C, v, os, opts));
21391			break;
21392		case 'b': case 'e':
21393			write_biff_rec(ba, 0x0205 /* BoolErr */, write_BoolErr(R, C, cell.v, os, opts, cell.t));
21394			break;
21395		/* TODO: codepage, sst */
21396		case 's': case 'str':
21397			if(opts.bookSST) {
21398				var isst = get_sst_id(opts.Strings, cell.v == null ? "" : String(cell.v), opts.revStrings);
21399				write_biff_rec(ba, 0x00fd /* LabelSst */, write_LabelSst(R, C, isst, os, opts));
21400			} else write_biff_rec(ba, 0x0204 /* Label */, write_Label(R, C, (cell.v == null ? "" : String(cell.v)).slice(0,255), os, opts));
21401			break;
21402		default:
21403			write_biff_rec(ba, 0x0201 /* Blank */, write_XLSCell(R, C, os));
21404	}
21405}
21406
21407/* [MS-XLS] 2.1.7.20.5 */
21408function write_ws_biff8(idx/*:number*/, opts, wb/*:Workbook*/) {
21409	var ba = buf_array();
21410	var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {};
21411	var _WB/*:WBWBProps*/ = ((wb||{}).Workbook||{}/*:any*/);
21412	var _sheet/*:WBWSProp*/ = ((_WB.Sheets||[])[idx]||{}/*:any*/);
21413	var dense = Array.isArray(ws);
21414	var b8 = opts.biff == 8;
21415	var ref/*:string*/, rr = "", cols/*:Array<string>*/ = [];
21416	var range = safe_decode_range(ws['!ref'] || "A1");
21417	var MAX_ROWS = b8 ? 65536 : 16384;
21418	if(range.e.c > 0xFF || range.e.r >= MAX_ROWS) {
21419		if(opts.WTF) throw new Error("Range " + (ws['!ref'] || "A1") + " exceeds format limit A1:IV16384");
21420		range.e.c = Math.min(range.e.c, 0xFF);
21421		range.e.r = Math.min(range.e.c, MAX_ROWS-1);
21422	}
21423
21424	write_biff_rec(ba, 0x0809, write_BOF(wb, 0x10, opts));
21425	/* [Uncalced] Index */
21426	write_biff_rec(ba, 0x000d /* CalcMode */, writeuint16(1));
21427	write_biff_rec(ba, 0x000c /* CalcCount */, writeuint16(100));
21428	write_biff_rec(ba, 0x000f /* CalcRefMode */, writebool(true));
21429	write_biff_rec(ba, 0x0011 /* CalcIter */, writebool(false));
21430	write_biff_rec(ba, 0x0010 /* CalcDelta */, write_Xnum(0.001));
21431	write_biff_rec(ba, 0x005f /* CalcSaveRecalc */, writebool(true));
21432	write_biff_rec(ba, 0x002a /* PrintRowCol */, writebool(false));
21433	write_biff_rec(ba, 0x002b /* PrintGrid */, writebool(false));
21434	write_biff_rec(ba, 0x0082 /* GridSet */, writeuint16(1));
21435	write_biff_rec(ba, 0x0080 /* Guts */, write_Guts([0,0]));
21436	/* DefaultRowHeight WsBool [Sync] [LPr] [HorizontalPageBreaks] [VerticalPageBreaks] */
21437	/* Header (string) */
21438	/* Footer (string) */
21439	write_biff_rec(ba, 0x0083 /* HCenter */, writebool(false));
21440	write_biff_rec(ba, 0x0084 /* VCenter */, writebool(false));
21441	/* ... */
21442	if(b8) write_ws_cols_biff8(ba, ws["!cols"]);
21443	/* ... */
21444	write_biff_rec(ba, 0x0200 /* Dimensions */, write_Dimensions(range, opts));
21445	/* ... */
21446
21447	if(b8) ws['!links'] = [];
21448	var comments = [];
21449	for(var R = range.s.r; R <= range.e.r; ++R) {
21450		rr = encode_row(R);
21451		for(var C = range.s.c; C <= range.e.c; ++C) {
21452			if(R === range.s.r) cols[C] = encode_col(C);
21453			ref = cols[C] + rr;
21454			var cell = dense ? (ws[R]||[])[C] : ws[ref];
21455			if(!cell) continue;
21456			/* write cell */
21457			write_ws_biff8_cell(ba, cell, R, C, opts);
21458			if(b8 && cell.l) ws['!links'].push([ref, cell.l]);
21459			if(b8 && cell.c) comments.push([ref, cell.c]);
21460		}
21461	}
21462	var cname/*:string*/ = _sheet.CodeName || _sheet.name || s;
21463	/* ... */
21464	// if(b8) comments.forEach(function(comment) { write_biff_rec(ba, 0x001c /* Note */, write_NoteSh(comment)); });
21465	/* ... */
21466	if(b8) write_biff_rec(ba, 0x023e /* Window2 */, write_Window2((_WB.Views||[])[0]));
21467	/* ... */
21468	if(b8 && (ws['!merges']||[]).length) write_biff_rec(ba, 0x00e5 /* MergeCells */, write_MergeCells(ws['!merges']));
21469	/* [LRng] *QUERYTABLE [PHONETICINFO] CONDFMTS */
21470	if(b8) write_ws_biff8_hlinks(ba, ws);
21471	/* [DVAL] */
21472	write_biff_rec(ba, 0x01ba /* CodeName */, write_XLUnicodeString(cname, opts));
21473	/* *WebPub *CellWatch [SheetExt] */
21474	if(b8) write_FEAT(ba, ws);
21475	/* *FEAT11 *RECORD12 */
21476	write_biff_rec(ba, 0x000a /* EOF */);
21477	return ba.end();
21478}
21479
21480/* [MS-XLS] 2.1.7.20.3 */
21481function write_biff8_global(wb/*:Workbook*/, bufs, opts/*:WriteOpts*/) {
21482	var A = buf_array();
21483	var _WB/*:WBWBProps*/ = ((wb||{}).Workbook||{}/*:any*/);
21484	var _sheets/*:Array<WBWSProp>*/ = (_WB.Sheets||[]);
21485	var _wb/*:WBProps*/ = /*::((*/_WB.WBProps||{/*::CodeName:"ThisWorkbook"*/}/*:: ):any)*/;
21486	var b8 = opts.biff == 8, b5 = opts.biff == 5;
21487	write_biff_rec(A, 0x0809, write_BOF(wb, 0x05, opts));
21488	if(opts.bookType == "xla") write_biff_rec(A, 0x0087 /* Addin */);
21489	write_biff_rec(A, 0x00e1 /* InterfaceHdr */, b8 ? writeuint16(0x04b0) : null);
21490	write_biff_rec(A, 0x00c1 /* Mms */, writezeroes(2));
21491	if(b5) write_biff_rec(A, 0x00bf /* ToolbarHdr */);
21492	if(b5) write_biff_rec(A, 0x00c0 /* ToolbarEnd */);
21493	write_biff_rec(A, 0x00e2 /* InterfaceEnd */);
21494	write_biff_rec(A, 0x005c /* WriteAccess */, write_WriteAccess("SheetJS", opts));
21495	/* [FileSharing] */
21496	write_biff_rec(A, 0x0042 /* CodePage */, writeuint16(b8 ? 0x04b0 : 0x04E4));
21497	/* *2047 Lel */
21498	if(b8) write_biff_rec(A, 0x0161 /* DSF */, writeuint16(0));
21499	if(b8) write_biff_rec(A, 0x01c0 /* Excel9File */);
21500	write_biff_rec(A, 0x013d /* RRTabId */, write_RRTabId(wb.SheetNames.length));
21501	if(b8 && wb.vbaraw) write_biff_rec(A, 0x00d3 /* ObProj */);
21502	/* [ObNoMacros] */
21503	if(b8 && wb.vbaraw) {
21504		var cname/*:string*/ = _wb.CodeName || "ThisWorkbook";
21505		write_biff_rec(A, 0x01ba /* CodeName */, write_XLUnicodeString(cname, opts));
21506	}
21507	write_biff_rec(A, 0x009c /* BuiltInFnGroupCount */, writeuint16(0x11));
21508	/* *FnGroupName *FnGrp12 */
21509	/* *Lbl */
21510	/* [OleObjectSize] */
21511	write_biff_rec(A, 0x0019 /* WinProtect */, writebool(false));
21512	write_biff_rec(A, 0x0012 /* Protect */, writebool(false));
21513	write_biff_rec(A, 0x0013 /* Password */, writeuint16(0));
21514	if(b8) write_biff_rec(A, 0x01af /* Prot4Rev */, writebool(false));
21515	if(b8) write_biff_rec(A, 0x01bc /* Prot4RevPass */, writeuint16(0));
21516	write_biff_rec(A, 0x003d /* Window1 */, write_Window1(opts));
21517	write_biff_rec(A, 0x0040 /* Backup */, writebool(false));
21518	write_biff_rec(A, 0x008d /* HideObj */, writeuint16(0));
21519	write_biff_rec(A, 0x0022 /* Date1904 */, writebool(safe1904(wb)=="true"));
21520	write_biff_rec(A, 0x000e /* CalcPrecision */, writebool(true));
21521	if(b8) write_biff_rec(A, 0x01b7 /* RefreshAll */, writebool(false));
21522	write_biff_rec(A, 0x00DA /* BookBool */, writeuint16(0));
21523	/* ... */
21524	write_FONTS_biff8(A, wb, opts);
21525	write_FMTS_biff8(A, wb.SSF, opts);
21526	write_CELLXFS_biff8(A, opts);
21527	/* ... */
21528	if(b8) write_biff_rec(A, 0x0160 /* UsesELFs */, writebool(false));
21529	var a = A.end();
21530
21531	var C = buf_array();
21532	/* METADATA [MTRSettings] [ForceFullCalculation] */
21533	if(b8) write_biff_rec(C, 0x008C /* Country */, write_Country());
21534	/* *SUPBOOK *LBL *RTD [RecalcId] *HFPicture *MSODRAWINGGROUP */
21535
21536	/* BIFF8: [SST *Continue] ExtSST */
21537	if(b8 && opts.Strings) write_biff_continue(C, 0x00FC /* SST */, write_SST(opts.Strings, opts));
21538
21539	/* *WebPub [WOpt] [CrErr] [BookExt] *FeatHdr *DConn [THEME] [CompressPictures] [Compat12] [GUIDTypeLib] */
21540	write_biff_rec(C, 0x000A /* EOF */);
21541	var c = C.end();
21542
21543	var B = buf_array();
21544	var blen = 0, j = 0;
21545	for(j = 0; j < wb.SheetNames.length; ++j) blen += (b8 ? 12 : 11) + (b8 ? 2 : 1) * wb.SheetNames[j].length;
21546	var start = a.length + blen + c.length;
21547	for(j = 0; j < wb.SheetNames.length; ++j) {
21548		var _sheet/*:WBWSProp*/ = _sheets[j] || ({}/*:any*/);
21549		write_biff_rec(B, 0x0085 /* BoundSheet8 */, write_BoundSheet8({pos:start, hs:_sheet.Hidden||0, dt:0, name:wb.SheetNames[j]}, opts));
21550		start += bufs[j].length;
21551	}
21552	/* 1*BoundSheet8 */
21553	var b = B.end();
21554	if(blen != b.length) throw new Error("BS8 " + blen + " != " + b.length);
21555
21556	var out = [];
21557	if(a.length) out.push(a);
21558	if(b.length) out.push(b);
21559	if(c.length) out.push(c);
21560	return bconcat(out);
21561}
21562
21563/* [MS-XLS] 2.1.7.20 Workbook Stream */
21564function write_biff8_buf(wb/*:Workbook*/, opts/*:WriteOpts*/) {
21565	var o = opts || {};
21566	var bufs = [];
21567
21568	if(wb && !wb.SSF) {
21569		wb.SSF = dup(table_fmt);
21570	}
21571	if(wb && wb.SSF) {
21572		make_ssf(); SSF_load_table(wb.SSF);
21573		// $FlowIgnore
21574		o.revssf = evert_num(wb.SSF); o.revssf[wb.SSF[65535]] = 0;
21575		o.ssf = wb.SSF;
21576	}
21577
21578	o.Strings = /*::((*/[]/*:: :any):SST)*/; o.Strings.Count = 0; o.Strings.Unique = 0;
21579	fix_write_opts(o);
21580
21581	o.cellXfs = [];
21582	get_cell_style(o.cellXfs, {}, {revssf:{"General":0}});
21583
21584	if(!wb.Props) wb.Props = {};
21585
21586	for(var i = 0; i < wb.SheetNames.length; ++i) bufs[bufs.length] = write_ws_biff8(i, o, wb);
21587	bufs.unshift(write_biff8_global(wb, bufs, o));
21588	return bconcat(bufs);
21589}
21590
21591function write_biff_buf(wb/*:Workbook*/, opts/*:WriteOpts*/) {
21592	for(var i = 0; i <= wb.SheetNames.length; ++i) {
21593		var ws = wb.Sheets[wb.SheetNames[i]];
21594		if(!ws || !ws["!ref"]) continue;
21595		var range = decode_range(ws["!ref"]);
21596		if(range.e.c > 255) { // note: 255 is IV
21597			if(typeof console != "undefined" && console.error) console.error("Worksheet '" + wb.SheetNames[i] + "' extends beyond column IV (255).  Data may be lost.");
21598		}
21599	}
21600
21601	var o = opts || {};
21602	switch(o.biff || 2) {
21603		case 8: case 5: return write_biff8_buf(wb, opts);
21604		case 4: case 3: case 2: return write_biff2_buf(wb, opts);
21605	}
21606	throw new Error("invalid type " + o.bookType + " for BIFF");
21607}
21608/* note: browser DOM element cannot see mso- style attrs, must parse */
21609function html_to_sheet(str/*:string*/, _opts)/*:Workbook*/ {
21610	var opts = _opts || {};
21611	if(DENSE != null && opts.dense == null) opts.dense = DENSE;
21612	var ws/*:Worksheet*/ = opts.dense ? ([]/*:any*/) : ({}/*:any*/);
21613	str = str.replace(/<!--.*?-->/g, "");
21614	var mtch/*:any*/ = str.match(/<table/i);
21615	if(!mtch) throw new Error("Invalid HTML: could not find <table>");
21616	var mtch2/*:any*/ = str.match(/<\/table/i);
21617	var i/*:number*/ = mtch.index, j/*:number*/ = mtch2 && mtch2.index || str.length;
21618	var rows = split_regex(str.slice(i, j), /(:?<tr[^>]*>)/i, "<tr>");
21619	var R = -1, C = 0, RS = 0, CS = 0;
21620	var range/*:Range*/ = {s:{r:10000000, c:10000000},e:{r:0,c:0}};
21621	var merges/*:Array<Range>*/ = [];
21622	for(i = 0; i < rows.length; ++i) {
21623		var row = rows[i].trim();
21624		var hd = row.slice(0,3).toLowerCase();
21625		if(hd == "<tr") { ++R; if(opts.sheetRows && opts.sheetRows <= R) { --R; break; } C = 0; continue; }
21626		if(hd != "<td" && hd != "<th") continue;
21627		var cells = row.split(/<\/t[dh]>/i);
21628		for(j = 0; j < cells.length; ++j) {
21629			var cell = cells[j].trim();
21630			if(!cell.match(/<t[dh]/i)) continue;
21631			var m = cell, cc = 0;
21632			/* TODO: parse styles etc */
21633			while(m.charAt(0) == "<" && (cc = m.indexOf(">")) > -1) m = m.slice(cc+1);
21634			for(var midx = 0; midx < merges.length; ++midx) {
21635				var _merge/*:Range*/ = merges[midx];
21636				if(_merge.s.c == C && _merge.s.r < R && R <= _merge.e.r) { C = _merge.e.c + 1; midx = -1; }
21637			}
21638			var tag = parsexmltag(cell.slice(0, cell.indexOf(">")));
21639			CS = tag.colspan ? +tag.colspan : 1;
21640			if((RS = +tag.rowspan)>1 || CS>1) merges.push({s:{r:R,c:C},e:{r:R + (RS||1) - 1, c:C + CS - 1}});
21641			var _t/*:string*/ = tag.t || tag["data-t"] || "";
21642			/* TODO: generate stub cells */
21643			if(!m.length) { C += CS; continue; }
21644			m = htmldecode(m);
21645			if(range.s.r > R) range.s.r = R; if(range.e.r < R) range.e.r = R;
21646			if(range.s.c > C) range.s.c = C; if(range.e.c < C) range.e.c = C;
21647			if(!m.length) { C += CS; continue; }
21648			var o/*:Cell*/ = {t:'s', v:m};
21649			if(opts.raw || !m.trim().length || _t == 's'){}
21650			else if(m === 'TRUE') o = {t:'b', v:true};
21651			else if(m === 'FALSE') o = {t:'b', v:false};
21652			else if(!isNaN(fuzzynum(m))) o = {t:'n', v:fuzzynum(m)};
21653			else if(!isNaN(fuzzydate(m).getDate())) {
21654				o = ({t:'d', v:parseDate(m)}/*:any*/);
21655				if(!opts.cellDates) o = ({t:'n', v:datenum(o.v)}/*:any*/);
21656				o.z = opts.dateNF || table_fmt[14];
21657			}
21658			if(opts.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = o; }
21659			else ws[encode_cell({r:R, c:C})] = o;
21660			C += CS;
21661		}
21662	}
21663	ws['!ref'] = encode_range(range);
21664	if(merges.length) ws["!merges"] = merges;
21665	return ws;
21666}
21667function make_html_row(ws/*:Worksheet*/, r/*:Range*/, R/*:number*/, o/*:Sheet2HTMLOpts*/)/*:string*/ {
21668	var M/*:Array<Range>*/ = (ws['!merges'] ||[]);
21669	var oo/*:Array<string>*/ = [];
21670	var sp = ({}/*:any*/);
21671	for(var C = r.s.c; C <= r.e.c; ++C) {
21672		var RS = 0, CS = 0;
21673		for(var j = 0; j < M.length; ++j) {
21674			if(M[j].s.r > R || M[j].s.c > C) continue;
21675			if(M[j].e.r < R || M[j].e.c < C) continue;
21676			if(M[j].s.r < R || M[j].s.c < C) { RS = -1; break; }
21677			RS = M[j].e.r - M[j].s.r + 1; CS = M[j].e.c - M[j].s.c + 1; break;
21678		}
21679		if(RS < 0) continue;
21680		var coord = encode_cell({r:R,c:C});
21681		var cell = o.dense ? (ws[R]||[])[C] : ws[coord];
21682		/* TODO: html entities */
21683		var w = (cell && cell.v != null) && (cell.h || escapehtml(cell.w || (format_cell(cell), cell.w) || "")) || "";
21684		sp = ({}/*:any*/);
21685		if(RS > 1) sp.rowspan = RS;
21686		if(CS > 1) sp.colspan = CS;
21687		if(o.editable) w = '<span contenteditable="true">' + w + '</span>';
21688		else if(cell) {
21689			sp["data-t"] = cell && cell.t || 'z';
21690			if(cell.v != null) sp["data-v"] = cell.v;
21691			if(cell.z != null) sp["data-z"] = cell.z;
21692			if(cell.l && (cell.l.Target || "#").charAt(0) != "#") w = '<a href="' + cell.l.Target +'">' + w + '</a>';
21693		}
21694		sp.id = (o.id || "sjs") + "-" + coord;
21695		oo.push(writextag('td', w, sp));
21696	}
21697	var preamble = "<tr>";
21698	return preamble + oo.join("") + "</tr>";
21699}
21700
21701var HTML_BEGIN = '<html><head><meta charset="utf-8"/><title>SheetJS Table Export</title></head><body>';
21702var HTML_END = '</body></html>';
21703
21704function html_to_workbook(str/*:string*/, opts)/*:Workbook*/ {
21705	var mtch = str.match(/<table[\s\S]*?>[\s\S]*?<\/table>/gi);
21706	if(!mtch || mtch.length == 0) throw new Error("Invalid HTML: could not find <table>");
21707	if(mtch.length == 1) {
21708		var w = sheet_to_workbook(html_to_sheet(mtch[0], opts), opts);
21709		w.bookType = "html";
21710		return w;
21711	}
21712	var wb = book_new();
21713	mtch.forEach(function(s, idx) { book_append_sheet(wb, html_to_sheet(s, opts), "Sheet" + (idx+1)); });
21714	wb.bookType = "html";
21715	return wb;
21716}
21717
21718function make_html_preamble(ws/*:Worksheet*/, R/*:Range*/, o/*:Sheet2HTMLOpts*/)/*:string*/ {
21719	var out/*:Array<string>*/ = [];
21720	return out.join("") + '<table' + (o && o.id ? ' id="' + o.id + '"' : "") + '>';
21721}
21722
21723function sheet_to_html(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*//*, wb:?Workbook*/)/*:string*/ {
21724	var o = opts || {};
21725	var header = o.header != null ? o.header : HTML_BEGIN;
21726	var footer = o.footer != null ? o.footer : HTML_END;
21727	var out/*:Array<string>*/ = [header];
21728	var r = decode_range(ws['!ref']);
21729	o.dense = Array.isArray(ws);
21730	out.push(make_html_preamble(ws, r, o));
21731	for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o));
21732	out.push("</table>" + footer);
21733	return out.join("");
21734}
21735
21736function sheet_add_dom(ws/*:Worksheet*/, table/*:HTMLElement*/, _opts/*:?any*/)/*:Worksheet*/ {
21737	var rows/*:HTMLCollection<HTMLTableRowElement>*/ = table.rows;
21738	if(!rows) {
21739		/* not an HTML TABLE */
21740		throw "Unsupported origin when " + table.tagName + " is not a TABLE";
21741	}
21742
21743	var opts = _opts || {};
21744	if(DENSE != null) opts.dense = DENSE;
21745	var or_R = 0, or_C = 0;
21746	if(opts.origin != null) {
21747		if(typeof opts.origin == 'number') or_R = opts.origin;
21748		else {
21749			var _origin/*:CellAddress*/ = typeof opts.origin == "string" ? decode_cell(opts.origin) : opts.origin;
21750			or_R = _origin.r; or_C = _origin.c;
21751		}
21752	}
21753
21754	var sheetRows = Math.min(opts.sheetRows||10000000, rows.length);
21755	var range/*:Range*/ = {s:{r:0,c:0},e:{r:or_R,c:or_C}};
21756	if(ws["!ref"]) {
21757		var _range/*:Range*/ = decode_range(ws["!ref"]);
21758		range.s.r = Math.min(range.s.r, _range.s.r);
21759		range.s.c = Math.min(range.s.c, _range.s.c);
21760		range.e.r = Math.max(range.e.r, _range.e.r);
21761		range.e.c = Math.max(range.e.c, _range.e.c);
21762		if(or_R == -1) range.e.r = or_R = _range.e.r + 1;
21763	}
21764	var merges/*:Array<Range>*/ = [], midx = 0;
21765	var rowinfo/*:Array<RowInfo>*/ = ws["!rows"] || (ws["!rows"] = []);
21766	var _R = 0, R = 0, _C = 0, C = 0, RS = 0, CS = 0;
21767	if(!ws["!cols"]) ws['!cols'] = [];
21768	for(; _R < rows.length && R < sheetRows; ++_R) {
21769		var row/*:HTMLTableRowElement*/ = rows[_R];
21770		if (is_dom_element_hidden(row)) {
21771			if (opts.display) continue;
21772			rowinfo[R] = {hidden: true};
21773		}
21774		var elts/*:HTMLCollection<HTMLTableCellElement>*/ = (row.cells);
21775		for(_C = C = 0; _C < elts.length; ++_C) {
21776			var elt/*:HTMLTableCellElement*/ = elts[_C];
21777			if (opts.display && is_dom_element_hidden(elt)) continue;
21778			var v/*:?string*/ = elt.hasAttribute('data-v') ? elt.getAttribute('data-v') : elt.hasAttribute('v') ? elt.getAttribute('v') : htmldecode(elt.innerHTML);
21779			var z/*:?string*/ = elt.getAttribute('data-z') || elt.getAttribute('z');
21780			for(midx = 0; midx < merges.length; ++midx) {
21781				var m/*:Range*/ = merges[midx];
21782				if(m.s.c == C + or_C && m.s.r < R + or_R && R + or_R <= m.e.r) { C = m.e.c+1 - or_C; midx = -1; }
21783			}
21784			/* TODO: figure out how to extract nonstandard mso- style */
21785			CS = +elt.getAttribute("colspan") || 1;
21786			if( ((RS = (+elt.getAttribute("rowspan") || 1)))>1 || CS>1) merges.push({s:{r:R + or_R,c:C + or_C},e:{r:R + or_R + (RS||1) - 1, c:C + or_C + (CS||1) - 1}});
21787			var o/*:Cell*/ = {t:'s', v:v};
21788			var _t/*:string*/ = elt.getAttribute("data-t") || elt.getAttribute("t") || "";
21789			if(v != null) {
21790				if(v.length == 0) o.t = _t || 'z';
21791				else if(opts.raw || v.trim().length == 0 || _t == "s"){}
21792				else if(v === 'TRUE') o = {t:'b', v:true};
21793				else if(v === 'FALSE') o = {t:'b', v:false};
21794				else if(!isNaN(fuzzynum(v))) o = {t:'n', v:fuzzynum(v)};
21795				else if(!isNaN(fuzzydate(v).getDate())) {
21796					o = ({t:'d', v:parseDate(v)}/*:any*/);
21797					if(!opts.cellDates) o = ({t:'n', v:datenum(o.v)}/*:any*/);
21798					o.z = opts.dateNF || table_fmt[14];
21799				}
21800			}
21801			if(o.z === undefined && z != null) o.z = z;
21802			/* The first link is used.  Links are assumed to be fully specified.
21803			 * TODO: The right way to process relative links is to make a new <a> */
21804			var l = "", Aelts = elt.getElementsByTagName("A");
21805			if(Aelts && Aelts.length) for(var Aelti = 0; Aelti < Aelts.length; ++Aelti)	if(Aelts[Aelti].hasAttribute("href")) {
21806				l = Aelts[Aelti].getAttribute("href"); if(l.charAt(0) != "#") break;
21807			}
21808			if(l && l.charAt(0) != "#" &&	l.slice(0, 11).toLowerCase() != 'javascript:') o.l = ({ Target: l });
21809			if(opts.dense) { if(!ws[R + or_R]) ws[R + or_R] = []; ws[R + or_R][C + or_C] = o; }
21810			else ws[encode_cell({c:C + or_C, r:R + or_R})] = o;
21811			if(range.e.c < C + or_C) range.e.c = C + or_C;
21812			C += CS;
21813		}
21814		++R;
21815	}
21816	if(merges.length) ws['!merges'] = (ws["!merges"] || []).concat(merges);
21817	range.e.r = Math.max(range.e.r, R - 1 + or_R);
21818	ws['!ref'] = encode_range(range);
21819	if(R >= sheetRows) ws['!fullref'] = encode_range((range.e.r = rows.length-_R+R-1 + or_R,range)); // We can count the real number of rows to parse but we don't to improve the performance
21820	return ws;
21821}
21822
21823function parse_dom_table(table/*:HTMLElement*/, _opts/*:?any*/)/*:Worksheet*/ {
21824	var opts = _opts || {};
21825	var ws/*:Worksheet*/ = opts.dense ? ([]/*:any*/) : ({}/*:any*/);
21826	return sheet_add_dom(ws, table, _opts);
21827}
21828
21829function table_to_book(table/*:HTMLElement*/, opts/*:?any*/)/*:Workbook*/ {
21830	var o = sheet_to_workbook(parse_dom_table(table, opts), opts);
21831	//o.bookType = "dom"; // TODO: define a type for this
21832	return o;
21833}
21834
21835function is_dom_element_hidden(element/*:HTMLElement*/)/*:boolean*/ {
21836	var display/*:string*/ = '';
21837	var get_computed_style/*:?function*/ = get_get_computed_style_function(element);
21838	if(get_computed_style) display = get_computed_style(element).getPropertyValue('display');
21839	if(!display) display = element.style && element.style.display;
21840	return display === 'none';
21841}
21842
21843/* global getComputedStyle */
21844function get_get_computed_style_function(element/*:HTMLElement*/)/*:?function*/ {
21845	// The proper getComputedStyle implementation is the one defined in the element window
21846	if(element.ownerDocument.defaultView && typeof element.ownerDocument.defaultView.getComputedStyle === 'function') return element.ownerDocument.defaultView.getComputedStyle;
21847	// If it is not available, try to get one from the global namespace
21848	if(typeof getComputedStyle === 'function') return getComputedStyle;
21849	return null;
21850}
21851/* OpenDocument */
21852function parse_text_p(text/*:string*//*::, tag*/)/*:Array<any>*/ {
21853	/* 6.1.2 White Space Characters */
21854	var fixed = text
21855		.replace(/[\t\r\n]/g, " ").trim().replace(/ +/g, " ")
21856		.replace(/<text:s\/>/g," ")
21857		.replace(/<text:s text:c="(\d+)"\/>/g, function($$,$1) { return Array(parseInt($1,10)+1).join(" "); })
21858		.replace(/<text:tab[^>]*\/>/g,"\t")
21859		.replace(/<text:line-break\/>/g,"\n");
21860	var v = unescapexml(fixed.replace(/<[^>]*>/g,""));
21861
21862	return [v];
21863}
21864
21865/* Note: ODS can stick styles in content.xml or styles.xml, FODS blurs lines */
21866function parse_ods_styles(d/*:string*/, _opts, _nfm) {
21867	var number_format_map = _nfm || {};
21868	var str = xlml_normalize(d);
21869	xlmlregex.lastIndex = 0;
21870	str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
21871	var Rn, NFtag, NF = "", tNF = "", y, etpos = 0, tidx = -1, infmt = false, payload = "";
21872	while((Rn = xlmlregex.exec(str))) {
21873		switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
21874		/* Number Format Definitions */
21875		case 'number-style': // <number:number-style> 16.29.2
21876		case 'currency-style': // <number:currency-style> 16.29.8
21877		case 'percentage-style': // <number:percentage-style> 16.29.10
21878		case 'date-style': // <number:date-style> 16.29.11
21879		case 'time-style': // <number:time-style> 16.29.19
21880		case 'text-style': // <number:text-style> 16.29.26
21881			if(Rn[1]==='/') {
21882				infmt = false;
21883				if(NFtag['truncate-on-overflow'] == "false") {
21884					if(NF.match(/h/)) NF = NF.replace(/h+/, "[$&]");
21885					else if(NF.match(/m/)) NF = NF.replace(/m+/, "[$&]");
21886					else if(NF.match(/s/)) NF = NF.replace(/s+/, "[$&]");
21887				}
21888				number_format_map[NFtag.name] = NF;
21889				NF = "";
21890			} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
21891				infmt = true;
21892				NF = "";
21893				NFtag = parsexmltag(Rn[0], false);
21894			} break;
21895
21896		// LibreOffice bug https://bugs.documentfoundation.org/show_bug.cgi?id=149484
21897		case 'boolean-style': // <number:boolean-style> 16.29.24
21898			if(Rn[1]==='/') {
21899				infmt = false;
21900				number_format_map[NFtag.name] = "General";
21901				NF = "";
21902			} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
21903				infmt = true;
21904				NF = "";
21905				NFtag = parsexmltag(Rn[0], false);
21906			} break;
21907
21908		/* Number Format Elements */
21909		case 'boolean': // <number:boolean> 16.29.25
21910			NF += "General"; // ODF spec is unfortunately underspecified here
21911			break;
21912
21913		case 'text': // <number:text> 16.29.27
21914			if(Rn[1]==='/') {
21915				payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
21916				// NOTE: Excel has a different interpretation of "%%" and friends
21917				if(payload == "%" && NFtag[0] == '<number:percentage-style') NF += "%";
21918				else NF += '"' + payload.replace(/"/g, '""') + '"';
21919			} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
21920				tidx = xlmlregex.lastIndex;
21921			} break;
21922
21923
21924		case 'day': { // <number:day> 16.29.12
21925			y = parsexmltag(Rn[0], false);
21926			switch(y["style"]) {
21927				case "short": NF += "d"; break;
21928				case "long": NF += "dd"; break;
21929				default: NF += "dd"; break; // TODO: error condition
21930			}
21931		} break;
21932
21933		case 'day-of-week': { // <number:day-of-week> 16.29.16
21934			y = parsexmltag(Rn[0], false);
21935			switch(y["style"]) {
21936				case "short": NF += "ddd"; break;
21937				case "long": NF += "dddd"; break;
21938				default: NF += "ddd"; break;
21939			}
21940		} break;
21941
21942		case 'era': { // <number:era> 16.29.15 TODO: proper mapping
21943			y = parsexmltag(Rn[0], false);
21944			switch(y["style"]) {
21945				case "short": NF += "ee"; break;
21946				case "long": NF += "eeee"; break;
21947				default: NF += "eeee"; break; // TODO: error condition
21948			}
21949		} break;
21950
21951		case 'hours': { // <number:hours> 16.29.20
21952			y = parsexmltag(Rn[0], false);
21953			switch(y["style"]) {
21954				case "short": NF += "h"; break;
21955				case "long": NF += "hh"; break;
21956				default: NF += "hh"; break; // TODO: error condition
21957			}
21958		} break;
21959
21960		case 'minutes': { // <number:minutes> 16.29.21
21961			y = parsexmltag(Rn[0], false);
21962			switch(y["style"]) {
21963				case "short": NF += "m"; break;
21964				case "long": NF += "mm"; break;
21965				default: NF += "mm"; break; // TODO: error condition
21966			}
21967		} break;
21968
21969		case 'month': { // <number:month> 16.29.13
21970			y = parsexmltag(Rn[0], false);
21971			if(y["textual"]) NF += "mm";
21972			switch(y["style"]) {
21973				case "short": NF += "m"; break;
21974				case "long": NF += "mm"; break;
21975				default: NF += "m"; break;
21976			}
21977		} break;
21978
21979		case 'seconds': { // <number:seconds> 16.29.22
21980			y = parsexmltag(Rn[0], false);
21981			switch(y["style"]) {
21982				case "short": NF += "s"; break;
21983				case "long": NF += "ss"; break;
21984				default: NF += "ss"; break; // TODO: error condition
21985			}
21986			if(y["decimal-places"]) NF += "." + fill("0", +y["decimal-places"]);
21987		} break;
21988
21989		case 'year': { // <number:year> 16.29.14
21990			y = parsexmltag(Rn[0], false);
21991			switch(y["style"]) {
21992				case "short": NF += "yy"; break;
21993				case "long": NF += "yyyy"; break;
21994				default: NF += "yy"; break; // TODO: error condition
21995			}
21996		} break;
21997
21998		case 'am-pm': // <number:am-pm> 16.29.23
21999			NF += "AM/PM"; // LO autocorrects A/P -> AM/PM
22000			break;
22001
22002		case 'week-of-year': // <number:week-of-year> 16.29.17
22003		case 'quarter': // <number:quarter> 16.29.18
22004			console.error("Excel does not support ODS format token " + Rn[3]);
22005			break;
22006
22007		case 'fill-character': // <number:fill-character> 16.29.5
22008			if(Rn[1]==='/') {
22009				payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
22010				// NOTE: Excel has a different interpretation of "%%" and friends
22011				NF += '"' + payload.replace(/"/g, '""') + '"*';
22012			} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
22013				tidx = xlmlregex.lastIndex;
22014			} break;
22015
22016		case 'scientific-number': // <number:scientific-number> 16.29.6
22017			// TODO: find a mapping for all parameters
22018			y = parsexmltag(Rn[0], false);
22019			NF += "0." + fill("0", +y["min-decimal-places"] || +y["decimal-places"] || 2) + fill("?", +y["decimal-places"] - +y["min-decimal-places"] || 0) + "E" + (parsexmlbool(y["forced-exponent-sign"]) ? "+" : "") + fill("0", +y["min-exponent-digits"] || 2);
22020			break;
22021
22022		case 'fraction': // <number:fraction> 16.29.7
22023			// TODO: find a mapping for all parameters
22024			y = parsexmltag(Rn[0], false);
22025			if(!+y["min-integer-digits"]) NF += "#";
22026			else NF += fill("0", +y["min-integer-digits"]);
22027			NF += " ";
22028			NF += fill("?", +y["min-numerator-digits"] || 1);
22029			NF += "/";
22030			if(+y["denominator-value"]) NF += y["denominator-value"];
22031			else NF += fill("?", +y["min-denominator-digits"] || 1);
22032			break;
22033
22034		case 'currency-symbol': // <number:currency-symbol> 16.29.9
22035			// TODO: localization with [$-...]
22036			if(Rn[1]==='/') {
22037				NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
22038			} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
22039				tidx = xlmlregex.lastIndex;
22040			} else NF += "$";
22041			break;
22042
22043		case 'text-properties': // <style:text-properties> 16.29.29
22044			y = parsexmltag(Rn[0], false);
22045			switch((y["color"]||"").toLowerCase().replace("#", "")) {
22046				case "ff0000": case "red": NF = "[Red]" + NF; break;
22047			}
22048			break;
22049
22050		case 'text-content': // <number:text-content> 16.29.28
22051			NF += "@";
22052			break;
22053
22054		case 'map': // <style:map> 16.3
22055			// TODO: handle more complex maps
22056			y = parsexmltag(Rn[0], false);
22057			if(unescapexml(y["condition"]) == "value()>=0") NF = number_format_map[y["apply-style-name"]] + ";" + NF;
22058			else console.error("ODS number format may be incorrect: " + y["condition"]);
22059			break;
22060
22061		case 'number': // <number:number> 16.29.3
22062			// TODO: handle all the attributes
22063			if(Rn[1]==='/') break;
22064			y = parsexmltag(Rn[0], false);
22065			tNF = "";
22066			tNF += fill("0", +y["min-integer-digits"] || 1);
22067			if(parsexmlbool(y["grouping"])) tNF = commaify(fill("#", Math.max(0, 4 - tNF.length)) + tNF);
22068			if(+y["min-decimal-places"] || +y["decimal-places"]) tNF += ".";
22069			if(+y["min-decimal-places"]) tNF += fill("0", +y["min-decimal-places"] || 1);
22070			if(+y["decimal-places"] - (+y["min-decimal-places"]||0)) tNF += fill("0", +y["decimal-places"] - (+y["min-decimal-places"]||0)); // TODO: should this be "#" ?
22071			NF += tNF;
22072			break;
22073
22074		case 'embedded-text': // <number:embedded-text> 16.29.4
22075			// TODO: verify interplay with grouping et al
22076			if(Rn[1]==='/') {
22077				if(etpos == 0) NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
22078				else NF = NF.slice(0, etpos) + '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"' + NF.slice(etpos);
22079			} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
22080				tidx = xlmlregex.lastIndex;
22081				etpos = -+parsexmltag(Rn[0], false)["position"] || 0;
22082			} break;
22083
22084	}}
22085	return number_format_map;
22086}
22087
22088function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ {
22089		var opts = _opts || {};
22090		if(DENSE != null && opts.dense == null) opts.dense = DENSE;
22091		var str = xlml_normalize(d);
22092		var state/*:Array<any>*/ = [], tmp;
22093		var tag/*:: = {}*/;
22094		var nfidx, NF = "", pidx = 0;
22095		var sheetag/*:: = {name:"", '名称':""}*/;
22096		var rowtag/*:: = {'行号':""}*/;
22097		var Sheets = {}, SheetNames/*:Array<string>*/ = [];
22098		var ws = opts.dense ? ([]/*:any*/) : ({}/*:any*/);
22099		var Rn, q/*:: :any = ({t:"", v:null, z:null, w:"",c:[],}:any)*/;
22100		var ctag = ({value:""}/*:any*/);
22101		var textp = "", textpidx = 0, textptag/*:: = {}*/;
22102		var textR = [];
22103		var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}};
22104		var row_ol = 0;
22105		var number_format_map = _nfm || {}, styles = {};
22106		var merges/*:Array<Range>*/ = [], mrange = {}, mR = 0, mC = 0;
22107		var rowinfo/*:Array<RowInfo>*/ = [], rowpeat = 1, colpeat = 1;
22108		var arrayf/*:Array<[Range, string]>*/ = [];
22109		var WB = {Names:[], WBProps:{}};
22110		var atag = ({}/*:any*/);
22111		var _Ref/*:[string, string]*/ = ["", ""];
22112		var comments/*:Array<Comment>*/ = [], comment/*:Comment*/ = ({}/*:any*/);
22113		var creator = "", creatoridx = 0;
22114		var isstub = false, intable = false;
22115		var i = 0;
22116		var baddate = 0;
22117		xlmlregex.lastIndex = 0;
22118		str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
22119		while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
22120
22121			case 'table': case '工作表': // 9.1.2 <table:table>
22122				if(Rn[1]==='/') {
22123					if(range.e.c >= range.s.c && range.e.r >= range.s.r) ws['!ref'] = encode_range(range);
22124					else ws['!ref'] = "A1:A1";
22125					if(opts.sheetRows > 0 && opts.sheetRows <= range.e.r) {
22126						ws['!fullref'] = ws['!ref'];
22127						range.e.r = opts.sheetRows - 1;
22128						ws['!ref'] = encode_range(range);
22129					}
22130					if(merges.length) ws['!merges'] = merges;
22131					if(rowinfo.length) ws["!rows"] = rowinfo;
22132					sheetag.name = sheetag['名称'] || sheetag.name;
22133					if(typeof JSON !== 'undefined') JSON.stringify(sheetag);
22134					SheetNames.push(sheetag.name);
22135					Sheets[sheetag.name] = ws;
22136					intable = false;
22137				}
22138				else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
22139					sheetag = parsexmltag(Rn[0], false);
22140					R = C = -1;
22141					range.s.r = range.s.c = 10000000; range.e.r = range.e.c = 0;
22142					ws = opts.dense ? ([]/*:any*/) : ({}/*:any*/); merges = [];
22143					rowinfo = [];
22144					intable = true;
22145				}
22146				break;
22147
22148			case 'table-row-group': // 9.1.9 <table:table-row-group>
22149				if(Rn[1] === "/") --row_ol; else ++row_ol;
22150				break;
22151			case 'table-row': case '行': // 9.1.3 <table:table-row>
22152				if(Rn[1] === '/') { R+=rowpeat; rowpeat = 1; break; }
22153				rowtag = parsexmltag(Rn[0], false);
22154				if(rowtag['行号']) R = rowtag['行号'] - 1; else if(R == -1) R = 0;
22155				rowpeat = +rowtag['number-rows-repeated'] || 1;
22156				/* TODO: remove magic */
22157				if(rowpeat < 10) for(i = 0; i < rowpeat; ++i) if(row_ol > 0) rowinfo[R + i] = {level: row_ol};
22158				C = -1; break;
22159			case 'covered-table-cell': // 9.1.5 <table:covered-table-cell>
22160				if(Rn[1] !== '/') ++C;
22161				if(opts.sheetStubs) {
22162					if(opts.dense) { if(!ws[R]) ws[R] = []; ws[R][C] = {t:'z'}; }
22163					else ws[encode_cell({r:R,c:C})] = {t:'z'};
22164				}
22165				textp = ""; textR = [];
22166				break; /* stub */
22167			case 'table-cell': case '数据':
22168				if(Rn[0].charAt(Rn[0].length-2) === '/') {
22169					++C;
22170					ctag = parsexmltag(Rn[0], false);
22171					colpeat = parseInt(ctag['number-columns-repeated']||"1", 10);
22172					q = ({t:'z', v:null/*:: , z:null, w:"",c:[]*/}/*:any*/);
22173					if(ctag.formula && opts.cellFormula != false) q.f = ods_to_csf_formula(unescapexml(ctag.formula));
22174					if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
22175					if((ctag['数据类型'] || ctag['value-type']) == "string") {
22176						q.t = "s"; q.v = unescapexml(ctag['string-value'] || "");
22177						if(opts.dense) {
22178							if(!ws[R]) ws[R] = [];
22179							ws[R][C] = q;
22180						} else {
22181							ws[encode_cell({r:R,c:C})] = q;
22182						}
22183					}
22184					C+= colpeat-1;
22185				} else if(Rn[1]!=='/') {
22186					++C;
22187					textp = ""; textpidx = 0; textR = [];
22188					colpeat = 1;
22189					var rptR = rowpeat ? R + rowpeat - 1 : R;
22190					if(C > range.e.c) range.e.c = C;
22191					if(C < range.s.c) range.s.c = C;
22192					if(R < range.s.r) range.s.r = R;
22193					if(rptR > range.e.r) range.e.r = rptR;
22194					ctag = parsexmltag(Rn[0], false);
22195					comments = []; comment = ({}/*:any*/);
22196					q = ({t:ctag['数据类型'] || ctag['value-type'], v:null/*:: , z:null, w:"",c:[]*/}/*:any*/);
22197					if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
22198					if(opts.cellFormula) {
22199						if(ctag.formula) ctag.formula = unescapexml(ctag.formula);
22200						if(ctag['number-matrix-columns-spanned'] && ctag['number-matrix-rows-spanned']) {
22201							mR = parseInt(ctag['number-matrix-rows-spanned'],10) || 0;
22202							mC = parseInt(ctag['number-matrix-columns-spanned'],10) || 0;
22203							mrange = {s: {r:R,c:C}, e:{r:R + mR-1,c:C + mC-1}};
22204							q.F = encode_range(mrange);
22205							arrayf.push([mrange, q.F]);
22206						}
22207						if(ctag.formula) q.f = ods_to_csf_formula(ctag.formula);
22208						else for(i = 0; i < arrayf.length; ++i)
22209							if(R >= arrayf[i][0].s.r && R <= arrayf[i][0].e.r)
22210								if(C >= arrayf[i][0].s.c && C <= arrayf[i][0].e.c)
22211									q.F = arrayf[i][1];
22212					}
22213					if(ctag['number-columns-spanned'] || ctag['number-rows-spanned']) {
22214						mR = parseInt(ctag['number-rows-spanned'],10) || 0;
22215						mC = parseInt(ctag['number-columns-spanned'],10) || 0;
22216						mrange = {s: {r:R,c:C}, e:{r:R + mR-1,c:C + mC-1}};
22217						merges.push(mrange);
22218					}
22219
22220					/* 19.675.2 table:number-columns-repeated */
22221					if(ctag['number-columns-repeated']) colpeat = parseInt(ctag['number-columns-repeated'], 10);
22222
22223					/* 19.385 office:value-type */
22224					switch(q.t) {
22225						case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']) || (+ctag['boolean-value'] >= 1); break;
22226						case 'float': q.t = 'n'; q.v = parseFloat(ctag.value); break;
22227						case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
22228						case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
22229						case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
22230							if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
22231							if(!q.z) q.z = 'm/d/yy'; break;
22232						case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
22233							if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
22234							if(!q.z) q.z = 'HH:MM:SS'; break;
22235						case 'number': q.t = 'n'; q.v = parseFloat(ctag['数据数值']); break;
22236						default:
22237							if(q.t === 'string' || q.t === 'text' || !q.t) {
22238								q.t = 's';
22239								if(ctag['string-value'] != null) { textp = unescapexml(ctag['string-value']); textR = []; }
22240							} else throw new Error('Unsupported value type ' + q.t);
22241					}
22242				} else {
22243					isstub = false;
22244					if(q.t === 's') {
22245						q.v = textp || '';
22246						if(textR.length) q.R = textR;
22247						isstub = textpidx == 0;
22248					}
22249					if(atag.Target) q.l = atag;
22250					if(comments.length > 0) { q.c = comments; comments = []; }
22251					if(textp && opts.cellText !== false) q.w = textp;
22252					if(isstub) { q.t = "z"; delete q.v; }
22253					if(!isstub || opts.sheetStubs) {
22254						if(!(opts.sheetRows && opts.sheetRows <= R)) {
22255							for(var rpt = 0; rpt < rowpeat; ++rpt) {
22256								colpeat = parseInt(ctag['number-columns-repeated']||"1", 10);
22257								if(opts.dense) {
22258									if(!ws[R + rpt]) ws[R + rpt] = [];
22259									ws[R + rpt][C] = rpt == 0 ? q : dup(q);
22260									while(--colpeat > 0) ws[R + rpt][C + colpeat] = dup(q);
22261								} else {
22262									ws[encode_cell({r:R + rpt,c:C})] = q;
22263									while(--colpeat > 0) ws[encode_cell({r:R + rpt,c:C + colpeat})] = dup(q);
22264								}
22265								if(range.e.c <= C) range.e.c = C;
22266							}
22267						}
22268					}
22269					colpeat = parseInt(ctag['number-columns-repeated']||"1", 10);
22270					C += colpeat-1; colpeat = 0;
22271					q = {/*:: t:"", v:null, z:null, w:"",c:[]*/};
22272					textp = ""; textR = [];
22273				}
22274				atag = ({}/*:any*/);
22275				break; // 9.1.4 <table:table-cell>
22276
22277			/* pure state */
22278			case 'document': // TODO: <office:document> is the root for FODS
22279			case 'document-content': case '电子表格文档': // 3.1.3.2 <office:document-content>
22280			case 'spreadsheet': case '主体': // 3.7 <office:spreadsheet>
22281			case 'scripts': // 3.12 <office:scripts>
22282			case 'styles': // TODO <office:styles>
22283			case 'font-face-decls': // 3.14 <office:font-face-decls>
22284			case 'master-styles': // 3.15.4 <office:master-styles> -- relevant for FODS
22285				if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;}
22286				else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], true]);
22287				break;
22288
22289			case 'annotation': // 14.1 <office:annotation>
22290				if(Rn[1]==='/'){
22291					if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;
22292					comment.t = textp;
22293					if(textR.length) /*::(*/comment/*:: :any)*/.R = textR;
22294					comment.a = creator;
22295					comments.push(comment);
22296				}
22297				else if(Rn[0].charAt(Rn[0].length-2) !== '/') {state.push([Rn[3], false]);}
22298				creator = ""; creatoridx = 0;
22299				textp = ""; textpidx = 0; textR = [];
22300				break;
22301
22302			case 'creator': // 4.3.2.7 <dc:creator>
22303				if(Rn[1]==='/') { creator = str.slice(creatoridx,Rn.index); }
22304				else creatoridx = Rn.index + Rn[0].length;
22305				break;
22306
22307			/* ignore state */
22308			case 'meta': case '元数据': // TODO: <office:meta> <uof:元数据> FODS/UOF
22309			case 'settings': // TODO: <office:settings>
22310			case 'config-item-set': // TODO: <office:config-item-set>
22311			case 'config-item-map-indexed': // TODO: <office:config-item-map-indexed>
22312			case 'config-item-map-entry': // TODO: <office:config-item-map-entry>
22313			case 'config-item-map-named': // TODO: <office:config-item-map-entry>
22314			case 'shapes': // 9.2.8 <table:shapes>
22315			case 'frame': // 10.4.2 <draw:frame>
22316			case 'text-box': // 10.4.3 <draw:text-box>
22317			case 'image': // 10.4.4 <draw:image>
22318			case 'data-pilot-tables': // 9.6.2 <table:data-pilot-tables>
22319			case 'list-style': // 16.30 <text:list-style>
22320			case 'form': // 13.13 <form:form>
22321			case 'dde-links': // 9.8 <table:dde-links>
22322			case 'event-listeners': // TODO
22323			case 'chart': // TODO
22324				if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;}
22325				else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], false]);
22326				textp = ""; textpidx = 0; textR = [];
22327				break;
22328
22329			case 'scientific-number': // <number:scientific-number>
22330			case 'currency-symbol': // <number:currency-symbol>
22331			case 'fill-character': // 16.29.5 <number:fill-character>
22332				break;
22333
22334			case 'text-style': // 16.27.25 <number:text-style>
22335			case 'boolean-style': // 16.27.23 <number:boolean-style>
22336			case 'number-style': // 16.27.2 <number:number-style>
22337			case 'currency-style': // 16.29.8 <number:currency-style>
22338			case 'percentage-style': // 16.27.9 <number:percentage-style>
22339			case 'date-style': // 16.27.10 <number:date-style>
22340			case 'time-style': // 16.27.18 <number:time-style>
22341				if(Rn[1]==='/'){
22342					var xlmlidx = xlmlregex.lastIndex;
22343					parse_ods_styles(str.slice(nfidx, xlmlregex.lastIndex), _opts, number_format_map);
22344					xlmlregex.lastIndex = xlmlidx;
22345				} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
22346					nfidx = xlmlregex.lastIndex - Rn[0].length;
22347				} break;
22348
22349			case 'script': break; // 3.13 <office:script>
22350			case 'libraries': break; // TODO: <ooo:libraries>
22351			case 'automatic-styles': break; // 3.15.3 <office:automatic-styles>
22352
22353			case 'default-style': // TODO: <style:default-style>
22354			case 'page-layout': break; // TODO: <style:page-layout>
22355			case 'style': { // 16.2 <style:style>
22356				var styletag = parsexmltag(Rn[0], false);
22357				if(styletag["family"] == "table-cell" && number_format_map[styletag["data-style-name"]]) styles[styletag["name"]] = number_format_map[styletag["data-style-name"]];
22358			} break;
22359			case 'map': break; // 16.3 <style:map>
22360			case 'font-face': break; // 16.21 <style:font-face>
22361
22362			case 'paragraph-properties': break; // 17.6 <style:paragraph-properties>
22363			case 'table-properties': break; // 17.15 <style:table-properties>
22364			case 'table-column-properties': break; // 17.16 <style:table-column-properties>
22365			case 'table-row-properties': break; // 17.17 <style:table-row-properties>
22366			case 'table-cell-properties': break; // 17.18 <style:table-cell-properties>
22367
22368			case 'number': // 16.27.3 <number:number>
22369				break;
22370
22371			case 'fraction': break; // TODO 16.27.6 <number:fraction>
22372
22373			case 'day': // 16.27.11 <number:day>
22374			case 'month': // 16.27.12 <number:month>
22375			case 'year': // 16.27.13 <number:year>
22376			case 'era': // 16.27.14 <number:era>
22377			case 'day-of-week': // 16.27.15 <number:day-of-week>
22378			case 'week-of-year': // 16.27.16 <number:week-of-year>
22379			case 'quarter': // 16.27.17 <number:quarter>
22380			case 'hours': // 16.27.19 <number:hours>
22381			case 'minutes': // 16.27.20 <number:minutes>
22382			case 'seconds': // 16.27.21 <number:seconds>
22383			case 'am-pm': // 16.27.22 <number:am-pm>
22384				break;
22385
22386			case 'boolean': break; // 16.27.24 <number:boolean>
22387			case 'text': // 16.27.26 <number:text>
22388				if(Rn[0].slice(-2) === "/>") break;
22389				else if(Rn[1]==="/") switch(state[state.length-1][0]) {
22390					case 'number-style':
22391					case 'date-style':
22392					case 'time-style':
22393						NF += str.slice(pidx, Rn.index);
22394						break;
22395				}
22396				else pidx = Rn.index + Rn[0].length;
22397				break;
22398
22399			case 'named-range': // 9.4.12 <table:named-range>
22400				tag = parsexmltag(Rn[0], false);
22401				_Ref = ods_to_csf_3D(tag['cell-range-address']);
22402				var nrange = ({Name:tag.name, Ref:_Ref[0] + '!' + _Ref[1]}/*:any*/);
22403				if(intable) nrange.Sheet = SheetNames.length;
22404				WB.Names.push(nrange);
22405				break;
22406
22407			case 'text-content': break; // 16.27.27 <number:text-content>
22408			case 'text-properties': break; // 16.27.27 <style:text-properties>
22409			case 'embedded-text': break; // 16.27.4 <number:embedded-text>
22410
22411			case 'body': case '电子表格': break; // 3.3 16.9.6 19.726.3
22412
22413			case 'forms': break; // 12.25.2 13.2
22414			case 'table-column': break; // 9.1.6 <table:table-column>
22415			case 'table-header-rows': break; // 9.1.7 <table:table-header-rows>
22416			case 'table-rows': break; // 9.1.12 <table:table-rows>
22417			/* TODO: outline levels */
22418			case 'table-column-group': break; // 9.1.10 <table:table-column-group>
22419			case 'table-header-columns': break; // 9.1.11 <table:table-header-columns>
22420			case 'table-columns': break; // 9.1.12 <table:table-columns>
22421
22422			case 'null-date': // 9.4.2 <table:null-date>
22423				tag = parsexmltag(Rn[0], false);
22424				switch(tag["date-value"]) {
22425					case "1904-01-01": WB.WBProps.date1904 = true;
22426					/* falls through */
22427					case "1900-01-01": baddate = 0;
22428				}
22429				break;
22430
22431			case 'graphic-properties': break; // 17.21 <style:graphic-properties>
22432			case 'calculation-settings': break; // 9.4.1 <table:calculation-settings>
22433			case 'named-expressions': break; // 9.4.11 <table:named-expressions>
22434			case 'label-range': break; // 9.4.9 <table:label-range>
22435			case 'label-ranges': break; // 9.4.10 <table:label-ranges>
22436			case 'named-expression': break; // 9.4.13 <table:named-expression>
22437			case 'sort': break; // 9.4.19 <table:sort>
22438			case 'sort-by': break; // 9.4.20 <table:sort-by>
22439			case 'sort-groups': break; // 9.4.22 <table:sort-groups>
22440
22441			case 'tab': break; // 6.1.4 <text:tab>
22442			case 'line-break': break; // 6.1.5 <text:line-break>
22443			case 'span': break; // 6.1.7 <text:span>
22444			case 'p': case '文本串': // 5.1.3 <text:p>
22445				if(['master-styles'].indexOf(state[state.length-1][0]) > -1) break;
22446				if(Rn[1]==='/' && (!ctag || !ctag['string-value'])) {
22447					var ptp = parse_text_p(str.slice(textpidx,Rn.index), textptag);
22448					textp = (textp.length > 0 ? textp + "\n" : "") + ptp[0];
22449				} else { textptag = parsexmltag(Rn[0], false); textpidx = Rn.index + Rn[0].length; }
22450				break; // <text:p>
22451			case 's': break; // <text:s>
22452
22453			case 'database-range': // 9.4.15 <table:database-range>
22454				if(Rn[1]==='/') break;
22455				try {
22456					_Ref = ods_to_csf_3D(parsexmltag(Rn[0])['target-range-address']);
22457					Sheets[_Ref[0]]['!autofilter'] = { ref:_Ref[1] };
22458				} catch(e) {/* empty */}
22459				break;
22460
22461			case 'date': break; // <*:date>
22462
22463			case 'object': break; // 10.4.6.2 <draw:object>
22464			case 'title': case '标题': break; // <*:title> OR <uof:标题>
22465			case 'desc': break; // <*:desc>
22466			case 'binary-data': break; // 10.4.5 TODO: b64 blob
22467
22468			/* 9.2 Advanced Tables */
22469			case 'table-source': break; // 9.2.6
22470			case 'scenario': break; // 9.2.6
22471
22472			case 'iteration': break; // 9.4.3 <table:iteration>
22473			case 'content-validations': break; // 9.4.4 <table:
22474			case 'content-validation': break; // 9.4.5 <table:
22475			case 'help-message': break; // 9.4.6 <table:
22476			case 'error-message': break; // 9.4.7 <table:
22477			case 'database-ranges': break; // 9.4.14 <table:database-ranges>
22478			case 'filter': break; // 9.5.2 <table:filter>
22479			case 'filter-and': break; // 9.5.3 <table:filter-and>
22480			case 'filter-or': break; // 9.5.4 <table:filter-or>
22481			case 'filter-condition': break; // 9.5.5 <table:filter-condition>
22482
22483			case 'list-level-style-bullet': break; // 16.31 <text:
22484			case 'list-level-style-number': break; // 16.32 <text:
22485			case 'list-level-properties': break; // 17.19 <style:
22486
22487			/* 7.3 Document Fields */
22488			case 'sender-firstname': // 7.3.6.2
22489			case 'sender-lastname': // 7.3.6.3
22490			case 'sender-initials': // 7.3.6.4
22491			case 'sender-title': // 7.3.6.5
22492			case 'sender-position': // 7.3.6.6
22493			case 'sender-email': // 7.3.6.7
22494			case 'sender-phone-private': // 7.3.6.8
22495			case 'sender-fax': // 7.3.6.9
22496			case 'sender-company': // 7.3.6.10
22497			case 'sender-phone-work': // 7.3.6.11
22498			case 'sender-street': // 7.3.6.12
22499			case 'sender-city': // 7.3.6.13
22500			case 'sender-postal-code': // 7.3.6.14
22501			case 'sender-country': // 7.3.6.15
22502			case 'sender-state-or-province': // 7.3.6.16
22503			case 'author-name': // 7.3.7.1
22504			case 'author-initials': // 7.3.7.2
22505			case 'chapter': // 7.3.8
22506			case 'file-name': // 7.3.9
22507			case 'template-name': // 7.3.9
22508			case 'sheet-name': // 7.3.9
22509				break;
22510
22511			case 'event-listener':
22512				break;
22513			/* TODO: FODS Properties */
22514			case 'initial-creator':
22515			case 'creation-date':
22516			case 'print-date':
22517			case 'generator':
22518			case 'document-statistic':
22519			case 'user-defined':
22520			case 'editing-duration':
22521			case 'editing-cycles':
22522				break;
22523
22524			/* TODO: FODS Config */
22525			case 'config-item':
22526				break;
22527
22528			/* TODO: style tokens */
22529			case 'page-number': break; // TODO <text:page-number>
22530			case 'page-count': break; // TODO <text:page-count>
22531			case 'time': break; // TODO <text:time>
22532
22533			/* 9.3 Advanced Table Cells */
22534			case 'cell-range-source': break; // 9.3.1 <table:
22535			case 'detective': break; // 9.3.2 <table:
22536			case 'operation': break; // 9.3.3 <table:
22537			case 'highlighted-range': break; // 9.3.4 <table:
22538
22539			/* 9.6 Data Pilot Tables <table: */
22540			case 'data-pilot-table': // 9.6.3
22541			case 'source-cell-range': // 9.6.5
22542			case 'source-service': // 9.6.6
22543			case 'data-pilot-field': // 9.6.7
22544			case 'data-pilot-level': // 9.6.8
22545			case 'data-pilot-subtotals': // 9.6.9
22546			case 'data-pilot-subtotal': // 9.6.10
22547			case 'data-pilot-members': // 9.6.11
22548			case 'data-pilot-member': // 9.6.12
22549			case 'data-pilot-display-info': // 9.6.13
22550			case 'data-pilot-sort-info': // 9.6.14
22551			case 'data-pilot-layout-info': // 9.6.15
22552			case 'data-pilot-field-reference': // 9.6.16
22553			case 'data-pilot-groups': // 9.6.17
22554			case 'data-pilot-group': // 9.6.18
22555			case 'data-pilot-group-member': // 9.6.19
22556				break;
22557
22558			/* 10.3 Drawing Shapes */
22559			case 'rect': // 10.3.2
22560				break;
22561
22562			/* 14.6 DDE Connections */
22563			case 'dde-connection-decls': // 14.6.2 <text:
22564			case 'dde-connection-decl': // 14.6.3 <text:
22565			case 'dde-link': // 14.6.4 <table:
22566			case 'dde-source': // 14.6.5 <office:
22567				break;
22568
22569			case 'properties': break; // 13.7 <form:properties>
22570			case 'property': break; // 13.8 <form:property>
22571
22572			case 'a': // 6.1.8 hyperlink
22573				if(Rn[1]!== '/') {
22574					atag = parsexmltag(Rn[0], false);
22575					if(!atag.href) break;
22576					atag.Target = unescapexml(atag.href); delete atag.href;
22577					if(atag.Target.charAt(0) == "#" && atag.Target.indexOf(".") > -1) {
22578						_Ref = ods_to_csf_3D(atag.Target.slice(1));
22579						atag.Target = "#" + _Ref[0] + "!" + _Ref[1];
22580					} else if(atag.Target.match(/^\.\.[\\\/]/)) atag.Target = atag.Target.slice(3);
22581				}
22582				break;
22583
22584			/* non-standard */
22585			case 'table-protection': break;
22586			case 'data-pilot-grand-total': break; // <table:
22587			case 'office-document-common-attrs': break; // bare
22588			default: switch(Rn[2]) {
22589				case 'dc:':       // TODO: properties
22590				case 'calcext:':  // ignore undocumented extensions
22591				case 'loext:':    // ignore undocumented extensions
22592				case 'ooo:':      // ignore undocumented extensions
22593				case 'chartooo:': // ignore undocumented extensions
22594				case 'draw:':     // TODO: drawing
22595				case 'style:':    // TODO: styles
22596				case 'chart:':    // TODO: charts
22597				case 'form:':     // TODO: forms
22598				case 'uof:':      // TODO: uof
22599				case '表:':       // TODO: uof
22600				case '字:':       // TODO: uof
22601					break;
22602				default: if(opts.WTF) throw new Error(Rn);
22603			}
22604		}
22605		var out/*:Workbook*/ = ({
22606			Sheets: Sheets,
22607			SheetNames: SheetNames,
22608			Workbook: WB
22609		}/*:any*/);
22610		if(opts.bookSheets) delete /*::(*/out/*:: :any)*/.Sheets;
22611		return out;
22612}
22613
22614function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
22615	opts = opts || ({}/*:any*/);
22616	if(safegetzipfile(zip, 'META-INF/manifest.xml')) parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts);
22617	var styles = getzipstr(zip, 'styles.xml');
22618	var Styles = styles && parse_ods_styles(utf8read(styles), opts);
22619	var content = getzipstr(zip, 'content.xml');
22620	if(!content) throw new Error("Missing content.xml in ODS / UOF file");
22621	var wb = parse_content_xml(utf8read(content), opts, Styles);
22622	if(safegetzipfile(zip, 'meta.xml')) wb.Props = parse_core_props(getzipdata(zip, 'meta.xml'));
22623	wb.bookType = "ods";
22624	return wb;
22625}
22626function parse_fods(data/*:string*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
22627	var wb = parse_content_xml(data, opts);
22628	wb.bookType = "fods";
22629	return wb;
22630}
22631
22632/* OpenDocument */
22633var write_styles_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function() {
22634	var master_styles = [
22635		'<office:master-styles>',
22636			'<style:master-page style:name="mp1" style:page-layout-name="mp1">',
22637				'<style:header/>',
22638				'<style:header-left style:display="false"/>',
22639				'<style:footer/>',
22640				'<style:footer-left style:display="false"/>',
22641			'</style:master-page>',
22642		'</office:master-styles>'
22643	].join("");
22644
22645	var payload = '<office:document-styles ' + wxt_helper({
22646		'xmlns:office':   "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
22647		'xmlns:table':    "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
22648		'xmlns:style':    "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
22649		'xmlns:text':     "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
22650		'xmlns:draw':     "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
22651		'xmlns:fo':       "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
22652		'xmlns:xlink':    "http://www.w3.org/1999/xlink",
22653		'xmlns:dc':       "http://purl.org/dc/elements/1.1/",
22654		'xmlns:number':   "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
22655		'xmlns:svg':      "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
22656		'xmlns:of':       "urn:oasis:names:tc:opendocument:xmlns:of:1.2",
22657		'office:version': "1.2"
22658	}) + '>' + master_styles + '</office:document-styles>';
22659
22660	return function wso(/*::wb, opts*/) {
22661		return XML_HEADER + payload;
22662	};
22663})();
22664
22665// TODO: find out if anyone actually read the spec.  LO has some wild errors
22666function write_number_format_ods(nf/*:string*/, nfidx/*:string*/)/*:string*/ {
22667	var type = "number", payload = "", nopts = { "style:name": nfidx }, c = "", i = 0;
22668	nf = nf.replace(/"[$]"/g, "$");
22669	/* TODO: replace with an actual parser based on a real grammar */
22670	j: {
22671		// TODO: support style maps
22672		if(nf.indexOf(";") > -1) {
22673			console.error("Unsupported ODS Style Map exported.  Using first branch of " + nf);
22674			nf = nf.slice(0, nf.indexOf(";"));
22675		}
22676
22677		if(nf == "@") { type = "text"; payload = "<number:text-content/>"; break j; }
22678
22679		/* currency flag */
22680		if(nf.indexOf(/\$/) > -1) { type = "currency"; }
22681
22682		/* opening string literal */
22683		if(nf[i] == '"') {
22684			c = "";
22685			while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
22686			if(nf[i+1] == "*") {
22687				i++;
22688				payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
22689			} else {
22690				payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
22691			}
22692			nf = nf.slice(i+1); i = 0;
22693		}
22694
22695		/* fractions */
22696		var t = nf.match(/# (\?+)\/(\?+)/);
22697		if(t) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:max-denominator-value": Math.max(+(t[1].replace(/./g, "9")), +(t[2].replace(/./g, "9"))) }); break j; }
22698		if((t=nf.match(/# (\?+)\/(\d+)/))) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:denominator-value": +t[2]}); break j; }
22699
22700		/* percentages */
22701		if((t=nf.match(/(\d+)(|\.\d+)%/))) { type = "percentage"; payload += writextag("number:number", null, {"number:decimal-places": t[2] && t.length - 1 || 0, "number:min-decimal-places": t[2] && t.length - 1 || 0, "number:min-integer-digits": t[1].length }) + "<number:text>%</number:text>"; break j; }
22702
22703		/* datetime */
22704		var has_time = false;
22705		if(["y","m","d"].indexOf(nf[0]) > -1) {
22706			type = "date";
22707			k: for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
22708				case "h": case "s": has_time = true; --i; break k;
22709				case "m":
22710					l: for(var h = i+1; h < nf.length; ++h) switch(nf[h]) {
22711						case "y": case "d": break l;
22712						case "h": case "s": has_time = true; --i; break k;
22713					}
22714					/* falls through */
22715				case "y": case "d":
22716					while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
22717					switch(c) {
22718						case "y": case "yy": payload += "<number:year/>"; break;
22719						case "yyy": case "yyyy": payload += '<number:year number:style="long"/>'; break;
22720						case "mmmmm": console.error("ODS has no equivalent of format |mmmmm|");
22721							/* falls through */
22722						case "m": case "mm": case "mmm": case "mmmm":
22723							payload += '<number:month number:style="' + (c.length % 2 ? "short" : "long") + '" number:textual="' + (c.length >= 3 ? "true" : "false") + '"/>';
22724							break;
22725						case "d": case "dd": payload += '<number:day number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
22726						case "ddd": case "dddd": payload += '<number:day-of-week number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
22727					}
22728					break;
22729				case '"':
22730					while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
22731					payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
22732					break;
22733				case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
22734				default: console.error("unrecognized character " + c + " in ODF format " + nf);
22735			}
22736			if(!has_time) break j;
22737			nf = nf.slice(i+1); i = 0;
22738		}
22739		if(nf.match(/^\[?[hms]/)) {
22740			if(type == "number") type = "time";
22741			if(nf.match(/\[/)) {
22742				nf = nf.replace(/[\[\]]/g, "");
22743				nopts['number:truncate-on-overflow'] = "false";
22744			}
22745			for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
22746				case "h": case "m": case "s":
22747					while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
22748					switch(c) {
22749						case "h": case "hh": payload += '<number:hours number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
22750						case "m": case "mm": payload += '<number:minutes number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
22751						case "s": case "ss":
22752							if(nf[i+1] == ".") do { c += nf[i+1]; ++i; } while(nf[i+1] == "0");
22753							payload += '<number:seconds number:style="' + (c.match("ss") ? "long" : "short") + '"' + (c.match(/\./) ? ' number:decimal-places="' + (c.match(/0+/)||[""])[0].length + '"' : "")+ '/>'; break;
22754					}
22755					break;
22756				case '"':
22757					while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
22758					payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
22759					break;
22760				case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
22761				case "a":
22762					if(nf.slice(i, i+3).toLowerCase() == "a/p") { payload += '<number:am-pm/>'; i += 2; break; } // Note: ODF does not support A/P
22763					if(nf.slice(i, i+5).toLowerCase() == "am/pm")  { payload += '<number:am-pm/>'; i += 4; break; }
22764					/* falls through */
22765				default: console.error("unrecognized character " + c + " in ODF format " + nf);
22766			}
22767			break j;
22768		}
22769
22770		/* currency flag */
22771		if(nf.indexOf(/\$/) > -1) { type = "currency"; }
22772
22773		/* should be in a char loop */
22774		if(nf[0] == "$") { payload += '<number:currency-symbol number:language="en" number:country="US">$</number:currency-symbol>'; nf = nf.slice(1); i = 0; }
22775		i = 0; if(nf[i] == '"') {
22776			while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
22777			if(nf[i+1] == "*") {
22778				i++;
22779				payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
22780			} else {
22781				payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
22782			}
22783			nf = nf.slice(i+1); i = 0;
22784		}
22785
22786		/* number TODO: interstitial text e.g. 000)000-0000 */
22787		var np = nf.match(/([#0][0#,]*)(\.[0#]*|)(E[+]?0*|)/i);
22788		if(!np || !np[0]) console.error("Could not find numeric part of " + nf);
22789		else {
22790			var base = np[1].replace(/,/g, "");
22791			payload += '<number:' + (np[3] ? "scientific-" : "")+ 'number' +
22792				' number:min-integer-digits="' + (base.indexOf("0") == -1 ? "0" : base.length - base.indexOf("0")) + '"' +
22793				(np[0].indexOf(",") > -1 ? ' number:grouping="true"' : "") +
22794				(np[2] && ' number:decimal-places="' + (np[2].length - 1) + '"' || ' number:decimal-places="0"') +
22795				(np[3] && np[3].indexOf("+") > -1 ? ' number:forced-exponent-sign="true"' : "" ) +
22796				(np[3] ? ' number:min-exponent-digits="' + np[3].match(/0+/)[0].length + '"' : "" ) +
22797				'>' +
22798				/* TODO: interstitial text placeholders */
22799				'</number:' + (np[3] ? "scientific-" : "") + 'number>';
22800			i = np.index + np[0].length;
22801		}
22802
22803		/* residual text */
22804		if(nf[i] == '"') {
22805			c = "";
22806			while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
22807			payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
22808		}
22809	}
22810
22811	if(!payload) { console.error("Could not generate ODS number format for |" + nf + "|"); return ""; }
22812	return writextag("number:" + type + "-style", payload, nopts);
22813}
22814
22815function write_names_ods(Names, SheetNames, idx) {
22816	var scoped = Names.filter(function(name) { return name.Sheet == (idx == -1 ? null : idx); });
22817	if(!scoped.length) return "";
22818	return "      <table:named-expressions>\n" + scoped.map(function(name) {
22819		var odsref =  csf_to_ods_3D(name.Ref);
22820		return "        " + writextag("table:named-range", null, {
22821			"table:name": name.Name,
22822			"table:cell-range-address": odsref,
22823			"table:base-cell-address": odsref.replace(/[\.]?[^\.]*$/, ".$A$1")
22824		});
22825	}).join("\n") + "\n      </table:named-expressions>\n";
22826}
22827var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function() {
22828	/* 6.1.2 White Space Characters */
22829	var write_text_p = function(text/*:string*/)/*:string*/ {
22830		return escapexml(text)
22831			.replace(/  +/g, function($$){return '<text:s text:c="'+$$.length+'"/>';})
22832			.replace(/\t/g, "<text:tab/>")
22833			.replace(/\n/g, "</text:p><text:p>")
22834			.replace(/^ /, "<text:s/>").replace(/ $/, "<text:s/>");
22835	};
22836
22837	var null_cell_xml = '          <table:table-cell />\n';
22838	var covered_cell_xml = '          <table:covered-table-cell/>\n';
22839	var write_ws = function(ws, wb/*:Workbook*/, i/*:number*/, opts, nfs)/*:string*/ {
22840		/* Section 9 Tables */
22841		var o/*:Array<string>*/ = [];
22842		o.push('      <table:table table:name="' + escapexml(wb.SheetNames[i]) + '" table:style-name="ta1">\n');
22843		var R=0,C=0, range = decode_range(ws['!ref']||"A1");
22844		var marr/*:Array<Range>*/ = ws['!merges'] || [], mi = 0;
22845		var dense = Array.isArray(ws);
22846		if(ws["!cols"]) {
22847			for(C = 0; C <= range.e.c; ++C) o.push('        <table:table-column' + (ws["!cols"][C] ? ' table:style-name="co' + ws["!cols"][C].ods + '"' : '') + '></table:table-column>\n');
22848		}
22849		var H = "", ROWS = ws["!rows"]||[];
22850		for(R = 0; R < range.s.r; ++R) {
22851			H = ROWS[R] ? ' table:style-name="ro' + ROWS[R].ods + '"' : "";
22852			o.push('        <table:table-row' + H + '></table:table-row>\n');
22853		}
22854		for(; R <= range.e.r; ++R) {
22855			H = ROWS[R] ? ' table:style-name="ro' + ROWS[R].ods + '"' : "";
22856			o.push('        <table:table-row' + H + '>\n');
22857			for(C=0; C < range.s.c; ++C) o.push(null_cell_xml);
22858			for(; C <= range.e.c; ++C) {
22859				var skip = false, ct = {}, textp = "";
22860				for(mi = 0; mi != marr.length; ++mi) {
22861					if(marr[mi].s.c > C) continue;
22862					if(marr[mi].s.r > R) continue;
22863					if(marr[mi].e.c < C) continue;
22864					if(marr[mi].e.r < R) continue;
22865					if(marr[mi].s.c != C || marr[mi].s.r != R) skip = true;
22866					ct['table:number-columns-spanned'] = (marr[mi].e.c - marr[mi].s.c + 1);
22867					ct['table:number-rows-spanned'] =    (marr[mi].e.r - marr[mi].s.r + 1);
22868					break;
22869				}
22870				if(skip) { o.push(covered_cell_xml); continue; }
22871				var ref = encode_cell({r:R, c:C}), cell = dense ? (ws[R]||[])[C]: ws[ref];
22872				if(cell && cell.f) {
22873					ct['table:formula'] = escapexml(csf_to_ods_formula(cell.f));
22874					if(cell.F) {
22875						if(cell.F.slice(0, ref.length) == ref) {
22876							var _Fref = decode_range(cell.F);
22877							ct['table:number-matrix-columns-spanned'] = (_Fref.e.c - _Fref.s.c + 1);
22878							ct['table:number-matrix-rows-spanned'] =    (_Fref.e.r - _Fref.s.r + 1);
22879						}
22880					}
22881				}
22882				if(!cell) { o.push(null_cell_xml); continue; }
22883				switch(cell.t) {
22884					case 'b':
22885						textp = (cell.v ? 'TRUE' : 'FALSE');
22886						ct['office:value-type'] = "boolean";
22887						ct['office:boolean-value'] = (cell.v ? 'true' : 'false');
22888						break;
22889					case 'n':
22890						textp = (cell.w||String(cell.v||0));
22891						ct['office:value-type'] = "float";
22892						ct['office:value'] = (cell.v||0);
22893						break;
22894					case 's': case 'str':
22895						textp = cell.v == null ? "" : cell.v;
22896						ct['office:value-type'] = "string";
22897						break;
22898					case 'd':
22899						textp = (cell.w||(parseDate(cell.v).toISOString()));
22900						ct['office:value-type'] = "date";
22901						ct['office:date-value'] = (parseDate(cell.v).toISOString());
22902						ct['table:style-name'] = "ce1";
22903						break;
22904					//case 'e':
22905					default: o.push(null_cell_xml); continue;
22906				}
22907				var text_p = write_text_p(textp);
22908				if(cell.l && cell.l.Target) {
22909					var _tgt = cell.l.Target;
22910					_tgt = _tgt.charAt(0) == "#" ? "#" + csf_to_ods_3D(_tgt.slice(1)) : _tgt;
22911					// TODO: choose correct parent path format based on link delimiters
22912					if(_tgt.charAt(0) != "#" && !_tgt.match(/^\w+:/)) _tgt = '../' + _tgt;
22913					text_p = writextag('text:a', text_p, {'xlink:href': _tgt.replace(/&/g, "&amp;")});
22914				}
22915				if(nfs[cell.z]) ct["table:style-name"] = "ce" + nfs[cell.z].slice(1);
22916				o.push('          ' + writextag('table:table-cell', writextag('text:p', text_p, {}), ct) + '\n');
22917			}
22918			o.push('        </table:table-row>\n');
22919		}
22920		if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, i));
22921		o.push('      </table:table>\n');
22922		return o.join("");
22923	};
22924
22925	var write_automatic_styles_ods = function(o/*:Array<string>*/, wb) {
22926		o.push(' <office:automatic-styles>\n');
22927
22928		/* column styles */
22929		var cidx = 0;
22930		wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
22931			if(!ws) return;
22932			if(ws["!cols"]) {
22933				for(var C = 0; C < ws["!cols"].length; ++C) if(ws["!cols"][C]) {
22934					var colobj = ws["!cols"][C];
22935					if(colobj.width == null && colobj.wpx == null && colobj.wch == null) continue;
22936					process_col(colobj);
22937					colobj.ods = cidx;
22938					var w = ws["!cols"][C].wpx + "px";
22939					o.push('  <style:style style:name="co' + cidx + '" style:family="table-column">\n');
22940					o.push('   <style:table-column-properties fo:break-before="auto" style:column-width="' + w + '"/>\n');
22941					o.push('  </style:style>\n');
22942					++cidx;
22943				}
22944			}
22945		});
22946
22947		/* row styles */
22948		var ridx = 0;
22949		wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
22950			if(!ws) return;
22951			if(ws["!rows"]) {
22952				for(var R = 0; R < ws["!rows"].length; ++R) if(ws["!rows"][R]) {
22953					ws["!rows"][R].ods = ridx;
22954					var h = ws["!rows"][R].hpx + "px";
22955					o.push('  <style:style style:name="ro' + ridx + '" style:family="table-row">\n');
22956					o.push('   <style:table-row-properties fo:break-before="auto" style:row-height="' + h + '"/>\n');
22957					o.push('  </style:style>\n');
22958					++ridx;
22959				}
22960			}
22961		});
22962
22963		/* table */
22964		o.push('  <style:style style:name="ta1" style:family="table" style:master-page-name="mp1">\n');
22965		o.push('   <style:table-properties table:display="true" style:writing-mode="lr-tb"/>\n');
22966		o.push('  </style:style>\n');
22967
22968		o.push('  <number:date-style style:name="N37" number:automatic-order="true">\n');
22969		o.push('   <number:month number:style="long"/>\n');
22970		o.push('   <number:text>/</number:text>\n');
22971		o.push('   <number:day number:style="long"/>\n');
22972		o.push('   <number:text>/</number:text>\n');
22973		o.push('   <number:year/>\n');
22974		o.push('  </number:date-style>\n');
22975
22976		/* number formats, table cells, text */
22977		var nfs = {};
22978		var nfi = 69;
22979		wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
22980			if(!ws) return;
22981			var range = decode_range(ws["!ref"]);
22982			for(var R = 0; R <= range.e.r; ++R) for(var C = 0; C <= range.e.c; ++C) {
22983				var c = Array.isArray(ws) ? (ws[R]||[])[C] : ws[encode_cell({r:R,c:C})];
22984				if(!c || !c.z || c.z.toLowerCase() == "general") continue;
22985				if(!nfs[c.z]) {
22986					var out = write_number_format_ods(c.z, "N" + nfi);
22987					if(out) { nfs[c.z] = "N" + nfi; ++nfi; o.push(out + "\n"); }
22988				}
22989			}
22990		});
22991		o.push('  <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N37"/>\n');
22992		keys(nfs).forEach(function(nf) {
22993			o.push('<style:style style:name="ce' + nfs[nf].slice(1) + '" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="' + nfs[nf] + '"/>\n');
22994		});
22995
22996		/* page-layout */
22997
22998		o.push(' </office:automatic-styles>\n');
22999		return nfs;
23000	};
23001
23002	return function wcx(wb, opts) {
23003		var o = [XML_HEADER];
23004		/* 3.1.3.2 */
23005		var attr = wxt_helper({
23006			'xmlns:office':       "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
23007			'xmlns:table':        "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
23008			'xmlns:style':        "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
23009			'xmlns:text':         "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
23010			'xmlns:draw':         "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
23011			'xmlns:fo':           "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
23012			'xmlns:xlink':        "http://www.w3.org/1999/xlink",
23013			'xmlns:dc':           "http://purl.org/dc/elements/1.1/",
23014			'xmlns:meta':         "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
23015			'xmlns:number':       "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
23016			'xmlns:presentation': "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
23017			'xmlns:svg':          "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
23018			'xmlns:chart':        "urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
23019			'xmlns:dr3d':         "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
23020			'xmlns:math':         "http://www.w3.org/1998/Math/MathML",
23021			'xmlns:form':         "urn:oasis:names:tc:opendocument:xmlns:form:1.0",
23022			'xmlns:script':       "urn:oasis:names:tc:opendocument:xmlns:script:1.0",
23023			'xmlns:ooo':          "http://openoffice.org/2004/office",
23024			'xmlns:ooow':         "http://openoffice.org/2004/writer",
23025			'xmlns:oooc':         "http://openoffice.org/2004/calc",
23026			'xmlns:dom':          "http://www.w3.org/2001/xml-events",
23027			'xmlns:xforms':       "http://www.w3.org/2002/xforms",
23028			'xmlns:xsd':          "http://www.w3.org/2001/XMLSchema",
23029			'xmlns:xsi':          "http://www.w3.org/2001/XMLSchema-instance",
23030			'xmlns:sheet':        "urn:oasis:names:tc:opendocument:sh33tjs:1.0",
23031			'xmlns:rpt':          "http://openoffice.org/2005/report",
23032			'xmlns:of':           "urn:oasis:names:tc:opendocument:xmlns:of:1.2",
23033			'xmlns:xhtml':        "http://www.w3.org/1999/xhtml",
23034			'xmlns:grddl':        "http://www.w3.org/2003/g/data-view#",
23035			'xmlns:tableooo':     "http://openoffice.org/2009/table",
23036			'xmlns:drawooo':      "http://openoffice.org/2010/draw",
23037			'xmlns:calcext':      "urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0",
23038			'xmlns:loext':        "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
23039			'xmlns:field':        "urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0",
23040			'xmlns:formx':        "urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0",
23041			'xmlns:css3t':        "http://www.w3.org/TR/css3-text/",
23042			'office:version':     "1.2"
23043		});
23044
23045		var fods = wxt_helper({
23046			'xmlns:config':    "urn:oasis:names:tc:opendocument:xmlns:config:1.0",
23047			'office:mimetype': "application/vnd.oasis.opendocument.spreadsheet"
23048		});
23049
23050		if(opts.bookType == "fods") {
23051			o.push('<office:document' + attr + fods + '>\n');
23052			o.push(write_meta_ods().replace(/<office:document-meta.*?>/, "").replace(/<\/office:document-meta>/, "") + "\n");
23053			// TODO: settings (equiv of settings.xml for ODS)
23054		} else o.push('<office:document-content' + attr  + '>\n');
23055		// o.push('  <office:scripts/>\n');
23056		var nfs = write_automatic_styles_ods(o, wb);
23057		o.push('  <office:body>\n');
23058		o.push('    <office:spreadsheet>\n');
23059		if(((wb.Workbook||{}).WBProps||{}).date1904) o.push('      <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n        <table:null-date table:date-value="1904-01-01"/>\n      </table:calculation-settings>\n');
23060		for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts, nfs));
23061		if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
23062		o.push('    </office:spreadsheet>\n');
23063		o.push('  </office:body>\n');
23064		if(opts.bookType == "fods") o.push('</office:document>');
23065		else o.push('</office:document-content>');
23066		return o.join("");
23067	};
23068})();
23069
23070function write_ods(wb/*:any*/, opts/*:any*/) {
23071	if(opts.bookType == "fods") return write_content_ods(wb, opts);
23072
23073	var zip = zip_new();
23074	var f = "";
23075
23076	var manifest/*:Array<Array<string> >*/ = [];
23077	var rdf/*:Array<[string, string]>*/ = [];
23078
23079	/* Part 3 Section 3.3 MIME Media Type */
23080	f = "mimetype";
23081	zip_add_file(zip, f, "application/vnd.oasis.opendocument.spreadsheet");
23082
23083	/* Part 1 Section 2.2 Documents */
23084	f = "content.xml";
23085	zip_add_file(zip, f, write_content_ods(wb, opts));
23086	manifest.push([f, "text/xml"]);
23087	rdf.push([f, "ContentFile"]);
23088
23089	/* TODO: these are hard-coded styles to satiate excel */
23090	f = "styles.xml";
23091	zip_add_file(zip, f, write_styles_ods(wb, opts));
23092	manifest.push([f, "text/xml"]);
23093	rdf.push([f, "StylesFile"]);
23094
23095	/* TODO: this is hard-coded to satiate excel */
23096	f = "meta.xml";
23097	zip_add_file(zip, f, XML_HEADER + write_meta_ods(/*::wb, opts*/));
23098	manifest.push([f, "text/xml"]);
23099	rdf.push([f, "MetadataFile"]);
23100
23101	/* Part 3 Section 6 Metadata Manifest File */
23102	f = "manifest.rdf";
23103	zip_add_file(zip, f, write_rdf(rdf/*, opts*/));
23104	manifest.push([f, "application/rdf+xml"]);
23105
23106	/* Part 3 Section 4 Manifest File */
23107	f = "META-INF/manifest.xml";
23108	zip_add_file(zip, f, write_manifest(manifest/*, opts*/));
23109
23110	return zip;
23111}
23112
23113/*! sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
23114var subarray = function() {
23115  try {
23116    if (typeof Uint8Array == "undefined")
23117      return "slice";
23118    if (typeof Uint8Array.prototype.subarray == "undefined")
23119      return "slice";
23120    if (typeof Buffer !== "undefined") {
23121      if (typeof Buffer.prototype.subarray == "undefined")
23122        return "slice";
23123      if ((typeof Buffer.from == "function" ? Buffer.from([72, 62]) : new Buffer([72, 62])) instanceof Uint8Array)
23124        return "subarray";
23125      return "slice";
23126    }
23127    return "subarray";
23128  } catch (e) {
23129    return "slice";
23130  }
23131}();
23132function u8_to_dataview(array) {
23133  return new DataView(array.buffer, array.byteOffset, array.byteLength);
23134}
23135function u8str(u8) {
23136  return typeof TextDecoder != "undefined" ? new TextDecoder().decode(u8) : utf8read(a2s(u8));
23137}
23138function stru8(str) {
23139  return typeof TextEncoder != "undefined" ? new TextEncoder().encode(str) : s2a(utf8write(str));
23140}
23141function u8contains(body, search) {
23142  var L = body.indexOf(search[0]);
23143  if (L == -1)
23144    return false;
23145  outer:
23146    for (; L <= body.length - search.length; ++L) {
23147      for (var j = 0; j < search.length; ++j)
23148        if (body[L + j] != search[j])
23149          continue outer;
23150      return true;
23151    }
23152  return false;
23153}
23154function u8concat(u8a) {
23155  var len = u8a.reduce(function(acc, x) {
23156    return acc + x.length;
23157  }, 0);
23158  var out = new Uint8Array(len);
23159  var off = 0;
23160  u8a.forEach(function(u8) {
23161    out.set(u8, off);
23162    off += u8.length;
23163  });
23164  return out;
23165}
23166function popcnt(x) {
23167  x -= x >> 1 & 1431655765;
23168  x = (x & 858993459) + (x >> 2 & 858993459);
23169  return (x + (x >> 4) & 252645135) * 16843009 >>> 24;
23170}
23171function readDecimal128LE(buf, offset) {
23172  var exp = (buf[offset + 15] & 127) << 7 | buf[offset + 14] >> 1;
23173  var mantissa = buf[offset + 14] & 1;
23174  for (var j = offset + 13; j >= offset; --j)
23175    mantissa = mantissa * 256 + buf[j];
23176  return (buf[offset + 15] & 128 ? -mantissa : mantissa) * Math.pow(10, exp - 6176);
23177}
23178function writeDecimal128LE(buf, offset, value) {
23179  var exp = Math.floor(value == 0 ? 0 : Math.LOG10E * Math.log(Math.abs(value))) + 6176 - 16;
23180  var mantissa = value / Math.pow(10, exp - 6176);
23181  buf[offset + 15] |= exp >> 7;
23182  buf[offset + 14] |= (exp & 127) << 1;
23183  for (var i = 0; mantissa >= 1; ++i, mantissa /= 256)
23184    buf[offset + i] = mantissa & 255;
23185  buf[offset + 15] |= value >= 0 ? 0 : 128;
23186}
23187function parse_varint49(buf, ptr) {
23188  var l = ptr ? ptr[0] : 0;
23189  var usz = buf[l] & 127;
23190  varint:
23191    if (buf[l++] >= 128) {
23192      usz |= (buf[l] & 127) << 7;
23193      if (buf[l++] < 128)
23194        break varint;
23195      usz |= (buf[l] & 127) << 14;
23196      if (buf[l++] < 128)
23197        break varint;
23198      usz |= (buf[l] & 127) << 21;
23199      if (buf[l++] < 128)
23200        break varint;
23201      usz += (buf[l] & 127) * Math.pow(2, 28);
23202      ++l;
23203      if (buf[l++] < 128)
23204        break varint;
23205      usz += (buf[l] & 127) * Math.pow(2, 35);
23206      ++l;
23207      if (buf[l++] < 128)
23208        break varint;
23209      usz += (buf[l] & 127) * Math.pow(2, 42);
23210      ++l;
23211      if (buf[l++] < 128)
23212        break varint;
23213    }
23214  if (ptr)
23215    ptr[0] = l;
23216  return usz;
23217}
23218function write_varint49(v) {
23219  var usz = new Uint8Array(7);
23220  usz[0] = v & 127;
23221  var L = 1;
23222  sz:
23223    if (v > 127) {
23224      usz[L - 1] |= 128;
23225      usz[L] = v >> 7 & 127;
23226      ++L;
23227      if (v <= 16383)
23228        break sz;
23229      usz[L - 1] |= 128;
23230      usz[L] = v >> 14 & 127;
23231      ++L;
23232      if (v <= 2097151)
23233        break sz;
23234      usz[L - 1] |= 128;
23235      usz[L] = v >> 21 & 127;
23236      ++L;
23237      if (v <= 268435455)
23238        break sz;
23239      usz[L - 1] |= 128;
23240      usz[L] = v / 256 >>> 21 & 127;
23241      ++L;
23242      if (v <= 34359738367)
23243        break sz;
23244      usz[L - 1] |= 128;
23245      usz[L] = v / 65536 >>> 21 & 127;
23246      ++L;
23247      if (v <= 4398046511103)
23248        break sz;
23249      usz[L - 1] |= 128;
23250      usz[L] = v / 16777216 >>> 21 & 127;
23251      ++L;
23252    }
23253  return usz[subarray](0, L);
23254}
23255function parse_packed_varints(buf) {
23256  var ptr = [0];
23257  var out = [];
23258  while (ptr[0] < buf.length)
23259    out.push(parse_varint49(buf, ptr));
23260  return out;
23261}
23262function write_packed_varints(nums) {
23263  return u8concat(nums.map(function(x) {
23264    return write_varint49(x);
23265  }));
23266}
23267function varint_to_i32(buf) {
23268  var l = 0, i32 = buf[l] & 127;
23269  varint:
23270    if (buf[l++] >= 128) {
23271      i32 |= (buf[l] & 127) << 7;
23272      if (buf[l++] < 128)
23273        break varint;
23274      i32 |= (buf[l] & 127) << 14;
23275      if (buf[l++] < 128)
23276        break varint;
23277      i32 |= (buf[l] & 127) << 21;
23278      if (buf[l++] < 128)
23279        break varint;
23280      i32 |= (buf[l] & 127) << 28;
23281    }
23282  return i32;
23283}
23284function varint_to_u64(buf) {
23285  var l = 0, lo = buf[l] & 127, hi = 0;
23286  varint:
23287    if (buf[l++] >= 128) {
23288      lo |= (buf[l] & 127) << 7;
23289      if (buf[l++] < 128)
23290        break varint;
23291      lo |= (buf[l] & 127) << 14;
23292      if (buf[l++] < 128)
23293        break varint;
23294      lo |= (buf[l] & 127) << 21;
23295      if (buf[l++] < 128)
23296        break varint;
23297      lo |= (buf[l] & 127) << 28;
23298      hi = buf[l] >> 4 & 7;
23299      if (buf[l++] < 128)
23300        break varint;
23301      hi |= (buf[l] & 127) << 3;
23302      if (buf[l++] < 128)
23303        break varint;
23304      hi |= (buf[l] & 127) << 10;
23305      if (buf[l++] < 128)
23306        break varint;
23307      hi |= (buf[l] & 127) << 17;
23308      if (buf[l++] < 128)
23309        break varint;
23310      hi |= (buf[l] & 127) << 24;
23311      if (buf[l++] < 128)
23312        break varint;
23313      hi |= (buf[l] & 127) << 31;
23314    }
23315  return [lo >>> 0, hi >>> 0];
23316}
23317function parse_shallow(buf) {
23318  var out = [], ptr = [0];
23319  while (ptr[0] < buf.length) {
23320    var off = ptr[0];
23321    var num = parse_varint49(buf, ptr);
23322    var type = num & 7;
23323    num = Math.floor(num / 8);
23324    var len = 0;
23325    var res;
23326    if (num == 0)
23327      break;
23328    switch (type) {
23329      case 0:
23330        {
23331          var l = ptr[0];
23332          while (buf[ptr[0]++] >= 128)
23333            ;
23334          res = buf[subarray](l, ptr[0]);
23335        }
23336        break;
23337      case 5:
23338        len = 4;
23339        res = buf[subarray](ptr[0], ptr[0] + len);
23340        ptr[0] += len;
23341        break;
23342      case 1:
23343        len = 8;
23344        res = buf[subarray](ptr[0], ptr[0] + len);
23345        ptr[0] += len;
23346        break;
23347      case 2:
23348        len = parse_varint49(buf, ptr);
23349        res = buf[subarray](ptr[0], ptr[0] + len);
23350        ptr[0] += len;
23351        break;
23352      case 3:
23353      case 4:
23354      default:
23355        throw new Error("PB Type ".concat(type, " for Field ").concat(num, " at offset ").concat(off));
23356    }
23357    var v = { data: res, type: type };
23358    if (out[num] == null)
23359      out[num] = [v];
23360    else
23361      out[num].push(v);
23362  }
23363  return out;
23364}
23365function write_shallow(proto) {
23366  var out = [];
23367  proto.forEach(function(field, idx) {
23368    if (idx == 0)
23369      return;
23370    field.forEach(function(item) {
23371      if (!item.data)
23372        return;
23373      out.push(write_varint49(idx * 8 + item.type));
23374      if (item.type == 2)
23375        out.push(write_varint49(item.data.length));
23376      out.push(item.data);
23377    });
23378  });
23379  return u8concat(out);
23380}
23381function mappa(data, cb) {
23382  return (data == null ? void 0 : data.map(function(d) {
23383    return cb(d.data);
23384  })) || [];
23385}
23386function parse_iwa_file(buf) {
23387  var _a;
23388  var out = [], ptr = [0];
23389  while (ptr[0] < buf.length) {
23390    var len = parse_varint49(buf, ptr);
23391    var ai = parse_shallow(buf[subarray](ptr[0], ptr[0] + len));
23392    ptr[0] += len;
23393    var res = {
23394      id: varint_to_i32(ai[1][0].data),
23395      messages: []
23396    };
23397    ai[2].forEach(function(b) {
23398      var mi = parse_shallow(b.data);
23399      var fl = varint_to_i32(mi[3][0].data);
23400      res.messages.push({
23401        meta: mi,
23402        data: buf[subarray](ptr[0], ptr[0] + fl)
23403      });
23404      ptr[0] += fl;
23405    });
23406    if ((_a = ai[3]) == null ? void 0 : _a[0])
23407      res.merge = varint_to_i32(ai[3][0].data) >>> 0 > 0;
23408    out.push(res);
23409  }
23410  return out;
23411}
23412function write_iwa_file(ias) {
23413  var bufs = [];
23414  ias.forEach(function(ia) {
23415    var ai = [
23416      [],
23417      [{ data: write_varint49(ia.id), type: 0 }],
23418      []
23419    ];
23420    if (ia.merge != null)
23421      ai[3] = [{ data: write_varint49(+!!ia.merge), type: 0 }];
23422    var midata = [];
23423    ia.messages.forEach(function(mi) {
23424      midata.push(mi.data);
23425      mi.meta[3] = [{ type: 0, data: write_varint49(mi.data.length) }];
23426      ai[2].push({ data: write_shallow(mi.meta), type: 2 });
23427    });
23428    var aipayload = write_shallow(ai);
23429    bufs.push(write_varint49(aipayload.length));
23430    bufs.push(aipayload);
23431    midata.forEach(function(mid) {
23432      return bufs.push(mid);
23433    });
23434  });
23435  return u8concat(bufs);
23436}
23437function parse_snappy_chunk(type, buf) {
23438  if (type != 0)
23439    throw new Error("Unexpected Snappy chunk type ".concat(type));
23440  var ptr = [0];
23441  var usz = parse_varint49(buf, ptr);
23442  var chunks = [];
23443  while (ptr[0] < buf.length) {
23444    var tag = buf[ptr[0]] & 3;
23445    if (tag == 0) {
23446      var len = buf[ptr[0]++] >> 2;
23447      if (len < 60)
23448        ++len;
23449      else {
23450        var c = len - 59;
23451        len = buf[ptr[0]];
23452        if (c > 1)
23453          len |= buf[ptr[0] + 1] << 8;
23454        if (c > 2)
23455          len |= buf[ptr[0] + 2] << 16;
23456        if (c > 3)
23457          len |= buf[ptr[0] + 3] << 24;
23458        len >>>= 0;
23459        len++;
23460        ptr[0] += c;
23461      }
23462      chunks.push(buf[subarray](ptr[0], ptr[0] + len));
23463      ptr[0] += len;
23464      continue;
23465    } else {
23466      var offset = 0, length = 0;
23467      if (tag == 1) {
23468        length = (buf[ptr[0]] >> 2 & 7) + 4;
23469        offset = (buf[ptr[0]++] & 224) << 3;
23470        offset |= buf[ptr[0]++];
23471      } else {
23472        length = (buf[ptr[0]++] >> 2) + 1;
23473        if (tag == 2) {
23474          offset = buf[ptr[0]] | buf[ptr[0] + 1] << 8;
23475          ptr[0] += 2;
23476        } else {
23477          offset = (buf[ptr[0]] | buf[ptr[0] + 1] << 8 | buf[ptr[0] + 2] << 16 | buf[ptr[0] + 3] << 24) >>> 0;
23478          ptr[0] += 4;
23479        }
23480      }
23481      if (offset == 0)
23482        throw new Error("Invalid offset 0");
23483      var j = chunks.length - 1, off = offset;
23484      while (j >= 0 && off >= chunks[j].length) {
23485        off -= chunks[j].length;
23486        --j;
23487      }
23488      if (j < 0) {
23489        if (off == 0)
23490          off = chunks[j = 0].length;
23491        else
23492          throw new Error("Invalid offset beyond length");
23493      }
23494      if (length < off)
23495        chunks.push(chunks[j][subarray](chunks[j].length - off, chunks[j].length - off + length));
23496      else {
23497        if (off > 0) {
23498          chunks.push(chunks[j][subarray](chunks[j].length - off));
23499          length -= off;
23500        }
23501        ++j;
23502        while (length >= chunks[j].length) {
23503          chunks.push(chunks[j]);
23504          length -= chunks[j].length;
23505          ++j;
23506        }
23507        if (length)
23508          chunks.push(chunks[j][subarray](0, length));
23509      }
23510      if (chunks.length > 100)
23511        chunks = [u8concat(chunks)];
23512    }
23513  }
23514  if (chunks.reduce(function(acc, u8) {
23515    return acc + u8.length;
23516  }, 0) != usz)
23517    throw new Error("Unexpected length: ".concat(chunks.reduce(function(acc, u8) {
23518      return acc + u8.length;
23519    }, 0), " != ").concat(usz));
23520  return chunks;
23521}
23522function decompress_iwa_file(buf) {
23523  if (Array.isArray(buf))
23524    buf = new Uint8Array(buf);
23525  var out = [];
23526  var l = 0;
23527  while (l < buf.length) {
23528    var t = buf[l++];
23529    var len = buf[l] | buf[l + 1] << 8 | buf[l + 2] << 16;
23530    l += 3;
23531    out.push.apply(out, parse_snappy_chunk(t, buf[subarray](l, l + len)));
23532    l += len;
23533  }
23534  if (l !== buf.length)
23535    throw new Error("data is not a valid framed stream!");
23536  return u8concat(out);
23537}
23538function compress_iwa_file(buf) {
23539  var out = [];
23540  var l = 0;
23541  while (l < buf.length) {
23542    var c = Math.min(buf.length - l, 268435455);
23543    var frame = new Uint8Array(4);
23544    out.push(frame);
23545    var usz = write_varint49(c);
23546    var L = usz.length;
23547    out.push(usz);
23548    if (c <= 60) {
23549      L++;
23550      out.push(new Uint8Array([c - 1 << 2]));
23551    } else if (c <= 256) {
23552      L += 2;
23553      out.push(new Uint8Array([240, c - 1 & 255]));
23554    } else if (c <= 65536) {
23555      L += 3;
23556      out.push(new Uint8Array([244, c - 1 & 255, c - 1 >> 8 & 255]));
23557    } else if (c <= 16777216) {
23558      L += 4;
23559      out.push(new Uint8Array([248, c - 1 & 255, c - 1 >> 8 & 255, c - 1 >> 16 & 255]));
23560    } else if (c <= 4294967296) {
23561      L += 5;
23562      out.push(new Uint8Array([252, c - 1 & 255, c - 1 >> 8 & 255, c - 1 >> 16 & 255, c - 1 >>> 24 & 255]));
23563    }
23564    out.push(buf[subarray](l, l + c));
23565    L += c;
23566    frame[0] = 0;
23567    frame[1] = L & 255;
23568    frame[2] = L >> 8 & 255;
23569    frame[3] = L >> 16 & 255;
23570    l += c;
23571  }
23572  return u8concat(out);
23573}
23574var numbers_lut_new = function() {
23575  return { sst: [], rsst: [], ofmt: [], nfmt: [] };
23576};
23577function numbers_format_cell(cell, t, flags, ofmt, nfmt) {
23578  var _a, _b, _c, _d;
23579  var ctype = t & 255, ver = t >> 8;
23580  var fmt = ver >= 5 ? nfmt : ofmt;
23581  dur:
23582    if (flags & (ver > 4 ? 8 : 4) && cell.t == "n" && ctype == 7) {
23583      var dstyle = ((_a = fmt[7]) == null ? void 0 : _a[0]) ? parse_varint49(fmt[7][0].data) : -1;
23584      if (dstyle == -1)
23585        break dur;
23586      var dmin = ((_b = fmt[15]) == null ? void 0 : _b[0]) ? parse_varint49(fmt[15][0].data) : -1;
23587      var dmax = ((_c = fmt[16]) == null ? void 0 : _c[0]) ? parse_varint49(fmt[16][0].data) : -1;
23588      var auto = ((_d = fmt[40]) == null ? void 0 : _d[0]) ? parse_varint49(fmt[40][0].data) : -1;
23589      var d = cell.v, dd = d;
23590      autodur:
23591        if (auto) {
23592          if (d == 0) {
23593            dmin = dmax = 2;
23594            break autodur;
23595          }
23596          if (d >= 604800)
23597            dmin = 1;
23598          else if (d >= 86400)
23599            dmin = 2;
23600          else if (d >= 3600)
23601            dmin = 4;
23602          else if (d >= 60)
23603            dmin = 8;
23604          else if (d >= 1)
23605            dmin = 16;
23606          else
23607            dmin = 32;
23608          if (Math.floor(d) != d)
23609            dmax = 32;
23610          else if (d % 60)
23611            dmax = 16;
23612          else if (d % 3600)
23613            dmax = 8;
23614          else if (d % 86400)
23615            dmax = 4;
23616          else if (d % 604800)
23617            dmax = 2;
23618          if (dmax < dmin)
23619            dmax = dmin;
23620        }
23621      if (dmin == -1 || dmax == -1)
23622        break dur;
23623      var dstr = [], zstr = [];
23624      if (dmin == 1) {
23625        dd = d / 604800;
23626        if (dmax == 1) {
23627          zstr.push('d"d"');
23628        } else {
23629          dd |= 0;
23630          d -= 604800 * dd;
23631        }
23632        dstr.push(dd + (dstyle == 2 ? " week" + (dd == 1 ? "" : "s") : dstyle == 1 ? "w" : ""));
23633      }
23634      if (dmin <= 2 && dmax >= 2) {
23635        dd = d / 86400;
23636        if (dmax > 2) {
23637          dd |= 0;
23638          d -= 86400 * dd;
23639        }
23640        zstr.push('d"d"');
23641        dstr.push(dd + (dstyle == 2 ? " day" + (dd == 1 ? "" : "s") : dstyle == 1 ? "d" : ""));
23642      }
23643      if (dmin <= 4 && dmax >= 4) {
23644        dd = d / 3600;
23645        if (dmax > 4) {
23646          dd |= 0;
23647          d -= 3600 * dd;
23648        }
23649        zstr.push((dmin >= 4 ? "[h]" : "h") + '"h"');
23650        dstr.push(dd + (dstyle == 2 ? " hour" + (dd == 1 ? "" : "s") : dstyle == 1 ? "h" : ""));
23651      }
23652      if (dmin <= 8 && dmax >= 8) {
23653        dd = d / 60;
23654        if (dmax > 8) {
23655          dd |= 0;
23656          d -= 60 * dd;
23657        }
23658        zstr.push((dmin >= 8 ? "[m]" : "m") + '"m"');
23659        if (dstyle == 0)
23660          dstr.push((dmin == 8 && dmax == 8 || dd >= 10 ? "" : "0") + dd);
23661        else
23662          dstr.push(dd + (dstyle == 2 ? " minute" + (dd == 1 ? "" : "s") : dstyle == 1 ? "m" : ""));
23663      }
23664      if (dmin <= 16 && dmax >= 16) {
23665        dd = d;
23666        if (dmax > 16) {
23667          dd |= 0;
23668          d -= dd;
23669        }
23670        zstr.push((dmin >= 16 ? "[s]" : "s") + '"s"');
23671        if (dstyle == 0)
23672          dstr.push((dmax == 16 && dmin == 16 || dd >= 10 ? "" : "0") + dd);
23673        else
23674          dstr.push(dd + (dstyle == 2 ? " second" + (dd == 1 ? "" : "s") : dstyle == 1 ? "s" : ""));
23675      }
23676      if (dmax >= 32) {
23677        dd = Math.round(1e3 * d);
23678        if (dmin < 32)
23679          zstr.push('.000"ms"');
23680        if (dstyle == 0)
23681          dstr.push((dd >= 100 ? "" : dd >= 10 ? "0" : "00") + dd);
23682        else
23683          dstr.push(dd + (dstyle == 2 ? " millisecond" + (dd == 1 ? "" : "s") : dstyle == 1 ? "ms" : ""));
23684      }
23685      cell.w = dstr.join(dstyle == 0 ? ":" : " ");
23686      cell.z = zstr.join(dstyle == 0 ? '":"' : " ");
23687      if (dstyle == 0)
23688        cell.w = cell.w.replace(/:(\d\d\d)$/, ".$1");
23689    }
23690}
23691function parse_old_storage(buf, lut, v) {
23692  var dv = u8_to_dataview(buf);
23693  var flags = dv.getUint32(4, true);
23694  var ridx = -1, sidx = -1, zidx = -1, ieee = NaN, dt = new Date(2001, 0, 1);
23695  var doff = v > 1 ? 12 : 8;
23696  if (flags & 2) {
23697    zidx = dv.getUint32(doff, true);
23698    doff += 4;
23699  }
23700  doff += popcnt(flags & (v > 1 ? 3468 : 396)) * 4;
23701  if (flags & 512) {
23702    ridx = dv.getUint32(doff, true);
23703    doff += 4;
23704  }
23705  doff += popcnt(flags & (v > 1 ? 12288 : 4096)) * 4;
23706  if (flags & 16) {
23707    sidx = dv.getUint32(doff, true);
23708    doff += 4;
23709  }
23710  if (flags & 32) {
23711    ieee = dv.getFloat64(doff, true);
23712    doff += 8;
23713  }
23714  if (flags & 64) {
23715    dt.setTime(dt.getTime() + dv.getFloat64(doff, true) * 1e3);
23716    doff += 8;
23717  }
23718  if (v > 1) {
23719    flags = dv.getUint32(8, true) >>> 16;
23720    if (flags & 255) {
23721      if (zidx == -1)
23722        zidx = dv.getUint32(doff, true);
23723      doff += 4;
23724    }
23725  }
23726  var ret;
23727  var t = buf[v >= 4 ? 1 : 2];
23728  switch (t) {
23729    case 0:
23730      return void 0;
23731    case 2:
23732      ret = { t: "n", v: ieee };
23733      break;
23734    case 3:
23735      ret = { t: "s", v: lut.sst[sidx] };
23736      break;
23737    case 5:
23738      ret = { t: "d", v: dt };
23739      break;
23740    case 6:
23741      ret = { t: "b", v: ieee > 0 };
23742      break;
23743    case 7:
23744      ret = { t: "n", v: ieee };
23745      break;
23746    case 8:
23747      ret = { t: "e", v: 0 };
23748      break;
23749    case 9:
23750      {
23751        if (ridx > -1)
23752          ret = { t: "s", v: lut.rsst[ridx] };
23753        else
23754          throw new Error("Unsupported cell type ".concat(buf[subarray](0, 4)));
23755      }
23756      break;
23757    default:
23758      throw new Error("Unsupported cell type ".concat(buf[subarray](0, 4)));
23759  }
23760  if (zidx > -1)
23761    numbers_format_cell(ret, t | v << 8, flags, lut.ofmt[zidx], lut.nfmt[zidx]);
23762  if (t == 7)
23763    ret.v /= 86400;
23764  return ret;
23765}
23766function parse_new_storage(buf, lut) {
23767  var dv = u8_to_dataview(buf);
23768  var flags = dv.getUint32(4, true);
23769  var fields = dv.getUint32(8, true);
23770  var doff = 12;
23771  var ridx = -1, sidx = -1, zidx = -1, d128 = NaN, ieee = NaN, dt = new Date(2001, 0, 1);
23772  if (fields & 1) {
23773    d128 = readDecimal128LE(buf, doff);
23774    doff += 16;
23775  }
23776  if (fields & 2) {
23777    ieee = dv.getFloat64(doff, true);
23778    doff += 8;
23779  }
23780  if (fields & 4) {
23781    dt.setTime(dt.getTime() + dv.getFloat64(doff, true) * 1e3);
23782    doff += 8;
23783  }
23784  if (fields & 8) {
23785    sidx = dv.getUint32(doff, true);
23786    doff += 4;
23787  }
23788  if (fields & 16) {
23789    ridx = dv.getUint32(doff, true);
23790    doff += 4;
23791  }
23792  var ret;
23793  var t = buf[1];
23794  switch (t) {
23795    case 0:
23796      return void 0;
23797    case 2:
23798      ret = { t: "n", v: d128 };
23799      break;
23800    case 3:
23801      ret = { t: "s", v: lut.sst[sidx] };
23802      break;
23803    case 5:
23804      ret = { t: "d", v: dt };
23805      break;
23806    case 6:
23807      ret = { t: "b", v: ieee > 0 };
23808      break;
23809    case 7:
23810      ret = { t: "n", v: ieee };
23811      break;
23812    case 8:
23813      ret = { t: "e", v: 0 };
23814      break;
23815    case 9:
23816      ret = { t: "s", v: lut.rsst[ridx] };
23817      break;
23818    case 10:
23819      ret = { t: "n", v: d128 };
23820      break;
23821    default:
23822      throw new Error("Unsupported cell type ".concat(buf[1], " : ").concat(fields & 31, " : ").concat(buf[subarray](0, 4)));
23823  }
23824  doff += popcnt(fields & 8160) * 4;
23825  if (fields & 516096) {
23826    if (zidx == -1)
23827      zidx = dv.getUint32(doff, true);
23828    doff += 4;
23829  }
23830  if (zidx > -1)
23831    numbers_format_cell(ret, t | 5 << 8, fields >> 13, lut.ofmt[zidx], lut.nfmt[zidx]);
23832  if (t == 7)
23833    ret.v /= 86400;
23834  return ret;
23835}
23836function write_new_storage(cell, sst) {
23837  var out = new Uint8Array(32), dv = u8_to_dataview(out), l = 12, flags = 0;
23838  out[0] = 5;
23839  switch (cell.t) {
23840    case "n":
23841      out[1] = 2;
23842      writeDecimal128LE(out, l, cell.v);
23843      flags |= 1;
23844      l += 16;
23845      break;
23846    case "b":
23847      out[1] = 6;
23848      dv.setFloat64(l, cell.v ? 1 : 0, true);
23849      flags |= 2;
23850      l += 8;
23851      break;
23852    case "s":
23853      var s = cell.v == null ? "" : String(cell.v);
23854      var isst = sst.indexOf(s);
23855      if (isst == -1)
23856        sst[isst = sst.length] = s;
23857      out[1] = 3;
23858      dv.setUint32(l, isst, true);
23859      flags |= 8;
23860      l += 4;
23861      break;
23862    default:
23863      throw "unsupported cell type " + cell.t;
23864  }
23865  dv.setUint32(8, flags, true);
23866  return out[subarray](0, l);
23867}
23868function write_old_storage(cell, sst) {
23869  var out = new Uint8Array(32), dv = u8_to_dataview(out), l = 12, flags = 0;
23870  out[0] = 4;
23871  switch (cell.t) {
23872    case "n":
23873      out[2] = 2;
23874      dv.setFloat64(l, cell.v, true);
23875      flags |= 32;
23876      l += 8;
23877      break;
23878    case "b":
23879      out[2] = 6;
23880      dv.setFloat64(l, cell.v ? 1 : 0, true);
23881      flags |= 32;
23882      l += 8;
23883      break;
23884    case "s":
23885      var s = cell.v == null ? "" : String(cell.v);
23886      var isst = sst.indexOf(s);
23887      if (isst == -1)
23888        sst[isst = sst.length] = s;
23889      out[2] = 3;
23890      dv.setUint32(l, isst, true);
23891      flags |= 16;
23892      l += 4;
23893      break;
23894    default:
23895      throw "unsupported cell type " + cell.t;
23896  }
23897  dv.setUint32(8, flags, true);
23898  return out[subarray](0, l);
23899}
23900function parse_cell_storage(buf, lut) {
23901  switch (buf[0]) {
23902    case 0:
23903    case 1:
23904    case 2:
23905    case 3:
23906    case 4:
23907      return parse_old_storage(buf, lut, buf[0]);
23908    case 5:
23909      return parse_new_storage(buf, lut);
23910    default:
23911      throw new Error("Unsupported payload version ".concat(buf[0]));
23912  }
23913}
23914function parse_TSP_Reference(buf) {
23915  var pb = parse_shallow(buf);
23916  return parse_varint49(pb[1][0].data);
23917}
23918function write_TSP_Reference(idx) {
23919  return write_shallow([
23920    [],
23921    [{ type: 0, data: write_varint49(idx) }]
23922  ]);
23923}
23924function numbers_add_oref(iwa, ref) {
23925  var _a;
23926  var orefs = ((_a = iwa.messages[0].meta[5]) == null ? void 0 : _a[0]) ? parse_packed_varints(iwa.messages[0].meta[5][0].data) : [];
23927  var orefidx = orefs.indexOf(ref);
23928  if (orefidx == -1) {
23929    orefs.push(ref);
23930    iwa.messages[0].meta[5] = [{ type: 2, data: write_packed_varints(orefs) }];
23931  }
23932}
23933function numbers_del_oref(iwa, ref) {
23934  var _a;
23935  var orefs = ((_a = iwa.messages[0].meta[5]) == null ? void 0 : _a[0]) ? parse_packed_varints(iwa.messages[0].meta[5][0].data) : [];
23936  iwa.messages[0].meta[5] = [{ type: 2, data: write_packed_varints(orefs.filter(function(r) {
23937    return r != ref;
23938  })) }];
23939}
23940function parse_TST_TableDataList(M, root) {
23941  var pb = parse_shallow(root.data);
23942  var type = varint_to_i32(pb[1][0].data);
23943  var entries = pb[3];
23944  var data = [];
23945  (entries || []).forEach(function(entry) {
23946    var le = parse_shallow(entry.data);
23947    if (!le[1])
23948      return;
23949    var key = varint_to_i32(le[1][0].data) >>> 0;
23950    switch (type) {
23951      case 1:
23952        data[key] = u8str(le[3][0].data);
23953        break;
23954      case 8:
23955        {
23956          var rt = M[parse_TSP_Reference(le[9][0].data)][0];
23957          var rtp = parse_shallow(rt.data);
23958          var rtpref = M[parse_TSP_Reference(rtp[1][0].data)][0];
23959          var mtype = varint_to_i32(rtpref.meta[1][0].data);
23960          if (mtype != 2001)
23961            throw new Error("2000 unexpected reference to ".concat(mtype));
23962          var tswpsa = parse_shallow(rtpref.data);
23963          data[key] = tswpsa[3].map(function(x) {
23964            return u8str(x.data);
23965          }).join("");
23966        }
23967        break;
23968      case 2:
23969        data[key] = parse_shallow(le[6][0].data);
23970        break;
23971      default:
23972        throw type;
23973    }
23974  });
23975  return data;
23976}
23977function parse_TST_TileRowInfo(u8, type) {
23978  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
23979  var pb = parse_shallow(u8);
23980  var R = varint_to_i32(pb[1][0].data) >>> 0;
23981  var cnt = varint_to_i32(pb[2][0].data) >>> 0;
23982  var wide_offsets = ((_b = (_a = pb[8]) == null ? void 0 : _a[0]) == null ? void 0 : _b.data) && varint_to_i32(pb[8][0].data) > 0 || false;
23983  var used_storage_u8, used_storage;
23984  if (((_d = (_c = pb[7]) == null ? void 0 : _c[0]) == null ? void 0 : _d.data) && type != 0) {
23985    used_storage_u8 = (_f = (_e = pb[7]) == null ? void 0 : _e[0]) == null ? void 0 : _f.data;
23986    used_storage = (_h = (_g = pb[6]) == null ? void 0 : _g[0]) == null ? void 0 : _h.data;
23987  } else if (((_j = (_i = pb[4]) == null ? void 0 : _i[0]) == null ? void 0 : _j.data) && type != 1) {
23988    used_storage_u8 = (_l = (_k = pb[4]) == null ? void 0 : _k[0]) == null ? void 0 : _l.data;
23989    used_storage = (_n = (_m = pb[3]) == null ? void 0 : _m[0]) == null ? void 0 : _n.data;
23990  } else
23991    throw "NUMBERS Tile missing ".concat(type, " cell storage");
23992  var width = wide_offsets ? 4 : 1;
23993  var used_storage_offsets = u8_to_dataview(used_storage_u8);
23994  var offsets = [];
23995  for (var C = 0; C < used_storage_u8.length / 2; ++C) {
23996    var off = used_storage_offsets.getUint16(C * 2, true);
23997    if (off < 65535)
23998      offsets.push([C, off]);
23999  }
24000  if (offsets.length != cnt)
24001    throw "Expected ".concat(cnt, " cells, found ").concat(offsets.length);
24002  var cells = [];
24003  for (C = 0; C < offsets.length - 1; ++C)
24004    cells[offsets[C][0]] = used_storage[subarray](offsets[C][1] * width, offsets[C + 1][1] * width);
24005  if (offsets.length >= 1)
24006    cells[offsets[offsets.length - 1][0]] = used_storage[subarray](offsets[offsets.length - 1][1] * width);
24007  return { R: R, cells: cells };
24008}
24009function parse_TST_Tile(M, root) {
24010  var _a;
24011  var pb = parse_shallow(root.data);
24012  var storage = -1;
24013  if ((_a = pb == null ? void 0 : pb[7]) == null ? void 0 : _a[0]) {
24014    if (varint_to_i32(pb[7][0].data) >>> 0)
24015      storage = 1;
24016    else
24017      storage = 0;
24018  }
24019  var ri = mappa(pb[5], function(u8) {
24020    return parse_TST_TileRowInfo(u8, storage);
24021  });
24022  return {
24023    nrows: varint_to_i32(pb[4][0].data) >>> 0,
24024    data: ri.reduce(function(acc, x) {
24025      if (!acc[x.R])
24026        acc[x.R] = [];
24027      x.cells.forEach(function(cell, C) {
24028        if (acc[x.R][C])
24029          throw new Error("Duplicate cell r=".concat(x.R, " c=").concat(C));
24030        acc[x.R][C] = cell;
24031      });
24032      return acc;
24033    }, [])
24034  };
24035}
24036function parse_TST_TableModelArchive(M, root, ws) {
24037  var _a, _b, _c, _d, _e, _f;
24038  var pb = parse_shallow(root.data);
24039  var range = { s: { r: 0, c: 0 }, e: { r: 0, c: 0 } };
24040  range.e.r = (varint_to_i32(pb[6][0].data) >>> 0) - 1;
24041  if (range.e.r < 0)
24042    throw new Error("Invalid row varint ".concat(pb[6][0].data));
24043  range.e.c = (varint_to_i32(pb[7][0].data) >>> 0) - 1;
24044  if (range.e.c < 0)
24045    throw new Error("Invalid col varint ".concat(pb[7][0].data));
24046  ws["!ref"] = encode_range(range);
24047  var dense = Array.isArray(ws);
24048  var store = parse_shallow(pb[4][0].data);
24049  var lut = numbers_lut_new();
24050  if ((_a = store[4]) == null ? void 0 : _a[0])
24051    lut.sst = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[4][0].data)][0]);
24052  if ((_b = store[11]) == null ? void 0 : _b[0])
24053    lut.ofmt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[11][0].data)][0]);
24054  if ((_c = store[17]) == null ? void 0 : _c[0])
24055    lut.rsst = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[17][0].data)][0]);
24056  if ((_d = store[22]) == null ? void 0 : _d[0])
24057    lut.nfmt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[22][0].data)][0]);
24058  var tile = parse_shallow(store[3][0].data);
24059  var _R = 0;
24060  tile[1].forEach(function(t) {
24061    var tl = parse_shallow(t.data);
24062    var ref2 = M[parse_TSP_Reference(tl[2][0].data)][0];
24063    var mtype2 = varint_to_i32(ref2.meta[1][0].data);
24064    if (mtype2 != 6002)
24065      throw new Error("6001 unexpected reference to ".concat(mtype2));
24066    var _tile = parse_TST_Tile(M, ref2);
24067    _tile.data.forEach(function(row, R) {
24068      row.forEach(function(buf, C) {
24069        var res = parse_cell_storage(buf, lut);
24070        if (res) {
24071          if (dense) {
24072            if (!ws[_R + R])
24073              ws[_R + R] = [];
24074            ws[_R + R][C] = res;
24075          } else {
24076            var addr = encode_cell({ r: _R + R, c: C });
24077            ws[addr] = res;
24078          }
24079        }
24080      });
24081    });
24082    _R += _tile.nrows;
24083  });
24084  if ((_e = store[13]) == null ? void 0 : _e[0]) {
24085    var ref = M[parse_TSP_Reference(store[13][0].data)][0];
24086    var mtype = varint_to_i32(ref.meta[1][0].data);
24087    if (mtype != 6144)
24088      throw new Error("Expected merge type 6144, found ".concat(mtype));
24089    ws["!merges"] = (_f = parse_shallow(ref.data)) == null ? void 0 : _f[1].map(function(pi) {
24090      var merge = parse_shallow(pi.data);
24091      var origin = u8_to_dataview(parse_shallow(merge[1][0].data)[1][0].data), size = u8_to_dataview(parse_shallow(merge[2][0].data)[1][0].data);
24092      return {
24093        s: { r: origin.getUint16(0, true), c: origin.getUint16(2, true) },
24094        e: {
24095          r: origin.getUint16(0, true) + size.getUint16(0, true) - 1,
24096          c: origin.getUint16(2, true) + size.getUint16(2, true) - 1
24097        }
24098      };
24099    });
24100  }
24101}
24102function parse_TST_TableInfoArchive(M, root, opts) {
24103  var pb = parse_shallow(root.data);
24104  var out;
24105  if (!(opts == null ? void 0 : opts.dense))
24106    out = { "!ref": "A1" };
24107  else
24108    out = [];
24109  out["!ref"] = "A1";
24110  var tableref = M[parse_TSP_Reference(pb[2][0].data)];
24111  var mtype = varint_to_i32(tableref[0].meta[1][0].data);
24112  if (mtype != 6001)
24113    throw new Error("6000 unexpected reference to ".concat(mtype));
24114  parse_TST_TableModelArchive(M, tableref[0], out);
24115  return out;
24116}
24117function parse_TN_SheetArchive(M, root, opts) {
24118  var _a;
24119  var pb = parse_shallow(root.data);
24120  var out = {
24121    name: ((_a = pb[1]) == null ? void 0 : _a[0]) ? u8str(pb[1][0].data) : "",
24122    sheets: []
24123  };
24124  var shapeoffs = mappa(pb[2], parse_TSP_Reference);
24125  shapeoffs.forEach(function(off) {
24126    M[off].forEach(function(m) {
24127      var mtype = varint_to_i32(m.meta[1][0].data);
24128      if (mtype == 6e3)
24129        out.sheets.push(parse_TST_TableInfoArchive(M, m, opts));
24130    });
24131  });
24132  return out;
24133}
24134function parse_TN_DocumentArchive(M, root, opts) {
24135  var _a;
24136  var out = book_new();
24137  var pb = parse_shallow(root.data);
24138  if ((_a = pb[2]) == null ? void 0 : _a[0])
24139    throw new Error("Keynote presentations are not supported");
24140  var sheetoffs = mappa(pb[1], parse_TSP_Reference);
24141  sheetoffs.forEach(function(off) {
24142    M[off].forEach(function(m) {
24143      var mtype = varint_to_i32(m.meta[1][0].data);
24144      if (mtype == 2) {
24145        var root2 = parse_TN_SheetArchive(M, m, opts);
24146        root2.sheets.forEach(function(sheet, idx) {
24147          book_append_sheet(out, sheet, idx == 0 ? root2.name : root2.name + "_" + idx, true);
24148        });
24149      }
24150    });
24151  });
24152  if (out.SheetNames.length == 0)
24153    throw new Error("Empty NUMBERS file");
24154  out.bookType = "numbers";
24155  return out;
24156}
24157function parse_numbers_iwa(cfb, opts) {
24158  var _a, _b, _c, _d, _e, _f, _g;
24159  var M = {}, indices = [];
24160  cfb.FullPaths.forEach(function(p) {
24161    if (p.match(/\.iwpv2/))
24162      throw new Error("Unsupported password protection");
24163  });
24164  cfb.FileIndex.forEach(function(s) {
24165    if (!s.name.match(/\.iwa$/))
24166      return;
24167    if (s.content[0] == 98)
24168      return;
24169    var o;
24170    try {
24171      o = decompress_iwa_file(s.content);
24172    } catch (e) {
24173      return console.log("?? " + s.content.length + " " + (e.message || e));
24174    }
24175    var packets;
24176    try {
24177      packets = parse_iwa_file(o);
24178    } catch (e) {
24179      return console.log("## " + (e.message || e));
24180    }
24181    packets.forEach(function(packet) {
24182      M[packet.id] = packet.messages;
24183      indices.push(packet.id);
24184    });
24185  });
24186  if (!indices.length)
24187    throw new Error("File has no messages");
24188  if (((_c = (_b = (_a = M == null ? void 0 : M[1]) == null ? void 0 : _a[0].meta) == null ? void 0 : _b[1]) == null ? void 0 : _c[0].data) && varint_to_i32(M[1][0].meta[1][0].data) == 1e4)
24189    throw new Error("Pages documents are not supported");
24190  var docroot = ((_g = (_f = (_e = (_d = M == null ? void 0 : M[1]) == null ? void 0 : _d[0]) == null ? void 0 : _e.meta) == null ? void 0 : _f[1]) == null ? void 0 : _g[0].data) && varint_to_i32(M[1][0].meta[1][0].data) == 1 && M[1][0];
24191  if (!docroot)
24192    indices.forEach(function(idx) {
24193      M[idx].forEach(function(iwam) {
24194        var mtype = varint_to_i32(iwam.meta[1][0].data) >>> 0;
24195        if (mtype == 1) {
24196          if (!docroot)
24197            docroot = iwam;
24198          else
24199            throw new Error("Document has multiple roots");
24200        }
24201      });
24202    });
24203  if (!docroot)
24204    throw new Error("Cannot find Document root");
24205  return parse_TN_DocumentArchive(M, docroot, opts);
24206}
24207function write_TST_TileRowInfo(data, SST, wide) {
24208  var _a, _b;
24209  var tri = [
24210    [],
24211    [{ type: 0, data: write_varint49(0) }],
24212    [{ type: 0, data: write_varint49(0) }],
24213    [{ type: 2, data: new Uint8Array([]) }],
24214    [{ type: 2, data: new Uint8Array(Array.from({ length: 510 }, function() {
24215      return 255;
24216    })) }],
24217    [{ type: 0, data: write_varint49(5) }],
24218    [{ type: 2, data: new Uint8Array([]) }],
24219    [{ type: 2, data: new Uint8Array(Array.from({ length: 510 }, function() {
24220      return 255;
24221    })) }],
24222    [{ type: 0, data: write_varint49(1) }]
24223  ];
24224  if (!((_a = tri[6]) == null ? void 0 : _a[0]) || !((_b = tri[7]) == null ? void 0 : _b[0]))
24225    throw "Mutation only works on post-BNC storages!";
24226  var cnt = 0;
24227  if (tri[7][0].data.length < 2 * data.length) {
24228    var new_7 = new Uint8Array(2 * data.length);
24229    new_7.set(tri[7][0].data);
24230    tri[7][0].data = new_7;
24231  }
24232  if (tri[4][0].data.length < 2 * data.length) {
24233    var new_4 = new Uint8Array(2 * data.length);
24234    new_4.set(tri[4][0].data);
24235    tri[4][0].data = new_4;
24236  }
24237  var dv = u8_to_dataview(tri[7][0].data), last_offset = 0, cell_storage = [];
24238  var _dv = u8_to_dataview(tri[4][0].data), _last_offset = 0, _cell_storage = [];
24239  var width = wide ? 4 : 1;
24240  for (var C = 0; C < data.length; ++C) {
24241    if (data[C] == null) {
24242      dv.setUint16(C * 2, 65535, true);
24243      _dv.setUint16(C * 2, 65535);
24244      continue;
24245    }
24246    dv.setUint16(C * 2, last_offset / width, true);
24247    _dv.setUint16(C * 2, _last_offset / width, true);
24248    var celload, _celload;
24249    switch (typeof data[C]) {
24250      case "string":
24251        celload = write_new_storage({ t: "s", v: data[C] }, SST);
24252        _celload = write_old_storage({ t: "s", v: data[C] }, SST);
24253        break;
24254      case "number":
24255        celload = write_new_storage({ t: "n", v: data[C] }, SST);
24256        _celload = write_old_storage({ t: "n", v: data[C] }, SST);
24257        break;
24258      case "boolean":
24259        celload = write_new_storage({ t: "b", v: data[C] }, SST);
24260        _celload = write_old_storage({ t: "b", v: data[C] }, SST);
24261        break;
24262      default:
24263        if (data[C] instanceof Date) {
24264          celload = write_new_storage({ t: "s", v: data[C].toISOString() }, SST);
24265          _celload = write_old_storage({ t: "s", v: data[C].toISOString() }, SST);
24266          break;
24267        }
24268        throw new Error("Unsupported value " + data[C]);
24269    }
24270    cell_storage.push(celload);
24271    last_offset += celload.length;
24272    {
24273      _cell_storage.push(_celload);
24274      _last_offset += _celload.length;
24275    }
24276    ++cnt;
24277  }
24278  tri[2][0].data = write_varint49(cnt);
24279  tri[5][0].data = write_varint49(5);
24280  for (; C < tri[7][0].data.length / 2; ++C) {
24281    dv.setUint16(C * 2, 65535, true);
24282    _dv.setUint16(C * 2, 65535, true);
24283  }
24284  tri[6][0].data = u8concat(cell_storage);
24285  tri[3][0].data = u8concat(_cell_storage);
24286  tri[8] = [{ type: 0, data: write_varint49(wide ? 1 : 0) }];
24287  return tri;
24288}
24289function write_iwam(type, payload) {
24290  return {
24291    meta: [
24292      [],
24293      [{ type: 0, data: write_varint49(type) }]
24294    ],
24295    data: payload
24296  };
24297}
24298function get_unique_msgid(dep, dependents) {
24299  if (!dependents.last)
24300    dependents.last = 927262;
24301  for (var i = dependents.last; i < 2e6; ++i)
24302    if (!dependents[i]) {
24303      dependents[dependents.last = i] = dep;
24304      return i;
24305    }
24306  throw new Error("Too many messages");
24307}
24308function build_numbers_deps(cfb) {
24309  var dependents = {};
24310  var indices = [];
24311  cfb.FileIndex.map(function(fi, idx) {
24312    return [fi, cfb.FullPaths[idx]];
24313  }).forEach(function(row) {
24314    var fi = row[0], fp = row[1];
24315    if (fi.type != 2)
24316      return;
24317    if (!fi.name.match(/\.iwa/))
24318      return;
24319    if (fi.name.match(/OperationStorage/))
24320      return;
24321    parse_iwa_file(decompress_iwa_file(fi.content)).forEach(function(packet) {
24322      indices.push(packet.id);
24323      dependents[packet.id] = { deps: [], location: fp, type: varint_to_i32(packet.messages[0].meta[1][0].data) };
24324    });
24325  });
24326  indices.sort(function(x, y) {
24327    return x - y;
24328  });
24329  var indices_varint = indices.filter(function(x) {
24330    return x > 1;
24331  }).map(function(x) {
24332    return [x, write_varint49(x)];
24333  });
24334  cfb.FileIndex.forEach(function(fi) {
24335    if (!fi.name.match(/\.iwa/))
24336      return;
24337    if (fi.name.match(/OperationStorage/))
24338      return;
24339    parse_iwa_file(decompress_iwa_file(fi.content)).forEach(function(ia) {
24340      indices_varint.forEach(function(ivi) {
24341        if (ia.messages.some(function(mess) {
24342          return varint_to_i32(mess.meta[1][0].data) != 11006 && u8contains(mess.data, ivi[1]);
24343        })) {
24344          dependents[ivi[0]].deps.push(ia.id);
24345        }
24346      });
24347    });
24348  });
24349  return dependents;
24350}
24351function write_numbers_iwa(wb, opts) {
24352  if (!opts || !opts.numbers)
24353    throw new Error("Must pass a `numbers` option -- check the README");
24354  var cfb = CFB.read(opts.numbers, { type: "base64" });
24355  var deps = build_numbers_deps(cfb);
24356  var docroot = numbers_iwa_find(cfb, deps, 1);
24357  if (docroot == null)
24358    throw "Could not find message ".concat(1, " in Numbers template");
24359  var sheetrefs = mappa(parse_shallow(docroot.messages[0].data)[1], parse_TSP_Reference);
24360  if (sheetrefs.length > 1)
24361    throw new Error("Template NUMBERS file must have exactly one sheet");
24362  wb.SheetNames.forEach(function(name, idx) {
24363    if (idx >= 1) {
24364      numbers_add_ws(cfb, deps, idx + 1);
24365      docroot = numbers_iwa_find(cfb, deps, 1);
24366      sheetrefs = mappa(parse_shallow(docroot.messages[0].data)[1], parse_TSP_Reference);
24367    }
24368    write_numbers_ws(cfb, deps, wb.Sheets[name], name, idx, sheetrefs[idx]);
24369  });
24370  return cfb;
24371}
24372function numbers_iwa_doit(cfb, deps, id, cb) {
24373  var entry = CFB.find(cfb, deps[id].location);
24374  if (!entry)
24375    throw "Could not find ".concat(deps[id].location, " in Numbers template");
24376  var x = parse_iwa_file(decompress_iwa_file(entry.content));
24377  var ainfo = x.find(function(packet) {
24378    return packet.id == id;
24379  });
24380  cb(ainfo, x);
24381  entry.content = compress_iwa_file(write_iwa_file(x));
24382  entry.size = entry.content.length;
24383}
24384function numbers_iwa_find(cfb, deps, id) {
24385  var entry = CFB.find(cfb, deps[id].location);
24386  if (!entry)
24387    throw "Could not find ".concat(deps[id].location, " in Numbers template");
24388  var x = parse_iwa_file(decompress_iwa_file(entry.content));
24389  var ainfo = x.find(function(packet) {
24390    return packet.id == id;
24391  });
24392  return ainfo;
24393}
24394function numbers_add_ws(cfb, deps, wsidx) {
24395  var sheetref = -1, newsheetref = -1;
24396  var remap = {};
24397  numbers_iwa_doit(cfb, deps, 1, function(docroot, arch) {
24398    var doc = parse_shallow(docroot.messages[0].data);
24399    sheetref = parse_TSP_Reference(parse_shallow(docroot.messages[0].data)[1][0].data);
24400    newsheetref = get_unique_msgid({ deps: [1], location: deps[sheetref].location, type: 2 }, deps);
24401    remap[sheetref] = newsheetref;
24402    numbers_add_oref(docroot, newsheetref);
24403    doc[1].push({ type: 2, data: write_TSP_Reference(newsheetref) });
24404    var sheet = numbers_iwa_find(cfb, deps, sheetref);
24405    sheet.id = newsheetref;
24406    if (deps[1].location == deps[newsheetref].location)
24407      arch.push(sheet);
24408    else
24409      numbers_iwa_doit(cfb, deps, newsheetref, function(_, x) {
24410        return x.push(sheet);
24411      });
24412    docroot.messages[0].data = write_shallow(doc);
24413  });
24414  var tiaref = -1;
24415  numbers_iwa_doit(cfb, deps, newsheetref, function(sheetroot, arch) {
24416    var sa = parse_shallow(sheetroot.messages[0].data);
24417    for (var i = 3; i <= 69; ++i)
24418      delete sa[i];
24419    var drawables = mappa(sa[2], parse_TSP_Reference);
24420    drawables.forEach(function(n) {
24421      return numbers_del_oref(sheetroot, n);
24422    });
24423    tiaref = get_unique_msgid({ deps: [newsheetref], location: deps[drawables[0]].location, type: deps[drawables[0]].type }, deps);
24424    numbers_add_oref(sheetroot, tiaref);
24425    remap[drawables[0]] = tiaref;
24426    sa[2] = [{ type: 2, data: write_TSP_Reference(tiaref) }];
24427    var tia = numbers_iwa_find(cfb, deps, drawables[0]);
24428    tia.id = tiaref;
24429    if (deps[drawables[0]].location == deps[newsheetref].location)
24430      arch.push(tia);
24431    else {
24432      var loc2 = deps[newsheetref].location;
24433      loc2 = loc2.replace(/^Root Entry\//, "");
24434      loc2 = loc2.replace(/^Index\//, "").replace(/\.iwa$/, "");
24435      numbers_iwa_doit(cfb, deps, 2, function(ai) {
24436        var mlist = parse_shallow(ai.messages[0].data);
24437        var parentidx = mlist[3].findIndex(function(m) {
24438          var _a, _b;
24439          var mm = parse_shallow(m.data);
24440          if ((_a = mm[3]) == null ? void 0 : _a[0])
24441            return u8str(mm[3][0].data) == loc2;
24442          if (((_b = mm[2]) == null ? void 0 : _b[0]) && u8str(mm[2][0].data) == loc2)
24443            return true;
24444          return false;
24445        });
24446        var parent = parse_shallow(mlist[3][parentidx].data);
24447        if (!parent[6])
24448          parent[6] = [];
24449        parent[6].push({
24450          type: 2,
24451          data: write_shallow([
24452            [],
24453            [{ type: 0, data: write_varint49(tiaref) }]
24454          ])
24455        });
24456        mlist[3][parentidx].data = write_shallow(parent);
24457        ai.messages[0].data = write_shallow(mlist);
24458      });
24459      numbers_iwa_doit(cfb, deps, tiaref, function(_, x) {
24460        return x.push(tia);
24461      });
24462    }
24463    sheetroot.messages[0].data = write_shallow(sa);
24464  });
24465  var tmaref = -1;
24466  numbers_iwa_doit(cfb, deps, tiaref, function(tiaroot, arch) {
24467    var tia = parse_shallow(tiaroot.messages[0].data);
24468    var da = parse_shallow(tia[1][0].data);
24469    for (var i = 3; i <= 69; ++i)
24470      delete da[i];
24471    var dap = parse_TSP_Reference(da[2][0].data);
24472    da[2][0].data = write_TSP_Reference(remap[dap]);
24473    tia[1][0].data = write_shallow(da);
24474    var oldtmaref = parse_TSP_Reference(tia[2][0].data);
24475    numbers_del_oref(tiaroot, oldtmaref);
24476    tmaref = get_unique_msgid({ deps: [tiaref], location: deps[oldtmaref].location, type: deps[oldtmaref].type }, deps);
24477    numbers_add_oref(tiaroot, tmaref);
24478    remap[oldtmaref] = tmaref;
24479    tia[2][0].data = write_TSP_Reference(tmaref);
24480    var tma = numbers_iwa_find(cfb, deps, oldtmaref);
24481    tma.id = tmaref;
24482    if (deps[tiaref].location == deps[tmaref].location)
24483      arch.push(tma);
24484    else
24485      numbers_iwa_doit(cfb, deps, tmaref, function(_, x) {
24486        return x.push(tma);
24487      });
24488    tiaroot.messages[0].data = write_shallow(tia);
24489  });
24490  var loc = deps[tmaref].location;
24491  loc = loc.replace(/^Root Entry\//, "");
24492  loc = loc.replace(/^Index\//, "").replace(/\.iwa$/, "");
24493  numbers_iwa_doit(cfb, deps, tmaref, function(tmaroot, arch) {
24494    var _a, _b;
24495    var tma = parse_shallow(tmaroot.messages[0].data);
24496    var uuid = u8str(tma[1][0].data), new_uuid = uuid.replace(/-[A-Z0-9]*/, "-".concat(wsidx.toString(16).padStart(4, "0")));
24497    tma[1][0].data = stru8(new_uuid);
24498    [12, 13, 29, 31, 32, 33, 39, 44, 47, 81, 82, 84].forEach(function(n) {
24499      return delete tma[n];
24500    });
24501    if (tma[45]) {
24502      var srrta = parse_shallow(tma[45][0].data);
24503      var ref = parse_TSP_Reference(srrta[1][0].data);
24504      numbers_del_oref(tmaroot, ref);
24505      delete tma[45];
24506    }
24507    if (tma[70]) {
24508      var hsoa = parse_shallow(tma[70][0].data);
24509      (_a = hsoa[2]) == null ? void 0 : _a.forEach(function(item) {
24510        var hsa = parse_shallow(item.data);
24511        [2, 3].map(function(n) {
24512          return hsa[n][0];
24513        }).forEach(function(hseadata) {
24514          var hsea = parse_shallow(hseadata.data);
24515          if (!hsea[8])
24516            return;
24517          var ref2 = parse_TSP_Reference(hsea[8][0].data);
24518          numbers_del_oref(tmaroot, ref2);
24519        });
24520      });
24521      delete tma[70];
24522    }
24523    [
24524      46,
24525      30,
24526      34,
24527      35,
24528      36,
24529      38,
24530      48,
24531      49,
24532      60,
24533      61,
24534      62,
24535      63,
24536      64,
24537      71,
24538      72,
24539      73,
24540      74,
24541      75,
24542      85,
24543      86,
24544      87,
24545      88,
24546      89
24547    ].forEach(function(n) {
24548      if (!tma[n])
24549        return;
24550      var ref2 = parse_TSP_Reference(tma[n][0].data);
24551      delete tma[n];
24552      numbers_del_oref(tmaroot, ref2);
24553    });
24554    var store = parse_shallow(tma[4][0].data);
24555    {
24556      [2, 4, 5, 6, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22].forEach(function(n) {
24557        var _a2;
24558        if (!((_a2 = store[n]) == null ? void 0 : _a2[0]))
24559          return;
24560        var oldref = parse_TSP_Reference(store[n][0].data);
24561        var newref = get_unique_msgid({ deps: [tmaref], location: deps[oldref].location, type: deps[oldref].type }, deps);
24562        numbers_del_oref(tmaroot, oldref);
24563        numbers_add_oref(tmaroot, newref);
24564        remap[oldref] = newref;
24565        var msg = numbers_iwa_find(cfb, deps, oldref);
24566        msg.id = newref;
24567        if (deps[oldref].location == deps[tmaref].location)
24568          arch.push(msg);
24569        else {
24570          deps[newref].location = deps[oldref].location.replace(oldref.toString(), newref.toString());
24571          if (deps[newref].location == deps[oldref].location)
24572            deps[newref].location = deps[newref].location.replace(/\.iwa/, "-".concat(newref, ".iwa"));
24573          CFB.utils.cfb_add(cfb, deps[newref].location, compress_iwa_file(write_iwa_file([msg])));
24574          var newloc = deps[newref].location;
24575          newloc = newloc.replace(/^Root Entry\//, "");
24576          newloc = newloc.replace(/^Index\//, "").replace(/\.iwa$/, "");
24577          numbers_iwa_doit(cfb, deps, 2, function(ai) {
24578            var mlist = parse_shallow(ai.messages[0].data);
24579            mlist[3].push({ type: 2, data: write_shallow([
24580              [],
24581              [{ type: 0, data: write_varint49(newref) }],
24582              [{ type: 2, data: stru8(newloc.replace(/-.*$/, "")) }],
24583              [{ type: 2, data: stru8(newloc) }],
24584              [{ type: 2, data: new Uint8Array([2, 0, 0]) }],
24585              [{ type: 2, data: new Uint8Array([2, 0, 0]) }],
24586              [],
24587              [],
24588              [],
24589              [],
24590              [{ type: 0, data: write_varint49(0) }],
24591              [],
24592              [{ type: 0, data: write_varint49(0) }]
24593            ]) });
24594            mlist[1] = [{ type: 0, data: write_varint49(Math.max(newref + 1, parse_varint49(mlist[1][0].data))) }];
24595            var parentidx = mlist[3].findIndex(function(m) {
24596              var _a3, _b2;
24597              var mm = parse_shallow(m.data);
24598              if ((_a3 = mm[3]) == null ? void 0 : _a3[0])
24599                return u8str(mm[3][0].data) == loc;
24600              if (((_b2 = mm[2]) == null ? void 0 : _b2[0]) && u8str(mm[2][0].data) == loc)
24601                return true;
24602              return false;
24603            });
24604            var parent = parse_shallow(mlist[3][parentidx].data);
24605            if (!parent[6])
24606              parent[6] = [];
24607            parent[6].push({
24608              type: 2,
24609              data: write_shallow([
24610                [],
24611                [{ type: 0, data: write_varint49(newref) }]
24612              ])
24613            });
24614            mlist[3][parentidx].data = write_shallow(parent);
24615            ai.messages[0].data = write_shallow(mlist);
24616          });
24617        }
24618        store[n][0].data = write_TSP_Reference(newref);
24619      });
24620      var row_headers = parse_shallow(store[1][0].data);
24621      {
24622        (_b = row_headers[2]) == null ? void 0 : _b.forEach(function(tspref) {
24623          var oldref = parse_TSP_Reference(tspref.data);
24624          var newref = get_unique_msgid({ deps: [tmaref], location: deps[oldref].location, type: deps[oldref].type }, deps);
24625          numbers_del_oref(tmaroot, oldref);
24626          numbers_add_oref(tmaroot, newref);
24627          remap[oldref] = newref;
24628          var msg = numbers_iwa_find(cfb, deps, oldref);
24629          msg.id = newref;
24630          if (deps[oldref].location == deps[tmaref].location) {
24631            arch.push(msg);
24632          } else {
24633            deps[newref].location = deps[oldref].location.replace(oldref.toString(), newref.toString());
24634            if (deps[newref].location == deps[oldref].location)
24635              deps[newref].location = deps[newref].location.replace(/\.iwa/, "-".concat(newref, ".iwa"));
24636            CFB.utils.cfb_add(cfb, deps[newref].location, compress_iwa_file(write_iwa_file([msg])));
24637            var newloc = deps[newref].location;
24638            newloc = newloc.replace(/^Root Entry\//, "");
24639            newloc = newloc.replace(/^Index\//, "").replace(/\.iwa$/, "");
24640            numbers_iwa_doit(cfb, deps, 2, function(ai) {
24641              var mlist = parse_shallow(ai.messages[0].data);
24642              mlist[3].push({ type: 2, data: write_shallow([
24643                [],
24644                [{ type: 0, data: write_varint49(newref) }],
24645                [{ type: 2, data: stru8(newloc.replace(/-.*$/, "")) }],
24646                [{ type: 2, data: stru8(newloc) }],
24647                [{ type: 2, data: new Uint8Array([2, 0, 0]) }],
24648                [{ type: 2, data: new Uint8Array([2, 0, 0]) }],
24649                [],
24650                [],
24651                [],
24652                [],
24653                [{ type: 0, data: write_varint49(0) }],
24654                [],
24655                [{ type: 0, data: write_varint49(0) }]
24656              ]) });
24657              mlist[1] = [{ type: 0, data: write_varint49(Math.max(newref + 1, parse_varint49(mlist[1][0].data))) }];
24658              var parentidx = mlist[3].findIndex(function(m) {
24659                var _a2, _b2;
24660                var mm = parse_shallow(m.data);
24661                if ((_a2 = mm[3]) == null ? void 0 : _a2[0])
24662                  return u8str(mm[3][0].data) == loc;
24663                if (((_b2 = mm[2]) == null ? void 0 : _b2[0]) && u8str(mm[2][0].data) == loc)
24664                  return true;
24665                return false;
24666              });
24667              var parent = parse_shallow(mlist[3][parentidx].data);
24668              if (!parent[6])
24669                parent[6] = [];
24670              parent[6].push({
24671                type: 2,
24672                data: write_shallow([
24673                  [],
24674                  [{ type: 0, data: write_varint49(newref) }]
24675                ])
24676              });
24677              mlist[3][parentidx].data = write_shallow(parent);
24678              ai.messages[0].data = write_shallow(mlist);
24679            });
24680          }
24681          tspref.data = write_TSP_Reference(newref);
24682        });
24683      }
24684      store[1][0].data = write_shallow(row_headers);
24685      var tiles = parse_shallow(store[3][0].data);
24686      {
24687        tiles[1].forEach(function(t) {
24688          var tst = parse_shallow(t.data);
24689          var oldtileref = parse_TSP_Reference(tst[2][0].data);
24690          var newtileref = remap[oldtileref];
24691          if (!remap[oldtileref]) {
24692            newtileref = get_unique_msgid({ deps: [tmaref], location: "", type: deps[oldtileref].type }, deps);
24693            deps[newtileref].location = "Root Entry/Index/Tables/Tile-".concat(newtileref, ".iwa");
24694            remap[oldtileref] = newtileref;
24695            var oldtile = numbers_iwa_find(cfb, deps, oldtileref);
24696            oldtile.id = newtileref;
24697            numbers_del_oref(tmaroot, oldtileref);
24698            numbers_add_oref(tmaroot, newtileref);
24699            CFB.utils.cfb_add(cfb, "/Index/Tables/Tile-".concat(newtileref, ".iwa"), compress_iwa_file(write_iwa_file([oldtile])));
24700            numbers_iwa_doit(cfb, deps, 2, function(ai) {
24701              var mlist = parse_shallow(ai.messages[0].data);
24702              mlist[3].push({ type: 2, data: write_shallow([
24703                [],
24704                [{ type: 0, data: write_varint49(newtileref) }],
24705                [{ type: 2, data: stru8("Tables/Tile") }],
24706                [{ type: 2, data: stru8("Tables/Tile-".concat(newtileref)) }],
24707                [{ type: 2, data: new Uint8Array([2, 0, 0]) }],
24708                [{ type: 2, data: new Uint8Array([2, 0, 0]) }],
24709                [],
24710                [],
24711                [],
24712                [],
24713                [{ type: 0, data: write_varint49(0) }],
24714                [],
24715                [{ type: 0, data: write_varint49(0) }]
24716              ]) });
24717              mlist[1] = [{ type: 0, data: write_varint49(Math.max(newtileref + 1, parse_varint49(mlist[1][0].data))) }];
24718              var parentidx = mlist[3].findIndex(function(m) {
24719                var _a2, _b2;
24720                var mm = parse_shallow(m.data);
24721                if ((_a2 = mm[3]) == null ? void 0 : _a2[0])
24722                  return u8str(mm[3][0].data) == loc;
24723                if (((_b2 = mm[2]) == null ? void 0 : _b2[0]) && u8str(mm[2][0].data) == loc)
24724                  return true;
24725                return false;
24726              });
24727              var parent = parse_shallow(mlist[3][parentidx].data);
24728              if (!parent[6])
24729                parent[6] = [];
24730              parent[6].push({
24731                type: 2,
24732                data: write_shallow([
24733                  [],
24734                  [{ type: 0, data: write_varint49(newtileref) }]
24735                ])
24736              });
24737              mlist[3][parentidx].data = write_shallow(parent);
24738              ai.messages[0].data = write_shallow(mlist);
24739            });
24740          }
24741          tst[2][0].data = write_TSP_Reference(newtileref);
24742          t.data = write_shallow(tst);
24743        });
24744      }
24745      store[3][0].data = write_shallow(tiles);
24746    }
24747    tma[4][0].data = write_shallow(store);
24748    tmaroot.messages[0].data = write_shallow(tma);
24749  });
24750}
24751function write_numbers_ws(cfb, deps, ws, wsname, sheetidx, rootref) {
24752  var drawables = [];
24753  numbers_iwa_doit(cfb, deps, rootref, function(docroot) {
24754    var sheetref = parse_shallow(docroot.messages[0].data);
24755    {
24756      sheetref[1] = [{ type: 2, data: stru8(wsname) }];
24757      drawables = mappa(sheetref[2], parse_TSP_Reference);
24758    }
24759    docroot.messages[0].data = write_shallow(sheetref);
24760  });
24761  var tia = numbers_iwa_find(cfb, deps, drawables[0]);
24762  var tmaref = parse_TSP_Reference(parse_shallow(tia.messages[0].data)[2][0].data);
24763  numbers_iwa_doit(cfb, deps, tmaref, function(docroot, x) {
24764    return write_numbers_tma(cfb, deps, ws, docroot, x, tmaref);
24765  });
24766}
24767var USE_WIDE_ROWS = true;
24768function write_numbers_tma(cfb, deps, ws, tmaroot, tmafile, tmaref) {
24769  var range = decode_range(ws["!ref"]);
24770  range.s.r = range.s.c = 0;
24771  var trunc = false;
24772  if (range.e.c > 999) {
24773    trunc = true;
24774    range.e.c = 999;
24775  }
24776  if (range.e.r > 999999) {
24777    trunc = true;
24778    range.e.r = 999999;
24779  }
24780  if (trunc)
24781    console.error("Truncating to ".concat(encode_range(range)));
24782  var data = sheet_to_json(ws, { range: range, header: 1 });
24783  var SST = ["~Sh33tJ5~"];
24784  var loc = deps[tmaref].location;
24785  loc = loc.replace(/^Root Entry\//, "");
24786  loc = loc.replace(/^Index\//, "").replace(/\.iwa$/, "");
24787  var pb = parse_shallow(tmaroot.messages[0].data);
24788  {
24789    pb[6][0].data = write_varint49(range.e.r + 1);
24790    pb[7][0].data = write_varint49(range.e.c + 1);
24791    delete pb[46];
24792    var store = parse_shallow(pb[4][0].data);
24793    {
24794      var row_header_ref = parse_TSP_Reference(parse_shallow(store[1][0].data)[2][0].data);
24795      numbers_iwa_doit(cfb, deps, row_header_ref, function(rowhead, _x) {
24796        var _a;
24797        var base_bucket = parse_shallow(rowhead.messages[0].data);
24798        if ((_a = base_bucket == null ? void 0 : base_bucket[2]) == null ? void 0 : _a[0])
24799          for (var R2 = 0; R2 < data.length; ++R2) {
24800            var _bucket = parse_shallow(base_bucket[2][0].data);
24801            _bucket[1][0].data = write_varint49(R2);
24802            _bucket[4][0].data = write_varint49(data[R2].length);
24803            base_bucket[2][R2] = { type: base_bucket[2][0].type, data: write_shallow(_bucket) };
24804          }
24805        rowhead.messages[0].data = write_shallow(base_bucket);
24806      });
24807      var col_header_ref = parse_TSP_Reference(store[2][0].data);
24808      numbers_iwa_doit(cfb, deps, col_header_ref, function(colhead, _x) {
24809        var base_bucket = parse_shallow(colhead.messages[0].data);
24810        for (var C = 0; C <= range.e.c; ++C) {
24811          var _bucket = parse_shallow(base_bucket[2][0].data);
24812          _bucket[1][0].data = write_varint49(C);
24813          _bucket[4][0].data = write_varint49(range.e.r + 1);
24814          base_bucket[2][C] = { type: base_bucket[2][0].type, data: write_shallow(_bucket) };
24815        }
24816        colhead.messages[0].data = write_shallow(base_bucket);
24817      });
24818      var rbtree = parse_shallow(store[9][0].data);
24819      rbtree[1] = [];
24820      var tilestore = parse_shallow(store[3][0].data);
24821      {
24822        var tstride = 256;
24823        tilestore[2] = [{ type: 0, data: write_varint49(tstride) }];
24824        var tileref = parse_TSP_Reference(parse_shallow(tilestore[1][0].data)[2][0].data);
24825        var save_token = function() {
24826          var metadata = numbers_iwa_find(cfb, deps, 2);
24827          var mlist = parse_shallow(metadata.messages[0].data);
24828          var mlst = mlist[3].filter(function(m) {
24829            return parse_varint49(parse_shallow(m.data)[1][0].data) == tileref;
24830          });
24831          return (mlst == null ? void 0 : mlst.length) ? parse_varint49(parse_shallow(mlst[0].data)[12][0].data) : 0;
24832        }();
24833        {
24834          CFB.utils.cfb_del(cfb, deps[tileref].location);
24835          numbers_iwa_doit(cfb, deps, 2, function(ai) {
24836            var mlist = parse_shallow(ai.messages[0].data);
24837            mlist[3] = mlist[3].filter(function(m) {
24838              return parse_varint49(parse_shallow(m.data)[1][0].data) != tileref;
24839            });
24840            var parentidx = mlist[3].findIndex(function(m) {
24841              var _a, _b;
24842              var mm = parse_shallow(m.data);
24843              if ((_a = mm[3]) == null ? void 0 : _a[0])
24844                return u8str(mm[3][0].data) == loc;
24845              if (((_b = mm[2]) == null ? void 0 : _b[0]) && u8str(mm[2][0].data) == loc)
24846                return true;
24847              return false;
24848            });
24849            var parent = parse_shallow(mlist[3][parentidx].data);
24850            if (!parent[6])
24851              parent[6] = [];
24852            parent[6] = parent[6].filter(function(m) {
24853              return parse_varint49(parse_shallow(m.data)[1][0].data) != tileref;
24854            });
24855            mlist[3][parentidx].data = write_shallow(parent);
24856            ai.messages[0].data = write_shallow(mlist);
24857          });
24858          numbers_del_oref(tmaroot, tileref);
24859        }
24860        tilestore[1] = [];
24861        var ntiles = Math.ceil((range.e.r + 1) / tstride);
24862        for (var tidx = 0; tidx < ntiles; ++tidx) {
24863          var newtileid = get_unique_msgid({
24864            deps: [],
24865            location: "",
24866            type: 6002
24867          }, deps);
24868          deps[newtileid].location = "Root Entry/Index/Tables/Tile-".concat(newtileid, ".iwa");
24869          var tiledata = [
24870            [],
24871            [{ type: 0, data: write_varint49(0) }],
24872            [{ type: 0, data: write_varint49(Math.min(range.e.r + 1, (tidx + 1) * tstride)) }],
24873            [{ type: 0, data: write_varint49(0) }],
24874            [{ type: 0, data: write_varint49(Math.min((tidx + 1) * tstride, range.e.r + 1) - tidx * tstride) }],
24875            [],
24876            [{ type: 0, data: write_varint49(5) }],
24877            [{ type: 0, data: write_varint49(1) }],
24878            [{ type: 0, data: write_varint49(USE_WIDE_ROWS ? 1 : 0) }]
24879          ];
24880          for (var R = tidx * tstride; R <= Math.min(range.e.r, (tidx + 1) * tstride - 1); ++R) {
24881            var tilerow = write_TST_TileRowInfo(data[R], SST, USE_WIDE_ROWS);
24882            tilerow[1][0].data = write_varint49(R - tidx * tstride);
24883            tiledata[5].push({ data: write_shallow(tilerow), type: 2 });
24884          }
24885          tilestore[1].push({ type: 2, data: write_shallow([
24886            [],
24887            [{ type: 0, data: write_varint49(tidx) }],
24888            [{ type: 2, data: write_TSP_Reference(newtileid) }]
24889          ]) });
24890          var newtile = {
24891            id: newtileid,
24892            messages: [write_iwam(6002, write_shallow(tiledata))]
24893          };
24894          var tilecontent = compress_iwa_file(write_iwa_file([newtile]));
24895          CFB.utils.cfb_add(cfb, "/Index/Tables/Tile-".concat(newtileid, ".iwa"), tilecontent);
24896          numbers_iwa_doit(cfb, deps, 2, function(ai) {
24897            var mlist = parse_shallow(ai.messages[0].data);
24898            mlist[3].push({ type: 2, data: write_shallow([
24899              [],
24900              [{ type: 0, data: write_varint49(newtileid) }],
24901              [{ type: 2, data: stru8("Tables/Tile") }],
24902              [{ type: 2, data: stru8("Tables/Tile-".concat(newtileid)) }],
24903              [{ type: 2, data: new Uint8Array([2, 0, 0]) }],
24904              [{ type: 2, data: new Uint8Array([2, 0, 0]) }],
24905              [],
24906              [],
24907              [],
24908              [],
24909              [{ type: 0, data: write_varint49(0) }],
24910              [],
24911              [{ type: 0, data: write_varint49(save_token) }]
24912            ]) });
24913            mlist[1] = [{ type: 0, data: write_varint49(Math.max(newtileid + 1, parse_varint49(mlist[1][0].data))) }];
24914            var parentidx = mlist[3].findIndex(function(m) {
24915              var _a, _b;
24916              var mm = parse_shallow(m.data);
24917              if ((_a = mm[3]) == null ? void 0 : _a[0])
24918                return u8str(mm[3][0].data) == loc;
24919              if (((_b = mm[2]) == null ? void 0 : _b[0]) && u8str(mm[2][0].data) == loc)
24920                return true;
24921              return false;
24922            });
24923            var parent = parse_shallow(mlist[3][parentidx].data);
24924            if (!parent[6])
24925              parent[6] = [];
24926            parent[6].push({
24927              type: 2,
24928              data: write_shallow([
24929                [],
24930                [{ type: 0, data: write_varint49(newtileid) }]
24931              ])
24932            });
24933            mlist[3][parentidx].data = write_shallow(parent);
24934            ai.messages[0].data = write_shallow(mlist);
24935          });
24936          numbers_add_oref(tmaroot, newtileid);
24937          rbtree[1].push({ type: 2, data: write_shallow([
24938            [],
24939            [{ type: 0, data: write_varint49(tidx * tstride) }],
24940            [{ type: 0, data: write_varint49(tidx) }]
24941          ]) });
24942        }
24943      }
24944      store[3][0].data = write_shallow(tilestore);
24945      store[9][0].data = write_shallow(rbtree);
24946      store[10] = [{ type: 2, data: new Uint8Array([]) }];
24947      if (ws["!merges"]) {
24948        var mergeid = get_unique_msgid({
24949          type: 6144,
24950          deps: [tmaref],
24951          location: deps[tmaref].location
24952        }, deps);
24953        tmafile.push({
24954          id: mergeid,
24955          messages: [write_iwam(6144, write_shallow([
24956            [],
24957            ws["!merges"].map(function(m) {
24958              return { type: 2, data: write_shallow([
24959                [],
24960                [{ type: 2, data: write_shallow([
24961                  [],
24962                  [{ type: 5, data: new Uint8Array(new Uint16Array([m.s.r, m.s.c]).buffer) }]
24963                ]) }],
24964                [{ type: 2, data: write_shallow([
24965                  [],
24966                  [{ type: 5, data: new Uint8Array(new Uint16Array([m.e.r - m.s.r + 1, m.e.c - m.s.c + 1]).buffer) }]
24967                ]) }]
24968              ]) };
24969            })
24970          ]))]
24971        });
24972        store[13] = [{ type: 2, data: write_TSP_Reference(mergeid) }];
24973        numbers_iwa_doit(cfb, deps, 2, function(ai) {
24974          var mlist = parse_shallow(ai.messages[0].data);
24975          var parentidx = mlist[3].findIndex(function(m) {
24976            var _a, _b;
24977            var mm = parse_shallow(m.data);
24978            if ((_a = mm[3]) == null ? void 0 : _a[0])
24979              return u8str(mm[3][0].data) == loc;
24980            if (((_b = mm[2]) == null ? void 0 : _b[0]) && u8str(mm[2][0].data) == loc)
24981              return true;
24982            return false;
24983          });
24984          var parent = parse_shallow(mlist[3][parentidx].data);
24985          if (!parent[6])
24986            parent[6] = [];
24987          parent[6].push({
24988            type: 2,
24989            data: write_shallow([
24990              [],
24991              [{ type: 0, data: write_varint49(mergeid) }]
24992            ])
24993          });
24994          mlist[3][parentidx].data = write_shallow(parent);
24995          ai.messages[0].data = write_shallow(mlist);
24996        });
24997        numbers_add_oref(tmaroot, mergeid);
24998      } else
24999        delete store[13];
25000      var sstref = parse_TSP_Reference(store[4][0].data);
25001      numbers_iwa_doit(cfb, deps, sstref, function(sstroot) {
25002        var sstdata = parse_shallow(sstroot.messages[0].data);
25003        {
25004          sstdata[3] = [];
25005          SST.forEach(function(str, i) {
25006            if (i == 0)
25007              return;
25008            sstdata[3].push({ type: 2, data: write_shallow([
25009              [],
25010              [{ type: 0, data: write_varint49(i) }],
25011              [{ type: 0, data: write_varint49(1) }],
25012              [{ type: 2, data: stru8(str) }]
25013            ]) });
25014          });
25015        }
25016        sstroot.messages[0].data = write_shallow(sstdata);
25017      });
25018    }
25019    pb[4][0].data = write_shallow(store);
25020  }
25021  tmaroot.messages[0].data = write_shallow(pb);
25022}
25023function fix_opts_func(defaults/*:Array<Array<any> >*/)/*:{(o:any):void}*/ {
25024	return function fix_opts(opts) {
25025		for(var i = 0; i != defaults.length; ++i) {
25026			var d = defaults[i];
25027			if(opts[d[0]] === undefined) opts[d[0]] = d[1];
25028			if(d[2] === 'n') opts[d[0]] = Number(opts[d[0]]);
25029		}
25030	};
25031}
25032
25033function fix_read_opts(opts) {
25034fix_opts_func([
25035	['cellNF', false], /* emit cell number format string as .z */
25036	['cellHTML', true], /* emit html string as .h */
25037	['cellFormula', true], /* emit formulae as .f */
25038	['cellStyles', false], /* emits style/theme as .s */
25039	['cellText', true], /* emit formatted text as .w */
25040	['cellDates', false], /* emit date cells with type `d` */
25041
25042	['sheetStubs', false], /* emit empty cells */
25043	['sheetRows', 0, 'n'], /* read n rows (0 = read all rows) */
25044
25045	['bookDeps', false], /* parse calculation chains */
25046	['bookSheets', false], /* only try to get sheet names (no Sheets) */
25047	['bookProps', false], /* only try to get properties (no Sheets) */
25048	['bookFiles', false], /* include raw file structure (keys, files, cfb) */
25049	['bookVBA', false], /* include vba raw data (vbaraw) */
25050
25051	['password',''], /* password */
25052	['WTF', false] /* WTF mode (throws errors) */
25053])(opts);
25054}
25055
25056function fix_write_opts(opts) {
25057fix_opts_func([
25058	['cellDates', false], /* write date cells with type `d` */
25059
25060	['bookSST', false], /* Generate Shared String Table */
25061
25062	['bookType', 'xlsx'], /* Type of workbook (xlsx/m/b) */
25063
25064	['compression', false], /* Use file compression */
25065
25066	['WTF', false] /* WTF mode (throws errors) */
25067])(opts);
25068}
25069function get_sheet_type(n/*:string*/)/*:string*/ {
25070	if(RELS.WS.indexOf(n) > -1) return "sheet";
25071	if(RELS.CS && n == RELS.CS) return "chart";
25072	if(RELS.DS && n == RELS.DS) return "dialog";
25073	if(RELS.MS && n == RELS.MS) return "macro";
25074	return (n && n.length) ? n : "sheet";
25075}
25076function safe_parse_wbrels(wbrels, sheets) {
25077	if(!wbrels) return 0;
25078	try {
25079		wbrels = sheets.map(function pwbr(w) { if(!w.id) w.id = w.strRelID; return [w.name, wbrels['!id'][w.id].Target, get_sheet_type(wbrels['!id'][w.id].Type)]; });
25080	} catch(e) { return null; }
25081	return !wbrels || wbrels.length === 0 ? null : wbrels;
25082}
25083
25084function safe_parse_sheet(zip, path/*:string*/, relsPath/*:string*/, sheet, idx/*:number*/, sheetRels, sheets, stype/*:string*/, opts, wb, themes, styles) {
25085	try {
25086		sheetRels[sheet]=parse_rels(getzipstr(zip, relsPath, true), path);
25087		var data = getzipdata(zip, path);
25088		var _ws;
25089		switch(stype) {
25090			case 'sheet':  _ws = parse_ws(data, path, idx, opts, sheetRels[sheet], wb, themes, styles); break;
25091			case 'chart':  _ws = parse_cs(data, path, idx, opts, sheetRels[sheet], wb, themes, styles);
25092				if(!_ws || !_ws['!drawel']) break;
25093				var dfile = resolve_path(_ws['!drawel'].Target, path);
25094				var drelsp = get_rels_path(dfile);
25095				var draw = parse_drawing(getzipstr(zip, dfile, true), parse_rels(getzipstr(zip, drelsp, true), dfile));
25096				var chartp = resolve_path(draw, dfile);
25097				var crelsp = get_rels_path(chartp);
25098				_ws = parse_chart(getzipstr(zip, chartp, true), chartp, opts, parse_rels(getzipstr(zip, crelsp, true), chartp), wb, _ws);
25099				break;
25100			case 'macro':  _ws = parse_ms(data, path, idx, opts, sheetRels[sheet], wb, themes, styles); break;
25101			case 'dialog': _ws = parse_ds(data, path, idx, opts, sheetRels[sheet], wb, themes, styles); break;
25102			default: throw new Error("Unrecognized sheet type " + stype);
25103		}
25104		sheets[sheet] = _ws;
25105
25106		/* scan rels for comments and threaded comments */
25107		var comments = [], tcomments = [];
25108		if(sheetRels && sheetRels[sheet]) keys(sheetRels[sheet]).forEach(function(n) {
25109			var dfile = "";
25110			if(sheetRels[sheet][n].Type == RELS.CMNT) {
25111				dfile = resolve_path(sheetRels[sheet][n].Target, path);
25112				comments = parse_cmnt(getzipdata(zip, dfile, true), dfile, opts);
25113				if(!comments || !comments.length) return;
25114				sheet_insert_comments(_ws, comments, false);
25115			}
25116			if(sheetRels[sheet][n].Type == RELS.TCMNT) {
25117				dfile = resolve_path(sheetRels[sheet][n].Target, path);
25118				tcomments = tcomments.concat(parse_tcmnt_xml(getzipdata(zip, dfile, true), opts));
25119			}
25120		});
25121		if(tcomments && tcomments.length) sheet_insert_comments(_ws, tcomments, true, opts.people || []);
25122	} catch(e) { if(opts.WTF) throw e; }
25123}
25124
25125function strip_front_slash(x/*:string*/)/*:string*/ { return x.charAt(0) == '/' ? x.slice(1) : x; }
25126
25127function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
25128	make_ssf();
25129	opts = opts || {};
25130	fix_read_opts(opts);
25131
25132	/* OpenDocument Part 3 Section 2.2.1 OpenDocument Package */
25133	if(safegetzipfile(zip, 'META-INF/manifest.xml')) return parse_ods(zip, opts);
25134	/* UOC */
25135	if(safegetzipfile(zip, 'objectdata.xml')) return parse_ods(zip, opts);
25136	/* Numbers */
25137	if(safegetzipfile(zip, 'Index/Document.iwa')) {
25138		if(typeof Uint8Array == "undefined") throw new Error('NUMBERS file parsing requires Uint8Array support');
25139		if(typeof parse_numbers_iwa != "undefined") {
25140			if(zip.FileIndex) return parse_numbers_iwa(zip, opts);
25141			var _zip = CFB.utils.cfb_new();
25142			zipentries(zip).forEach(function(e) { zip_add_file(_zip, e, getzipbin(zip, e)); });
25143			return parse_numbers_iwa(_zip, opts);
25144		}
25145		throw new Error('Unsupported NUMBERS file');
25146	}
25147	if(!safegetzipfile(zip, '[Content_Types].xml')) {
25148		if(safegetzipfile(zip, 'index.xml.gz')) throw new Error('Unsupported NUMBERS 08 file');
25149		if(safegetzipfile(zip, 'index.xml')) throw new Error('Unsupported NUMBERS 09 file');
25150		var index_zip = CFB.find(zip, 'Index.zip');
25151		if(index_zip) {
25152			opts = dup(opts);
25153			delete opts.type;
25154			if(typeof index_zip.content == "string") opts.type = "binary";
25155			// TODO: Bun buffer bug
25156			if(typeof Bun !== "undefined" && Buffer.isBuffer(index_zip.content)) return readSync(new Uint8Array(index_zip.content), opts);
25157			return readSync(index_zip.content, opts);
25158		}
25159		throw new Error('Unsupported ZIP file');
25160	}
25161
25162	var entries = zipentries(zip);
25163	var dir = parse_ct((getzipstr(zip, '[Content_Types].xml')/*:?any*/));
25164	var xlsb = false;
25165	var sheets, binname;
25166	if(dir.workbooks.length === 0) {
25167		binname = "xl/workbook.xml";
25168		if(getzipdata(zip,binname, true)) dir.workbooks.push(binname);
25169	}
25170	if(dir.workbooks.length === 0) {
25171		binname = "xl/workbook.bin";
25172		if(!getzipdata(zip,binname,true)) throw new Error("Could not find workbook");
25173		dir.workbooks.push(binname);
25174		xlsb = true;
25175	}
25176	if(dir.workbooks[0].slice(-3) == "bin") xlsb = true;
25177
25178	var themes = ({}/*:any*/);
25179	var styles = ({}/*:any*/);
25180	if(!opts.bookSheets && !opts.bookProps) {
25181		strs = [];
25182		if(dir.sst) try { strs=parse_sst(getzipdata(zip, strip_front_slash(dir.sst)), dir.sst, opts); } catch(e) { if(opts.WTF) throw e; }
25183
25184		if(opts.cellStyles && dir.themes.length) themes = parse_theme_xml(getzipstr(zip, dir.themes[0].replace(/^\//,''), true)||"", opts);
25185
25186		if(dir.style) styles = parse_sty(getzipdata(zip, strip_front_slash(dir.style)), dir.style, themes, opts);
25187	}
25188
25189	/*var externbooks = */dir.links.map(function(link) {
25190		try {
25191			var rels = parse_rels(getzipstr(zip, get_rels_path(strip_front_slash(link))), link);
25192			return parse_xlink(getzipdata(zip, strip_front_slash(link)), rels, link, opts);
25193		} catch(e) {}
25194	});
25195
25196	var wb = parse_wb(getzipdata(zip, strip_front_slash(dir.workbooks[0])), dir.workbooks[0], opts);
25197
25198	var props = {}, propdata = "";
25199
25200	if(dir.coreprops.length) {
25201		propdata = getzipdata(zip, strip_front_slash(dir.coreprops[0]), true);
25202		if(propdata) props = parse_core_props(propdata);
25203		if(dir.extprops.length !== 0) {
25204			propdata = getzipdata(zip, strip_front_slash(dir.extprops[0]), true);
25205			if(propdata) parse_ext_props(propdata, props, opts);
25206		}
25207	}
25208
25209	var custprops = {};
25210	if(!opts.bookSheets || opts.bookProps) {
25211		if (dir.custprops.length !== 0) {
25212			propdata = getzipstr(zip, strip_front_slash(dir.custprops[0]), true);
25213			if(propdata) custprops = parse_cust_props(propdata, opts);
25214		}
25215	}
25216
25217	var out = ({}/*:any*/);
25218	if(opts.bookSheets || opts.bookProps) {
25219		if(wb.Sheets) sheets = wb.Sheets.map(function pluck(x){ return x.name; });
25220		else if(props.Worksheets && props.SheetNames.length > 0) sheets=props.SheetNames;
25221		if(opts.bookProps) { out.Props = props; out.Custprops = custprops; }
25222		if(opts.bookSheets && typeof sheets !== 'undefined') out.SheetNames = sheets;
25223		if(opts.bookSheets ? out.SheetNames : opts.bookProps) return out;
25224	}
25225	sheets = {};
25226
25227	var deps = {};
25228	if(opts.bookDeps && dir.calcchain) deps=parse_cc(getzipdata(zip, strip_front_slash(dir.calcchain)),dir.calcchain,opts);
25229
25230	var i=0;
25231	var sheetRels = ({}/*:any*/);
25232	var path, relsPath;
25233
25234	{
25235		var wbsheets = wb.Sheets;
25236		props.Worksheets = wbsheets.length;
25237		props.SheetNames = [];
25238		for(var j = 0; j != wbsheets.length; ++j) {
25239			props.SheetNames[j] = wbsheets[j].name;
25240		}
25241	}
25242
25243	var wbext = xlsb ? "bin" : "xml";
25244	var wbrelsi = dir.workbooks[0].lastIndexOf("/");
25245	var wbrelsfile = (dir.workbooks[0].slice(0, wbrelsi+1) + "_rels/" + dir.workbooks[0].slice(wbrelsi+1) + ".rels").replace(/^\//,"");
25246	if(!safegetzipfile(zip, wbrelsfile)) wbrelsfile = 'xl/_rels/workbook.' + wbext + '.rels';
25247	var wbrels = parse_rels(getzipstr(zip, wbrelsfile, true), wbrelsfile.replace(/_rels.*/, "s5s"));
25248
25249	if((dir.metadata || []).length >= 1) {
25250		/* TODO: MDX and other types of metadata */
25251		opts.xlmeta = parse_xlmeta(getzipdata(zip, strip_front_slash(dir.metadata[0])),dir.metadata[0],opts);
25252	}
25253
25254	if((dir.people || []).length >= 1) {
25255		opts.people = parse_people_xml(getzipdata(zip, strip_front_slash(dir.people[0])),opts);
25256	}
25257
25258	if(wbrels) wbrels = safe_parse_wbrels(wbrels, wb.Sheets);
25259
25260	/* Numbers iOS hack */
25261	var nmode = (getzipdata(zip,"xl/worksheets/sheet.xml",true))?1:0;
25262	wsloop: for(i = 0; i != props.Worksheets; ++i) {
25263		var stype = "sheet";
25264		if(wbrels && wbrels[i]) {
25265			path = 'xl/' + (wbrels[i][1]).replace(/[\/]?xl\//, "");
25266			if(!safegetzipfile(zip, path)) path = wbrels[i][1];
25267			if(!safegetzipfile(zip, path)) path = wbrelsfile.replace(/_rels\/.*$/,"") + wbrels[i][1];
25268			stype = wbrels[i][2];
25269		} else {
25270			path = 'xl/worksheets/sheet'+(i+1-nmode)+"." + wbext;
25271			path = path.replace(/sheet0\./,"sheet.");
25272		}
25273		relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels");
25274		if(opts && opts.sheets != null) switch(typeof opts.sheets) {
25275			case "number": if(i != opts.sheets) continue wsloop; break;
25276			case "string": if(props.SheetNames[i].toLowerCase() != opts.sheets.toLowerCase()) continue wsloop; break;
25277			default: if(Array.isArray && Array.isArray(opts.sheets)) {
25278				var snjseen = false;
25279				for(var snj = 0; snj != opts.sheets.length; ++snj) {
25280					if(typeof opts.sheets[snj] == "number" && opts.sheets[snj] == i) snjseen=1;
25281					if(typeof opts.sheets[snj] == "string" && opts.sheets[snj].toLowerCase() == props.SheetNames[i].toLowerCase()) snjseen = 1;
25282				}
25283				if(!snjseen) continue wsloop;
25284			}
25285		}
25286		safe_parse_sheet(zip, path, relsPath, props.SheetNames[i], i, sheetRels, sheets, stype, opts, wb, themes, styles);
25287	}
25288
25289	out = ({
25290		Directory: dir,
25291		Workbook: wb,
25292		Props: props,
25293		Custprops: custprops,
25294		Deps: deps,
25295		Sheets: sheets,
25296		SheetNames: props.SheetNames,
25297		Strings: strs,
25298		Styles: styles,
25299		Themes: themes,
25300		SSF: dup(table_fmt)
25301	}/*:any*/);
25302	if(opts && opts.bookFiles) {
25303		if(zip.files) {
25304			out.keys = entries;
25305			out.files = zip.files;
25306		} else {
25307			out.keys = [];
25308			out.files = {};
25309			zip.FullPaths.forEach(function(p, idx) {
25310				p = p.replace(/^Root Entry[\/]/, "");
25311				out.keys.push(p);
25312				out.files[p] = zip.FileIndex[idx];
25313			});
25314		}
25315	}
25316	if(opts && opts.bookVBA) {
25317		if(dir.vba.length > 0) out.vbaraw = getzipdata(zip,strip_front_slash(dir.vba[0]),true);
25318		else if(dir.defaults && dir.defaults.bin === CT_VBA) out.vbaraw = getzipdata(zip, 'xl/vbaProject.bin',true);
25319	}
25320	// TODO: pass back content types metdata for xlsm/xlsx resolution
25321	out.bookType = xlsb ? "xlsb" : "xlsx";
25322	return out;
25323}
25324
25325/* [MS-OFFCRYPTO] 2.1.1 */
25326function parse_xlsxcfb(cfb, _opts/*:?ParseOpts*/)/*:Workbook*/ {
25327	var opts = _opts || {};
25328	var f = 'Workbook', data = CFB.find(cfb, f);
25329	try {
25330	f = '/!DataSpaces/Version';
25331	data = CFB.find(cfb, f); if(!data || !data.content) throw new Error("ECMA-376 Encrypted file missing " + f);
25332	/*var version = */parse_DataSpaceVersionInfo(data.content);
25333
25334	/* 2.3.4.1 */
25335	f = '/!DataSpaces/DataSpaceMap';
25336	data = CFB.find(cfb, f); if(!data || !data.content) throw new Error("ECMA-376 Encrypted file missing " + f);
25337	var dsm = parse_DataSpaceMap(data.content);
25338	if(dsm.length !== 1 || dsm[0].comps.length !== 1 || dsm[0].comps[0].t !== 0 || dsm[0].name !== "StrongEncryptionDataSpace" || dsm[0].comps[0].v !== "EncryptedPackage")
25339		throw new Error("ECMA-376 Encrypted file bad " + f);
25340
25341	/* 2.3.4.2 */
25342	f = '/!DataSpaces/DataSpaceInfo/StrongEncryptionDataSpace';
25343	data = CFB.find(cfb, f); if(!data || !data.content) throw new Error("ECMA-376 Encrypted file missing " + f);
25344	var seds = parse_DataSpaceDefinition(data.content);
25345	if(seds.length != 1 || seds[0] != "StrongEncryptionTransform")
25346		throw new Error("ECMA-376 Encrypted file bad " + f);
25347
25348	/* 2.3.4.3 */
25349	f = '/!DataSpaces/TransformInfo/StrongEncryptionTransform/!Primary';
25350	data = CFB.find(cfb, f); if(!data || !data.content) throw new Error("ECMA-376 Encrypted file missing " + f);
25351	/*var hdr = */parse_Primary(data.content);
25352	} catch(e) {}
25353
25354	f = '/EncryptionInfo';
25355	data = CFB.find(cfb, f); if(!data || !data.content) throw new Error("ECMA-376 Encrypted file missing " + f);
25356	var einfo = parse_EncryptionInfo(data.content);
25357
25358	/* 2.3.4.4 */
25359	f = '/EncryptedPackage';
25360	data = CFB.find(cfb, f); if(!data || !data.content) throw new Error("ECMA-376 Encrypted file missing " + f);
25361
25362/*global decrypt_agile */
25363/*:: declare var decrypt_agile:any; */
25364	if(einfo[0] == 0x04 && typeof decrypt_agile !== 'undefined') return decrypt_agile(einfo[1], data.content, opts.password || "", opts);
25365/*global decrypt_std76 */
25366/*:: declare var decrypt_std76:any; */
25367	if(einfo[0] == 0x02 && typeof decrypt_std76 !== 'undefined') return decrypt_std76(einfo[1], data.content, opts.password || "", opts);
25368	throw new Error("File is password-protected");
25369}
25370
25371function write_zip_xlsb(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
25372	if(wb && !wb.SSF) {
25373		wb.SSF = dup(table_fmt);
25374	}
25375	if(wb && wb.SSF) {
25376		make_ssf(); SSF_load_table(wb.SSF);
25377		// $FlowIgnore
25378		opts.revssf = evert_num(wb.SSF); opts.revssf[wb.SSF[65535]] = 0;
25379		opts.ssf = wb.SSF;
25380	}
25381	opts.rels = {}; opts.wbrels = {};
25382	opts.Strings = /*::((*/[]/*:: :any):SST)*/; opts.Strings.Count = 0; opts.Strings.Unique = 0;
25383	if(browser_has_Map) opts.revStrings = new Map();
25384	else { opts.revStrings = {}; opts.revStrings.foo = []; delete opts.revStrings.foo; }
25385	var wbext = "bin";
25386	var vbafmt = true;
25387	var ct = new_ct();
25388	fix_write_opts(opts = opts || {});
25389	var zip = zip_new();
25390	var f = "", rId = 0;
25391
25392	opts.cellXfs = [];
25393	get_cell_style(opts.cellXfs, {}, {revssf:{"General":0}});
25394
25395	if(!wb.Props) wb.Props = {};
25396
25397	f = "docProps/core.xml";
25398	zip_add_file(zip, f, write_core_props(wb.Props, opts));
25399	ct.coreprops.push(f);
25400	add_rels(opts.rels, 2, f, RELS.CORE_PROPS);
25401
25402	/*::if(!wb.Props) throw "unreachable"; */
25403	f = "docProps/app.xml";
25404	if(wb.Props && wb.Props.SheetNames){/* empty */}
25405	else if(!wb.Workbook || !wb.Workbook.Sheets) wb.Props.SheetNames = wb.SheetNames;
25406	else {
25407		var _sn = [];
25408		for(var _i = 0; _i < wb.SheetNames.length; ++_i)
25409			if((wb.Workbook.Sheets[_i]||{}).Hidden != 2) _sn.push(wb.SheetNames[_i]);
25410		wb.Props.SheetNames = _sn;
25411	}
25412	wb.Props.Worksheets = wb.Props.SheetNames.length;
25413	zip_add_file(zip, f, write_ext_props(wb.Props, opts));
25414	ct.extprops.push(f);
25415	add_rels(opts.rels, 3, f, RELS.EXT_PROPS);
25416
25417	if(wb.Custprops !== wb.Props && keys(wb.Custprops||{}).length > 0) {
25418		f = "docProps/custom.xml";
25419		zip_add_file(zip, f, write_cust_props(wb.Custprops, opts));
25420		ct.custprops.push(f);
25421		add_rels(opts.rels, 4, f, RELS.CUST_PROPS);
25422	}
25423
25424	for(rId=1;rId <= wb.SheetNames.length; ++rId) {
25425		var wsrels = {'!id':{}};
25426		var ws = wb.Sheets[wb.SheetNames[rId-1]];
25427		var _type = (ws || {})["!type"] || "sheet";
25428		switch(_type) {
25429		case "chart":
25430			/* falls through */
25431		default:
25432			f = "xl/worksheets/sheet" + rId + "." + wbext;
25433			zip_add_file(zip, f, write_ws_bin(rId-1, opts, wb, wsrels));
25434			ct.sheets.push(f);
25435			add_rels(opts.wbrels, -1, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
25436		}
25437
25438		if(ws) {
25439			var comments = ws['!comments'];
25440			var need_vml = false;
25441			var cf = "";
25442			if(comments && comments.length > 0) {
25443				cf = "xl/comments" + rId + "." + wbext;
25444				zip_add_file(zip, cf, write_comments_bin(comments, opts));
25445				ct.comments.push(cf);
25446				add_rels(wsrels, -1, "../comments" + rId + "." + wbext, RELS.CMNT);
25447				need_vml = true;
25448			}
25449			if(ws['!legacy']) {
25450				if(need_vml) zip_add_file(zip, "xl/drawings/vmlDrawing" + (rId) + ".vml", write_vml(rId, ws['!comments']));
25451			}
25452			delete ws['!comments'];
25453			delete ws['!legacy'];
25454		}
25455
25456		if(wsrels['!id'].rId1) zip_add_file(zip, get_rels_path(f), write_rels(wsrels));
25457	}
25458
25459	if(opts.Strings != null && opts.Strings.length > 0) {
25460		f = "xl/sharedStrings." + wbext;
25461		zip_add_file(zip, f, write_sst_bin(opts.Strings, opts));
25462		ct.strs.push(f);
25463		add_rels(opts.wbrels, -1, "sharedStrings." + wbext, RELS.SST);
25464	}
25465
25466	f = "xl/workbook." + wbext;
25467	zip_add_file(zip, f, write_wb_bin(wb, opts));
25468	ct.workbooks.push(f);
25469	add_rels(opts.rels, 1, f, RELS.WB);
25470
25471	/* TODO: something more intelligent with themes */
25472
25473	f = "xl/theme/theme1.xml";
25474	var ww = write_theme(wb.Themes, opts);
25475	zip_add_file(zip, f, ww);
25476	ct.themes.push(f);
25477	add_rels(opts.wbrels, -1, "theme/theme1.xml", RELS.THEME);
25478
25479	/* TODO: something more intelligent with styles */
25480
25481	f = "xl/styles." + wbext;
25482	zip_add_file(zip, f, write_sty_bin(wb, opts));
25483	ct.styles.push(f);
25484	add_rels(opts.wbrels, -1, "styles." + wbext, RELS.STY);
25485
25486	if(wb.vbaraw && vbafmt) {
25487		f = "xl/vbaProject.bin";
25488		zip_add_file(zip, f, wb.vbaraw);
25489		ct.vba.push(f);
25490		add_rels(opts.wbrels, -1, "vbaProject.bin", RELS.VBA);
25491	}
25492
25493	f = "xl/metadata." + wbext;
25494	zip_add_file(zip, f, write_xlmeta_bin());
25495	ct.metadata.push(f);
25496	add_rels(opts.wbrels, -1, "metadata." + wbext, RELS.XLMETA);
25497
25498	zip_add_file(zip, "[Content_Types].xml", write_ct(ct, opts));
25499	zip_add_file(zip, '_rels/.rels', write_rels(opts.rels));
25500	zip_add_file(zip, 'xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels));
25501
25502	delete opts.revssf; delete opts.ssf;
25503	return zip;
25504}
25505
25506function write_zip_xlsx(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
25507	if(wb && !wb.SSF) {
25508		wb.SSF = dup(table_fmt);
25509	}
25510	if(wb && wb.SSF) {
25511		make_ssf(); SSF_load_table(wb.SSF);
25512		// $FlowIgnore
25513		opts.revssf = evert_num(wb.SSF); opts.revssf[wb.SSF[65535]] = 0;
25514		opts.ssf = wb.SSF;
25515	}
25516	opts.rels = {}; opts.wbrels = {};
25517	opts.Strings = /*::((*/[]/*:: :any):SST)*/; opts.Strings.Count = 0; opts.Strings.Unique = 0;
25518	if(browser_has_Map) opts.revStrings = new Map();
25519	else { opts.revStrings = {}; opts.revStrings.foo = []; delete opts.revStrings.foo; }
25520	var wbext = "xml";
25521	var vbafmt = VBAFMTS.indexOf(opts.bookType) > -1;
25522	var ct = new_ct();
25523	fix_write_opts(opts = opts || {});
25524	var zip = zip_new();
25525	var f = "", rId = 0;
25526
25527	opts.cellXfs = [];
25528	get_cell_style(opts.cellXfs, {}, {revssf:{"General":0}});
25529
25530	if(!wb.Props) wb.Props = {};
25531
25532	f = "docProps/core.xml";
25533	zip_add_file(zip, f, write_core_props(wb.Props, opts));
25534	ct.coreprops.push(f);
25535	add_rels(opts.rels, 2, f, RELS.CORE_PROPS);
25536
25537	/*::if(!wb.Props) throw "unreachable"; */
25538	f = "docProps/app.xml";
25539	if(wb.Props && wb.Props.SheetNames){/* empty */}
25540	else if(!wb.Workbook || !wb.Workbook.Sheets) wb.Props.SheetNames = wb.SheetNames;
25541	else {
25542		var _sn = [];
25543		for(var _i = 0; _i < wb.SheetNames.length; ++_i)
25544			if((wb.Workbook.Sheets[_i]||{}).Hidden != 2) _sn.push(wb.SheetNames[_i]);
25545		wb.Props.SheetNames = _sn;
25546	}
25547	wb.Props.Worksheets = wb.Props.SheetNames.length;
25548	zip_add_file(zip, f, write_ext_props(wb.Props, opts));
25549	ct.extprops.push(f);
25550	add_rels(opts.rels, 3, f, RELS.EXT_PROPS);
25551
25552	if(wb.Custprops !== wb.Props && keys(wb.Custprops||{}).length > 0) {
25553		f = "docProps/custom.xml";
25554		zip_add_file(zip, f, write_cust_props(wb.Custprops, opts));
25555		ct.custprops.push(f);
25556		add_rels(opts.rels, 4, f, RELS.CUST_PROPS);
25557	}
25558
25559	var people = ["SheetJ5"];
25560	opts.tcid = 0;
25561
25562	for(rId=1;rId <= wb.SheetNames.length; ++rId) {
25563		var wsrels = {'!id':{}};
25564		var ws = wb.Sheets[wb.SheetNames[rId-1]];
25565		var _type = (ws || {})["!type"] || "sheet";
25566		switch(_type) {
25567		case "chart":
25568			/* falls through */
25569		default:
25570			f = "xl/worksheets/sheet" + rId + "." + wbext;
25571			zip_add_file(zip, f, write_ws_xml(rId-1, opts, wb, wsrels));
25572			ct.sheets.push(f);
25573			add_rels(opts.wbrels, -1, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
25574		}
25575
25576		if(ws) {
25577			var comments = ws['!comments'];
25578			var need_vml = false;
25579			var cf = "";
25580			if(comments && comments.length > 0) {
25581				var needtc = false;
25582				comments.forEach(function(carr) {
25583					carr[1].forEach(function(c) { if(c.T == true) needtc = true; });
25584				});
25585				if(needtc) {
25586					cf = "xl/threadedComments/threadedComment" + rId + ".xml";
25587					zip_add_file(zip, cf, write_tcmnt_xml(comments, people, opts));
25588					ct.threadedcomments.push(cf);
25589					add_rels(wsrels, -1, "../threadedComments/threadedComment" + rId + ".xml", RELS.TCMNT);
25590				}
25591
25592				cf = "xl/comments" + rId + "." + wbext;
25593				zip_add_file(zip, cf, write_comments_xml(comments, opts));
25594				ct.comments.push(cf);
25595				add_rels(wsrels, -1, "../comments" + rId + "." + wbext, RELS.CMNT);
25596				need_vml = true;
25597			}
25598			if(ws['!legacy']) {
25599				if(need_vml) zip_add_file(zip, "xl/drawings/vmlDrawing" + (rId) + ".vml", write_vml(rId, ws['!comments']));
25600			}
25601			delete ws['!comments'];
25602			delete ws['!legacy'];
25603		}
25604
25605		if(wsrels['!id'].rId1) zip_add_file(zip, get_rels_path(f), write_rels(wsrels));
25606	}
25607
25608	if(opts.Strings != null && opts.Strings.length > 0) {
25609		f = "xl/sharedStrings." + wbext;
25610		zip_add_file(zip, f, write_sst_xml(opts.Strings, opts));
25611		ct.strs.push(f);
25612		add_rels(opts.wbrels, -1, "sharedStrings." + wbext, RELS.SST);
25613	}
25614
25615	f = "xl/workbook." + wbext;
25616	zip_add_file(zip, f, write_wb_xml(wb, opts));
25617	ct.workbooks.push(f);
25618	add_rels(opts.rels, 1, f, RELS.WB);
25619
25620	/* TODO: something more intelligent with themes */
25621
25622	f = "xl/theme/theme1.xml";
25623	zip_add_file(zip, f, write_theme(wb.Themes, opts));
25624	ct.themes.push(f);
25625	add_rels(opts.wbrels, -1, "theme/theme1.xml", RELS.THEME);
25626
25627	/* TODO: something more intelligent with styles */
25628
25629	f = "xl/styles." + wbext;
25630	zip_add_file(zip, f, write_sty_xml(wb, opts));
25631	ct.styles.push(f);
25632	add_rels(opts.wbrels, -1, "styles." + wbext, RELS.STY);
25633
25634	if(wb.vbaraw && vbafmt) {
25635		f = "xl/vbaProject.bin";
25636		zip_add_file(zip, f, wb.vbaraw);
25637		ct.vba.push(f);
25638		add_rels(opts.wbrels, -1, "vbaProject.bin", RELS.VBA);
25639	}
25640
25641	f = "xl/metadata." + wbext;
25642	zip_add_file(zip, f, write_xlmeta_xml());
25643	ct.metadata.push(f);
25644	add_rels(opts.wbrels, -1, "metadata." + wbext, RELS.XLMETA);
25645
25646	if(people.length > 1) {
25647		f = "xl/persons/person.xml";
25648		zip_add_file(zip, f, write_people_xml(people, opts));
25649		ct.people.push(f);
25650		add_rels(opts.wbrels, -1, "persons/person.xml", RELS.PEOPLE);
25651	}
25652
25653	zip_add_file(zip, "[Content_Types].xml", write_ct(ct, opts));
25654	zip_add_file(zip, '_rels/.rels', write_rels(opts.rels));
25655	zip_add_file(zip, 'xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels));
25656
25657	delete opts.revssf; delete opts.ssf;
25658	return zip;
25659}
25660
25661function firstbyte(f/*:RawData*/,o/*:?TypeOpts*/)/*:Array<number>*/ {
25662	var x = "";
25663	switch((o||{}).type || "base64") {
25664		case 'buffer': return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
25665		case 'base64': x = Base64_decode(f.slice(0,12)); break;
25666		case 'binary': x = f; break;
25667		case 'array':  return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
25668		default: throw new Error("Unrecognized type " + (o && o.type || "undefined"));
25669	}
25670	return [x.charCodeAt(0), x.charCodeAt(1), x.charCodeAt(2), x.charCodeAt(3), x.charCodeAt(4), x.charCodeAt(5), x.charCodeAt(6), x.charCodeAt(7)];
25671}
25672
25673function read_cfb(cfb/*:CFBContainer*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
25674	if(CFB.find(cfb, "EncryptedPackage")) return parse_xlsxcfb(cfb, opts);
25675	return parse_xlscfb(cfb, opts);
25676}
25677
25678function read_zip(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
25679	var zip, d = data;
25680	var o = opts||{};
25681	if(!o.type) o.type = (has_buf && Buffer.isBuffer(data)) ? "buffer" : "base64";
25682	zip = zip_read(d, o);
25683	return parse_zip(zip, o);
25684}
25685
25686function read_plaintext(data/*:string*/, o/*:ParseOpts*/)/*:Workbook*/ {
25687	var i = 0;
25688	main: while(i < data.length) switch(data.charCodeAt(i)) {
25689		case 0x0A: case 0x0D: case 0x20: ++i; break;
25690		case 0x3C: return parse_xlml(data.slice(i),o);
25691		default: break main;
25692	}
25693	return PRN.to_workbook(data, o);
25694}
25695
25696function read_plaintext_raw(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
25697	var str = "", bytes = firstbyte(data, o);
25698	switch(o.type) {
25699		case 'base64': str = Base64_decode(data); break;
25700		case 'binary': str = data; break;
25701		case 'buffer': str = data.toString('binary'); break;
25702		case 'array': str = cc2str(data); break;
25703		default: throw new Error("Unrecognized type " + o.type);
25704	}
25705	if(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) str = utf8read(str);
25706	o.type = "binary";
25707	return read_plaintext(str, o);
25708}
25709
25710function read_utf16(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
25711	var d = data;
25712	if(o.type == 'base64') d = Base64_decode(d);
25713	d = typeof $cptable !== "undefined" ? $cptable.utils.decode(1200, d.slice(2), 'str') : utf16leread(d.slice(2));
25714	o.type = "binary";
25715	return read_plaintext(d, o);
25716}
25717
25718function bstrify(data/*:string*/)/*:string*/ {
25719	return !data.match(/[^\x00-\x7F]/) ? data : utf8write(data);
25720}
25721
25722function read_prn(data, d, o, str) {
25723	if(str) { o.type = "string"; return PRN.to_workbook(data, o); }
25724	return PRN.to_workbook(d, o);
25725}
25726
25727function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
25728	reset_cp();
25729	var o = opts||{};
25730	if(o.codepage && typeof $cptable === "undefined") console.error("Codepage tables are not loaded.  Non-ASCII characters may not give expected results");
25731	if(typeof ArrayBuffer !== 'undefined' && data instanceof ArrayBuffer) return readSync(new Uint8Array(data), (o = dup(o), o.type = "array", o));
25732	if(typeof Uint8Array !== 'undefined' && data instanceof Uint8Array && !o.type) o.type = typeof Deno !== "undefined" ? "buffer" : "array";
25733	var d = data, n = [0,0,0,0], str = false;
25734	if(o.cellStyles) { o.cellNF = true; o.sheetStubs = true; }
25735	_ssfopts = {};
25736	if(o.dateNF) _ssfopts.dateNF = o.dateNF;
25737	if(!o.type) o.type = (has_buf && Buffer.isBuffer(data)) ? "buffer" : "base64";
25738	if(o.type == "file") { o.type = has_buf ? "buffer" : "binary"; d = read_binary(data); if(typeof Uint8Array !== 'undefined' && !has_buf) o.type = "array"; }
25739	if(o.type == "string") { str = true; o.type = "binary"; o.codepage = 65001; d = bstrify(data); }
25740	if(o.type == 'array' && typeof Uint8Array !== 'undefined' && data instanceof Uint8Array && typeof ArrayBuffer !== 'undefined') {
25741		// $FlowIgnore
25742		var ab=new ArrayBuffer(3), vu=new Uint8Array(ab); vu.foo="bar";
25743		// $FlowIgnore
25744		if(!vu.foo) {o=dup(o); o.type='array'; return readSync(ab2a(d), o);}
25745	}
25746	switch((n = firstbyte(d, o))[0]) {
25747		case 0xD0: if(n[1] === 0xCF && n[2] === 0x11 && n[3] === 0xE0 && n[4] === 0xA1 && n[5] === 0xB1 && n[6] === 0x1A && n[7] === 0xE1) return read_cfb(CFB.read(d, o), o); break;
25748		case 0x09: if(n[1] <= 0x08) return parse_xlscfb(d, o); break;
25749		case 0x3C: return parse_xlml(d, o);
25750		case 0x49:
25751			if(n[1] === 0x49 && n[2] === 0x2a && n[3] === 0x00) throw new Error("TIFF Image File is not a spreadsheet");
25752			if(n[1] === 0x44) return read_wb_ID(d, o);
25753			break;
25754		case 0x54: if(n[1] === 0x41 && n[2] === 0x42 && n[3] === 0x4C) return DIF.to_workbook(d, o); break;
25755		case 0x50: return (n[1] === 0x4B && n[2] < 0x09 && n[3] < 0x09) ? read_zip(d, o) : read_prn(data, d, o, str);
25756		case 0xEF: return n[3] === 0x3C ? parse_xlml(d, o) : read_prn(data, d, o, str);
25757		case 0xFF:
25758			if(n[1] === 0xFE) { return read_utf16(d, o); }
25759			else if(n[1] === 0x00 && n[2] === 0x02 && n[3] === 0x00) return WK_.to_workbook(d, o);
25760			break;
25761		case 0x00:
25762			if(n[1] === 0x00) {
25763				if(n[2] >= 0x02 && n[3] === 0x00) return WK_.to_workbook(d, o);
25764				if(n[2] === 0x00 && (n[3] === 0x08 || n[3] === 0x09)) return WK_.to_workbook(d, o);
25765			}
25766			break;
25767		case 0x03: case 0x83: case 0x8B: case 0x8C: return DBF.to_workbook(d, o);
25768		case 0x7B: if(n[1] === 0x5C && n[2] === 0x72 && n[3] === 0x74) return rtf_to_workbook(d, o); break;
25769		case 0x0A: case 0x0D: case 0x20: return read_plaintext_raw(d, o);
25770		case 0x89: if(n[1] === 0x50 && n[2] === 0x4E && n[3] === 0x47) throw new Error("PNG Image File is not a spreadsheet"); break;
25771		case 0x08: if(n[1] === 0xE7) throw new Error("Unsupported Multiplan 1.x file!"); break;
25772		case 0x0C:
25773			if(n[1] === 0xEC) throw new Error("Unsupported Multiplan 2.x file!");
25774			if(n[1] === 0xED) throw new Error("Unsupported Multiplan 3.x file!");
25775			break;
25776	}
25777	if(DBF_SUPPORTED_VERSIONS.indexOf(n[0]) > -1 && n[2] <= 12 && n[3] <= 31) return DBF.to_workbook(d, o);
25778	return read_prn(data, d, o, str);
25779}
25780
25781function readFileSync(filename/*:string*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
25782	var o = opts||{}; o.type = 'file';
25783	return readSync(filename, o);
25784}
25785function write_cfb_ctr(cfb/*:CFBContainer*/, o/*:WriteOpts*/)/*:any*/ {
25786	switch(o.type) {
25787		case "base64": case "binary": break;
25788		case "buffer": case "array": o.type = ""; break;
25789		case "file": return write_dl(o.file, CFB.write(cfb, {type:has_buf ? 'buffer' : ""}));
25790		case "string": throw new Error("'string' output type invalid for '" + o.bookType + "' files");
25791		default: throw new Error("Unrecognized type " + o.type);
25792	}
25793	return CFB.write(cfb, o);
25794}
25795
25796function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
25797	switch(opts.bookType) {
25798		case "ods": return write_ods(wb, opts);
25799		case "numbers": return write_numbers_iwa(wb, opts);
25800		case "xlsb": return write_zip_xlsb(wb, opts);
25801		default: return write_zip_xlsx(wb, opts);
25802	}
25803}
25804
25805/*:: declare var encrypt_agile:any; */
25806function write_zip_type(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:any*/ {
25807	var o = dup(opts||{});
25808	var z = write_zip(wb, o);
25809	return write_zip_denouement(z, o);
25810}
25811function write_zip_typeXLSX(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:any*/ {
25812	var o = dup(opts||{});
25813	var z = write_zip_xlsx(wb, o);
25814	return write_zip_denouement(z, o);
25815}
25816function write_zip_denouement(z/*:any*/, o/*:?WriteOpts*/)/*:any*/ {
25817	var oopts = {};
25818	var ftype = has_buf ? "nodebuffer" : (typeof Uint8Array !== "undefined" ? "array" : "string");
25819	if(o.compression) oopts.compression = 'DEFLATE';
25820	if(o.password) oopts.type = ftype;
25821	else switch(o.type) {
25822		case "base64": oopts.type = "base64"; break;
25823		case "binary": oopts.type = "string"; break;
25824		case "string": throw new Error("'string' output type invalid for '" + o.bookType + "' files");
25825		case "buffer":
25826		case "file": oopts.type = ftype; break;
25827		default: throw new Error("Unrecognized type " + o.type);
25828	}
25829	var out = z.FullPaths ? CFB.write(z, {fileType:"zip", type: /*::(*/{"nodebuffer": "buffer", "string": "binary"}/*:: :any)*/[oopts.type] || oopts.type, compression: !!o.compression}) : z.generate(oopts);
25830	if(typeof Deno !== "undefined") {
25831		if(typeof out == "string") {
25832			if(o.type == "binary" || o.type == "base64") return out;
25833			out = new Uint8Array(s2ab(out));
25834		}
25835	}
25836/*jshint -W083 */
25837	if(o.password && typeof encrypt_agile !== 'undefined') return write_cfb_ctr(encrypt_agile(out, o.password), o); // eslint-disable-line no-undef
25838/*jshint +W083 */
25839	if(o.type === "file") return write_dl(o.file, out);
25840	return o.type == "string" ? utf8read(/*::(*/out/*:: :any)*/) : out;
25841}
25842
25843function write_cfb_type(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:any*/ {
25844	var o = opts||{};
25845	var cfb/*:CFBContainer*/ = write_xlscfb(wb, o);
25846	return write_cfb_ctr(cfb, o);
25847}
25848
25849function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/*:any*/ {
25850	if(!bom) bom = "";
25851	var o = bom + out;
25852	switch(opts.type) {
25853		case "base64": return Base64_encode(utf8write(o));
25854		case "binary": return utf8write(o);
25855		case "string": return out;
25856		case "file": return write_dl(opts.file, o, 'utf8');
25857		case "buffer": {
25858			if(has_buf) return Buffer_from(o, 'utf8');
25859			else if(typeof TextEncoder !== "undefined") return new TextEncoder().encode(o);
25860			else return write_string_type(o, {type:'binary'}).split("").map(function(c) { return c.charCodeAt(0); });
25861		}
25862	}
25863	throw new Error("Unrecognized type " + opts.type);
25864}
25865
25866function write_stxt_type(out/*:string*/, opts/*:WriteOpts*/)/*:any*/ {
25867	switch(opts.type) {
25868		case "base64": return Base64_encode_pass(out);
25869		case "binary": return out;
25870		case "string": return out; /* override in sheet_to_txt */
25871		case "file": return write_dl(opts.file, out, 'binary');
25872		case "buffer": {
25873			if(has_buf) return Buffer_from(out, 'binary');
25874			else return out.split("").map(function(c) { return c.charCodeAt(0); });
25875		}
25876	}
25877	throw new Error("Unrecognized type " + opts.type);
25878}
25879
25880/* TODO: test consistency */
25881function write_binary_type(out, opts/*:WriteOpts*/)/*:any*/ {
25882	switch(opts.type) {
25883		case "string":
25884		case "base64":
25885		case "binary":
25886			var bstr = "";
25887			// $FlowIgnore
25888			for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
25889			return opts.type == 'base64' ? Base64_encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
25890		case "file": return write_dl(opts.file, out);
25891		case "buffer": return out;
25892		default: throw new Error("Unrecognized type " + opts.type);
25893	}
25894}
25895
25896function writeSyncXLSX(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
25897	reset_cp();
25898	check_wb(wb);
25899	var o = dup(opts||{});
25900	if(o.cellStyles) { o.cellNF = true; o.sheetStubs = true; }
25901	if(o.type == "array") { o.type = "binary"; var out/*:string*/ = (writeSyncXLSX(wb, o)/*:any*/); o.type = "array"; return s2ab(out); }
25902	return write_zip_typeXLSX(wb, o);
25903}
25904
25905function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
25906	reset_cp();
25907	check_wb(wb);
25908	var o = dup(opts||{});
25909	if(o.cellStyles) { o.cellNF = true; o.sheetStubs = true; }
25910	if(o.type == "array") { o.type = "binary"; var out/*:string*/ = (writeSync(wb, o)/*:any*/); o.type = "array"; return s2ab(out); }
25911	var idx = 0;
25912	if(o.sheet) {
25913		if(typeof o.sheet == "number") idx = o.sheet;
25914		else idx = wb.SheetNames.indexOf(o.sheet);
25915		if(!wb.SheetNames[idx]) throw new Error("Sheet not found: " + o.sheet + " : " + (typeof o.sheet));
25916	}
25917	switch(o.bookType || 'xlsb') {
25918		case 'xml':
25919		case 'xlml': return write_string_type(write_xlml(wb, o), o);
25920		case 'slk':
25921		case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb), o);
25922		case 'htm':
25923		case 'html': return write_string_type(sheet_to_html(wb.Sheets[wb.SheetNames[idx]], o), o);
25924		case 'txt': return write_stxt_type(sheet_to_txt(wb.Sheets[wb.SheetNames[idx]], o), o);
25925		case 'csv': return write_string_type(sheet_to_csv(wb.Sheets[wb.SheetNames[idx]], o), o, "\ufeff");
25926		case 'dif': return write_string_type(DIF.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
25927		case 'dbf': return write_binary_type(DBF.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
25928		case 'prn': return write_string_type(PRN.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
25929		case 'rtf': return write_string_type(sheet_to_rtf(wb.Sheets[wb.SheetNames[idx]], o), o);
25930		case 'eth': return write_string_type(ETH.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
25931		case 'fods': return write_string_type(write_ods(wb, o), o);
25932		case 'wk1': return write_binary_type(WK_.sheet_to_wk1(wb.Sheets[wb.SheetNames[idx]], o), o);
25933		case 'wk3': return write_binary_type(WK_.book_to_wk3(wb, o), o);
25934		case 'biff2': if(!o.biff) o.biff = 2; /* falls through */
25935		case 'biff3': if(!o.biff) o.biff = 3; /* falls through */
25936		case 'biff4': if(!o.biff) o.biff = 4; return write_binary_type(write_biff_buf(wb, o), o);
25937		case 'biff5': if(!o.biff) o.biff = 5; /* falls through */
25938		case 'biff8':
25939		case 'xla':
25940		case 'xls': if(!o.biff) o.biff = 8; return write_cfb_type(wb, o);
25941		case 'xlsx':
25942		case 'xlsm':
25943		case 'xlam':
25944		case 'xlsb':
25945		case 'numbers':
25946		case 'ods': return write_zip_type(wb, o);
25947		default: throw new Error ("Unrecognized bookType |" + o.bookType + "|");
25948	}
25949}
25950
25951function resolve_book_type(o/*:WriteFileOpts*/) {
25952	if(o.bookType) return;
25953	var _BT = {
25954		"xls": "biff8",
25955		"htm": "html",
25956		"slk": "sylk",
25957		"socialcalc": "eth",
25958		"Sh33tJS": "WTF"
25959	};
25960	var ext = o.file.slice(o.file.lastIndexOf(".")).toLowerCase();
25961	if(ext.match(/^\.[a-z]+$/)) o.bookType = ext.slice(1);
25962	o.bookType = _BT[o.bookType] || o.bookType;
25963}
25964
25965function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) {
25966	var o = opts||{}; o.type = 'file';
25967	o.file = filename;
25968	resolve_book_type(o);
25969	return writeSync(wb, o);
25970}
25971
25972function writeFileSyncXLSX(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) {
25973	var o = opts||{}; o.type = 'file';
25974	o.file = filename;
25975	resolve_book_type(o);
25976	return writeSyncXLSX(wb, o);
25977}
25978
25979
25980function writeFileAsync(filename/*:string*/, wb/*:Workbook*/, opts/*:?WriteFileOpts*/, cb/*:?(e?:ErrnoError)=>void*/) {
25981	var o = opts||{}; o.type = 'file';
25982	o.file = filename;
25983	resolve_book_type(o);
25984	o.type = 'buffer';
25985	var _cb = cb; if(!(_cb instanceof Function)) _cb = (opts/*:any*/);
25986	return _fs.writeFile(filename, writeSync(wb, o), _cb);
25987}
25988/*::
25989type MJRObject = {
25990	row: any;
25991	isempty: boolean;
25992};
25993*/
25994function make_json_row(sheet/*:Worksheet*/, r/*:Range*/, R/*:number*/, cols/*:Array<string>*/, header/*:number*/, hdr/*:Array<any>*/, dense/*:boolean*/, o/*:Sheet2JSONOpts*/)/*:MJRObject*/ {
25995	var rr = encode_row(R);
25996	var defval = o.defval, raw = o.raw || !Object.prototype.hasOwnProperty.call(o, "raw");
25997	var isempty = true;
25998	var row/*:any*/ = (header === 1) ? [] : {};
25999	if(header !== 1) {
26000		if(Object.defineProperty) try { Object.defineProperty(row, '__rowNum__', {value:R, enumerable:false}); } catch(e) { row.__rowNum__ = R; }
26001		else row.__rowNum__ = R;
26002	}
26003	if(!dense || sheet[R]) for (var C = r.s.c; C <= r.e.c; ++C) {
26004		var val = dense ? sheet[R][C] : sheet[cols[C] + rr];
26005		if(val === undefined || val.t === undefined) {
26006			if(defval === undefined) continue;
26007			if(hdr[C] != null) { row[hdr[C]] = defval; }
26008			continue;
26009		}
26010		var v = val.v;
26011		switch(val.t){
26012			case 'z': if(v == null) break; continue;
26013			case 'e': v = (v == 0 ? null : void 0); break;
26014			case 's': case 'd': case 'b': case 'n': break;
26015			default: throw new Error('unrecognized type ' + val.t);
26016		}
26017		if(hdr[C] != null) {
26018			if(v == null) {
26019				if(val.t == "e" && v === null) row[hdr[C]] = null;
26020				else if(defval !== undefined) row[hdr[C]] = defval;
26021				else if(raw && v === null) row[hdr[C]] = null;
26022				else continue;
26023			} else {
26024				row[hdr[C]] = raw && (val.t !== "n" || (val.t === "n" && o.rawNumbers !== false)) ? v : format_cell(val,v,o);
26025			}
26026			if(v != null) isempty = false;
26027		}
26028	}
26029	return { row: row, isempty: isempty };
26030}
26031
26032
26033function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/) {
26034	if(sheet == null || sheet["!ref"] == null) return [];
26035	var val = {t:'n',v:0}, header = 0, offset = 1, hdr/*:Array<any>*/ = [], v=0, vv="";
26036	var r = {s:{r:0,c:0},e:{r:0,c:0}};
26037	var o = opts || {};
26038	var range = o.range != null ? o.range : sheet["!ref"];
26039	if(o.header === 1) header = 1;
26040	else if(o.header === "A") header = 2;
26041	else if(Array.isArray(o.header)) header = 3;
26042	else if(o.header == null) header = 0;
26043	switch(typeof range) {
26044		case 'string': r = safe_decode_range(range); break;
26045		case 'number': r = safe_decode_range(sheet["!ref"]); r.s.r = range; break;
26046		default: r = range;
26047	}
26048	if(header > 0) offset = 0;
26049	var rr = encode_row(r.s.r);
26050	var cols/*:Array<string>*/ = [];
26051	var out/*:Array<any>*/ = [];
26052	var outi = 0, counter = 0;
26053	var dense = Array.isArray(sheet);
26054	var R = r.s.r, C = 0;
26055	var header_cnt = {};
26056	if(dense && !sheet[R]) sheet[R] = [];
26057	var colinfo/*:Array<ColInfo>*/ = o.skipHidden && sheet["!cols"] || [];
26058	var rowinfo/*:Array<ColInfo>*/ = o.skipHidden && sheet["!rows"] || [];
26059	for(C = r.s.c; C <= r.e.c; ++C) {
26060		if(((colinfo[C]||{}).hidden)) continue;
26061		cols[C] = encode_col(C);
26062		val = dense ? sheet[R][C] : sheet[cols[C] + rr];
26063		switch(header) {
26064			case 1: hdr[C] = C - r.s.c; break;
26065			case 2: hdr[C] = cols[C]; break;
26066			case 3: hdr[C] = o.header[C - r.s.c]; break;
26067			default:
26068				if(val == null) val = {w: "__EMPTY", t: "s"};
26069				vv = v = format_cell(val, null, o);
26070				counter = header_cnt[v] || 0;
26071				if(!counter) header_cnt[v] = 1;
26072				else {
26073					do { vv = v + "_" + (counter++); } while(header_cnt[vv]); header_cnt[v] = counter;
26074					header_cnt[vv] = 1;
26075				}
26076				hdr[C] = vv;
26077		}
26078	}
26079	for (R = r.s.r + offset; R <= r.e.r; ++R) {
26080		if ((rowinfo[R]||{}).hidden) continue;
26081		var row = make_json_row(sheet, r, R, cols, header, hdr, dense, o);
26082		if((row.isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row.row;
26083	}
26084	out.length = outi;
26085	return out;
26086}
26087
26088var qreg = /"/g;
26089function make_csv_row(sheet/*:Worksheet*/, r/*:Range*/, R/*:number*/, cols/*:Array<string>*/, fs/*:number*/, rs/*:number*/, FS/*:string*/, o/*:Sheet2CSVOpts*/)/*:?string*/ {
26090	var isempty = true;
26091	var row/*:Array<string>*/ = [], txt = "", rr = encode_row(R);
26092	for(var C = r.s.c; C <= r.e.c; ++C) {
26093		if (!cols[C]) continue;
26094		var val = o.dense ? (sheet[R]||[])[C]: sheet[cols[C] + rr];
26095		if(val == null) txt = "";
26096		else if(val.v != null) {
26097			isempty = false;
26098			txt = ''+(o.rawNumbers && val.t == "n" ? val.v : format_cell(val, null, o));
26099			for(var i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34 || o.forceQuotes) {txt = "\"" + txt.replace(qreg, '""') + "\""; break; }
26100			if(txt == "ID") txt = '"ID"';
26101		} else if(val.f != null && !val.F) {
26102			isempty = false;
26103			txt = '=' + val.f; if(txt.indexOf(",") >= 0) txt = '"' + txt.replace(qreg, '""') + '"';
26104		} else txt = "";
26105		/* NOTE: Excel CSV does not support array formulae */
26106		row.push(txt);
26107	}
26108	if(o.blankrows === false && isempty) return null;
26109	return row.join(FS);
26110}
26111
26112function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/)/*:string*/ {
26113	var out/*:Array<string>*/ = [];
26114	var o = opts == null ? {} : opts;
26115	if(sheet == null || sheet["!ref"] == null) return "";
26116	var r = safe_decode_range(sheet["!ref"]);
26117	var FS = o.FS !== undefined ? o.FS : ",", fs = FS.charCodeAt(0);
26118	var RS = o.RS !== undefined ? o.RS : "\n", rs = RS.charCodeAt(0);
26119	var endregex = new RegExp((FS=="|" ? "\\|" : FS)+"+$");
26120	var row = "", cols/*:Array<string>*/ = [];
26121	o.dense = Array.isArray(sheet);
26122	var colinfo/*:Array<ColInfo>*/ = o.skipHidden && sheet["!cols"] || [];
26123	var rowinfo/*:Array<ColInfo>*/ = o.skipHidden && sheet["!rows"] || [];
26124	for(var C = r.s.c; C <= r.e.c; ++C) if (!((colinfo[C]||{}).hidden)) cols[C] = encode_col(C);
26125	var w = 0;
26126	for(var R = r.s.r; R <= r.e.r; ++R) {
26127		if ((rowinfo[R]||{}).hidden) continue;
26128		row = make_csv_row(sheet, r, R, cols, fs, rs, FS, o);
26129		if(row == null) { continue; }
26130		if(o.strip) row = row.replace(endregex,"");
26131		if(row || (o.blankrows !== false)) out.push((w++ ? RS : "") + row);
26132	}
26133	delete o.dense;
26134	return out.join("");
26135}
26136
26137function sheet_to_txt(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
26138	if(!opts) opts = {}; opts.FS = "\t"; opts.RS = "\n";
26139	var s = sheet_to_csv(sheet, opts);
26140	if(typeof $cptable == 'undefined' || opts.type == 'string') return s;
26141	var o = $cptable.utils.encode(1200, s, 'str');
26142	return String.fromCharCode(255) + String.fromCharCode(254) + o;
26143}
26144
26145function sheet_to_formulae(sheet/*:Worksheet*/)/*:Array<string>*/ {
26146	var y = "", x, val="";
26147	if(sheet == null || sheet["!ref"] == null) return [];
26148	var r = safe_decode_range(sheet['!ref']), rr = "", cols/*:Array<string>*/ = [], C;
26149	var cmds/*:Array<string>*/ = [];
26150	var dense = Array.isArray(sheet);
26151	for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C);
26152	for(var R = r.s.r; R <= r.e.r; ++R) {
26153		rr = encode_row(R);
26154		for(C = r.s.c; C <= r.e.c; ++C) {
26155			y = cols[C] + rr;
26156			x = dense ? (sheet[R]||[])[C] : sheet[y];
26157			val = "";
26158			if(x === undefined) continue;
26159			else if(x.F != null) {
26160				y = x.F;
26161				if(!x.f) continue;
26162				val = x.f;
26163				if(y.indexOf(":") == -1) y = y + ":" + y;
26164			}
26165			if(x.f != null) val = x.f;
26166			else if(x.t == 'z') continue;
26167			else if(x.t == 'n' && x.v != null) val = "" + x.v;
26168			else if(x.t == 'b') val = x.v ? "TRUE" : "FALSE";
26169			else if(x.w !== undefined) val = "'" + x.w;
26170			else if(x.v === undefined) continue;
26171			else if(x.t == 's') val = "'" + x.v;
26172			else val = ""+x.v;
26173			cmds[cmds.length] = y + "=" + val;
26174		}
26175	}
26176	return cmds;
26177}
26178
26179function sheet_add_json(_ws/*:?Worksheet*/, js/*:Array<any>*/, opts)/*:Worksheet*/ {
26180	var o = opts || {};
26181	var dense = _ws ? Array.isArray(_ws) : o.dense;
26182	if(DENSE != null && dense == null) dense = DENSE;
26183	var offset = +!o.skipHeader;
26184	var ws/*:Worksheet*/ = _ws || (dense ? ([]/*:any*/) : ({}/*:any*/));
26185	var _R = 0, _C = 0;
26186	if(ws && o.origin != null) {
26187		if(typeof o.origin == 'number') _R = o.origin;
26188		else {
26189			var _origin/*:CellAddress*/ = typeof o.origin == "string" ? decode_cell(o.origin) : o.origin;
26190			_R = _origin.r; _C = _origin.c;
26191		}
26192	}
26193	var range/*:Range*/ = ({s: {c:0, r:0}, e: {c:_C, r:_R + js.length - 1 + offset}}/*:any*/);
26194	if(ws['!ref']) {
26195		var _range = safe_decode_range(ws['!ref']);
26196		range.e.c = Math.max(range.e.c, _range.e.c);
26197		range.e.r = Math.max(range.e.r, _range.e.r);
26198		if(_R == -1) { _R = _range.e.r + 1; range.e.r = _R + js.length - 1 + offset; }
26199	} else {
26200		if(_R == -1) { _R = 0; range.e.r = js.length - 1 + offset; }
26201	}
26202	var hdr/*:Array<string>*/ = o.header || [], C = 0;
26203	var ROW = [];
26204	js.forEach(function (JS, R/*:number*/) {
26205		if(dense && !ws[_R + R + offset]) ws[_R + R + offset] = [];
26206		if(dense) ROW = ws[_R + R + offset];
26207		keys(JS).forEach(function(k) {
26208			if((C=hdr.indexOf(k)) == -1) hdr[C=hdr.length] = k;
26209			var v = JS[k];
26210			var t = 'z';
26211			var z = "";
26212			var ref = dense ? "" : encode_cell({c:_C + C,r:_R + R + offset});
26213			var cell/*:Cell*/ = dense ? ROW[_C + C] : ws[ref];
26214			if(v && typeof v === 'object' && !(v instanceof Date)){
26215				ws[ref] = v;
26216			} else {
26217				if(typeof v == 'number') t = 'n';
26218				else if(typeof v == 'boolean') t = 'b';
26219				else if(typeof v == 'string') t = 's';
26220				else if(v instanceof Date) {
26221					t = 'd';
26222					if(!o.cellDates) { t = 'n'; v = datenum(v); }
26223					z = (cell != null && cell.z && fmt_is_date(cell.z)) ? cell.z : (o.dateNF || table_fmt[14]);
26224				}
26225				else if(v === null && o.nullError) { t = 'e'; v = 0; }
26226				if(!cell) {
26227					if(!dense) ws[ref] = cell = ({t:t, v:v}/*:any*/);
26228					else ROW[_C + C] = cell = ({t:t, v:v}/*:any*/);
26229				} else {
26230					cell.t = t; cell.v = v;
26231					delete cell.w; delete cell.R;
26232					if(z) cell.z = z;
26233				}
26234				if(z) cell.z = z;
26235			}
26236		});
26237	});
26238	range.e.c = Math.max(range.e.c, _C + hdr.length - 1);
26239	var __R = encode_row(_R);
26240	if(dense && !ws[_R]) ws[_R] = [];
26241	if(offset) for(C = 0; C < hdr.length; ++C) {
26242		if(dense) ws[_R][C + _C] = {t:'s', v:hdr[C]};
26243		else ws[encode_col(C + _C) + __R] = {t:'s', v:hdr[C]};
26244	}
26245	ws['!ref'] = encode_range(range);
26246	return ws;
26247}
26248function json_to_sheet(js/*:Array<any>*/, opts)/*:Worksheet*/ { return sheet_add_json(null, js, opts); }
26249
26250/* get cell, creating a stub if necessary */
26251function ws_get_cell_stub(ws/*:Worksheet*/, R, C/*:?number*/)/*:Cell*/ {
26252	/* A1 cell address */
26253	if(typeof R == "string") {
26254		/* dense */
26255		if(Array.isArray(ws)) {
26256			var RC = decode_cell(R);
26257			if(!ws[RC.r]) ws[RC.r] = [];
26258			return ws[RC.r][RC.c] || (ws[RC.r][RC.c] = {t:'z'});
26259		}
26260		return ws[R] || (ws[R] = {t:'z'});
26261	}
26262	/* cell address object */
26263	if(typeof R != "number") return ws_get_cell_stub(ws, encode_cell(R));
26264	/* R and C are 0-based indices */
26265	return ws_get_cell_stub(ws, encode_cell({r:R,c:C||0}));
26266}
26267
26268/* find sheet index for given name / validate index */
26269function wb_sheet_idx(wb/*:Workbook*/, sh/*:number|string*/) {
26270	if(typeof sh == "number") {
26271		if(sh >= 0 && wb.SheetNames.length > sh) return sh;
26272		throw new Error("Cannot find sheet # " + sh);
26273	} else if(typeof sh == "string") {
26274		var idx = wb.SheetNames.indexOf(sh);
26275		if(idx > -1) return idx;
26276		throw new Error("Cannot find sheet name |" + sh + "|");
26277	} else throw new Error("Cannot find sheet |" + sh + "|");
26278}
26279
26280/* simple blank workbook object */
26281function book_new()/*:Workbook*/ {
26282	return { SheetNames: [], Sheets: {} };
26283}
26284
26285/* add a worksheet to the end of a given workbook */
26286function book_append_sheet(wb/*:Workbook*/, ws/*:Worksheet*/, name/*:?string*/, roll/*:?boolean*/)/*:string*/ {
26287	var i = 1;
26288	if(!name) for(; i <= 0xFFFF; ++i, name = undefined) if(wb.SheetNames.indexOf(name = "Sheet" + i) == -1) break;
26289	if(!name || wb.SheetNames.length >= 0xFFFF) throw new Error("Too many worksheets");
26290	if(roll && wb.SheetNames.indexOf(name) >= 0) {
26291		var m = name.match(/(^.*?)(\d+)$/);
26292		i = m && +m[2] || 0;
26293		var root = m && m[1] || name;
26294		for(++i; i <= 0xFFFF; ++i) if(wb.SheetNames.indexOf(name = root + i) == -1) break;
26295	}
26296	check_ws_name(name);
26297	if(wb.SheetNames.indexOf(name) >= 0) throw new Error("Worksheet with name |" + name + "| already exists!");
26298
26299	wb.SheetNames.push(name);
26300	wb.Sheets[name] = ws;
26301	return name;
26302}
26303
26304/* set sheet visibility (visible/hidden/very hidden) */
26305function book_set_sheet_visibility(wb/*:Workbook*/, sh/*:number|string*/, vis/*:number*/) {
26306	if(!wb.Workbook) wb.Workbook = {};
26307	if(!wb.Workbook.Sheets) wb.Workbook.Sheets = [];
26308
26309	var idx = wb_sheet_idx(wb, sh);
26310	// $FlowIgnore
26311	if(!wb.Workbook.Sheets[idx]) wb.Workbook.Sheets[idx] = {};
26312
26313	switch(vis) {
26314		case 0: case 1: case 2: break;
26315		default: throw new Error("Bad sheet visibility setting " + vis);
26316	}
26317	// $FlowIgnore
26318	wb.Workbook.Sheets[idx].Hidden = vis;
26319}
26320
26321/* set number format */
26322function cell_set_number_format(cell/*:Cell*/, fmt/*:string|number*/) {
26323	cell.z = fmt;
26324	return cell;
26325}
26326
26327/* set cell hyperlink */
26328function cell_set_hyperlink(cell/*:Cell*/, target/*:string*/, tooltip/*:?string*/) {
26329	if(!target) {
26330		delete cell.l;
26331	} else {
26332		cell.l = ({ Target: target }/*:Hyperlink*/);
26333		if(tooltip) cell.l.Tooltip = tooltip;
26334	}
26335	return cell;
26336}
26337function cell_set_internal_link(cell/*:Cell*/, range/*:string*/, tooltip/*:?string*/) { return cell_set_hyperlink(cell, "#" + range, tooltip); }
26338
26339/* add to cell comments */
26340function cell_add_comment(cell/*:Cell*/, text/*:string*/, author/*:?string*/) {
26341	if(!cell.c) cell.c = [];
26342	cell.c.push({t:text, a:author||"SheetJS"});
26343}
26344
26345/* set array formula and flush related cells */
26346function sheet_set_array_formula(ws/*:Worksheet*/, range, formula/*:string*/, dynamic/*:boolean*/) {
26347	var rng = typeof range != "string" ? range : safe_decode_range(range);
26348	var rngstr = typeof range == "string" ? range : encode_range(range);
26349	for(var R = rng.s.r; R <= rng.e.r; ++R) for(var C = rng.s.c; C <= rng.e.c; ++C) {
26350		var cell = ws_get_cell_stub(ws, R, C);
26351		cell.t = 'n';
26352		cell.F = rngstr;
26353		delete cell.v;
26354		if(R == rng.s.r && C == rng.s.c) {
26355			cell.f = formula;
26356			if(dynamic) cell.D = true;
26357		}
26358	}
26359	var wsr = decode_range(ws["!ref"]);
26360	if(wsr.s.r > rng.s.r) wsr.s.r = rng.s.r;
26361	if(wsr.s.c > rng.s.c) wsr.s.c = rng.s.c;
26362	if(wsr.e.r < rng.e.r) wsr.e.r = rng.e.r;
26363	if(wsr.e.c < rng.e.c) wsr.e.c = rng.e.c;
26364	ws["!ref"] = encode_range(wsr);
26365	return ws;
26366}
26367
26368var utils1/*:any*/ = {
26369	encode_col: encode_col,
26370	encode_row: encode_row,
26371	encode_cell: encode_cell,
26372	encode_range: encode_range,
26373	decode_col: decode_col,
26374	decode_row: decode_row,
26375	split_cell: split_cell,
26376	decode_cell: decode_cell,
26377	decode_range: decode_range,
26378	format_cell: format_cell,
26379	sheet_add_aoa: sheet_add_aoa,
26380	sheet_add_json: sheet_add_json,
26381	sheet_add_dom: sheet_add_dom,
26382	aoa_to_sheet: aoa_to_sheet,
26383	json_to_sheet: json_to_sheet,
26384	table_to_sheet: parse_dom_table,
26385	table_to_book: table_to_book,
26386	sheet_to_csv: sheet_to_csv,
26387	sheet_to_txt: sheet_to_txt,
26388	sheet_to_json: sheet_to_json,
26389	sheet_to_html: sheet_to_html,
26390	sheet_to_formulae: sheet_to_formulae,
26391	sheet_to_row_object_array: sheet_to_json,
26392	sheet_get_cell: ws_get_cell_stub,
26393	book_new: book_new,
26394	book_append_sheet: book_append_sheet,
26395	book_set_sheet_visibility: book_set_sheet_visibility,
26396	cell_set_number_format: cell_set_number_format,
26397	cell_set_hyperlink: cell_set_hyperlink,
26398	cell_set_internal_link: cell_set_internal_link,
26399	cell_add_comment: cell_add_comment,
26400	sheet_set_array_formula: sheet_set_array_formula,
26401	consts: {
26402		SHEET_VISIBLE: 0,
26403		SHEET_HIDDEN: 1,
26404		SHEET_VERY_HIDDEN: 2
26405	}
26406};
26407
26408var _Readable;
26409function set_readable(R) { _Readable = R; }
26410
26411function write_csv_stream(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
26412	var stream = _Readable();
26413	var o = opts == null ? {} : opts;
26414	if(sheet == null || sheet["!ref"] == null) { stream.push(null); return stream; }
26415	var r = safe_decode_range(sheet["!ref"]);
26416	var FS = o.FS !== undefined ? o.FS : ",", fs = FS.charCodeAt(0);
26417	var RS = o.RS !== undefined ? o.RS : "\n", rs = RS.charCodeAt(0);
26418	var endregex = new RegExp((FS=="|" ? "\\|" : FS)+"+$");
26419	var row/*:?string*/ = "", cols/*:Array<string>*/ = [];
26420	o.dense = Array.isArray(sheet);
26421	var colinfo/*:Array<ColInfo>*/ = o.skipHidden && sheet["!cols"] || [];
26422	var rowinfo/*:Array<RowInfo>*/ = o.skipHidden && sheet["!rows"] || [];
26423	for(var C = r.s.c; C <= r.e.c; ++C) if (!((colinfo[C]||{}).hidden)) cols[C] = encode_col(C);
26424	var R = r.s.r;
26425	var BOM = false, w = 0;
26426	stream._read = function() {
26427		if(!BOM) { BOM = true; return stream.push("\uFEFF"); }
26428		while(R <= r.e.r) {
26429			++R;
26430			if ((rowinfo[R-1]||{}).hidden) continue;
26431			row = make_csv_row(sheet, r, R-1, cols, fs, rs, FS, o);
26432			if(row != null) {
26433				if(o.strip) row = row.replace(endregex,"");
26434				if(row || (o.blankrows !== false)) return stream.push((w++ ? RS : "") + row);
26435			}
26436		}
26437		return stream.push(null);
26438	};
26439	return stream;
26440}
26441
26442function write_html_stream(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/) {
26443	var stream = _Readable();
26444
26445	var o = opts || {};
26446	var header = o.header != null ? o.header : HTML_BEGIN;
26447	var footer = o.footer != null ? o.footer : HTML_END;
26448	stream.push(header);
26449	var r = decode_range(ws['!ref']);
26450	o.dense = Array.isArray(ws);
26451	stream.push(make_html_preamble(ws, r, o));
26452	var R = r.s.r;
26453	var end = false;
26454	stream._read = function() {
26455		if(R > r.e.r) {
26456			if(!end) { end = true; stream.push("</table>" + footer); }
26457			return stream.push(null);
26458		}
26459		while(R <= r.e.r) {
26460			stream.push(make_html_row(ws, r, R, o));
26461			++R;
26462			break;
26463		}
26464	};
26465	return stream;
26466}
26467
26468function write_json_stream(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
26469	var stream = _Readable({objectMode:true});
26470
26471	if(sheet == null || sheet["!ref"] == null) { stream.push(null); return stream; }
26472	var val = {t:'n',v:0}, header = 0, offset = 1, hdr/*:Array<any>*/ = [], v=0, vv="";
26473	var r = {s:{r:0,c:0},e:{r:0,c:0}};
26474	var o = opts || {};
26475	var range = o.range != null ? o.range : sheet["!ref"];
26476	if(o.header === 1) header = 1;
26477	else if(o.header === "A") header = 2;
26478	else if(Array.isArray(o.header)) header = 3;
26479	switch(typeof range) {
26480		case 'string': r = safe_decode_range(range); break;
26481		case 'number': r = safe_decode_range(sheet["!ref"]); r.s.r = range; break;
26482		default: r = range;
26483	}
26484	if(header > 0) offset = 0;
26485	var rr = encode_row(r.s.r);
26486	var cols/*:Array<string>*/ = [];
26487	var counter = 0;
26488	var dense = Array.isArray(sheet);
26489	var R = r.s.r, C = 0;
26490	var header_cnt = {};
26491	if(dense && !sheet[R]) sheet[R] = [];
26492	var colinfo/*:Array<ColInfo>*/ = o.skipHidden && sheet["!cols"] || [];
26493	var rowinfo/*:Array<RowInfo>*/ = o.skipHidden && sheet["!rows"] || [];
26494	for(C = r.s.c; C <= r.e.c; ++C) {
26495		if(((colinfo[C]||{}).hidden)) continue;
26496		cols[C] = encode_col(C);
26497		val = dense ? sheet[R][C] : sheet[cols[C] + rr];
26498		switch(header) {
26499			case 1: hdr[C] = C - r.s.c; break;
26500			case 2: hdr[C] = cols[C]; break;
26501			case 3: hdr[C] = o.header[C - r.s.c]; break;
26502			default:
26503				if(val == null) val = {w: "__EMPTY", t: "s"};
26504				vv = v = format_cell(val, null, o);
26505				counter = header_cnt[v] || 0;
26506				if(!counter) header_cnt[v] = 1;
26507				else {
26508					do { vv = v + "_" + (counter++); } while(header_cnt[vv]); header_cnt[v] = counter;
26509					header_cnt[vv] = 1;
26510				}
26511				hdr[C] = vv;
26512		}
26513	}
26514	R = r.s.r + offset;
26515	stream._read = function() {
26516		while(R <= r.e.r) {
26517			if ((rowinfo[R-1]||{}).hidden) continue;
26518			var row = make_json_row(sheet, r, R, cols, header, hdr, dense, o);
26519			++R;
26520			if((row.isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) {
26521				stream.push(row.row);
26522				return;
26523			}
26524		}
26525		return stream.push(null);
26526	};
26527	return stream;
26528}
26529
26530var __stream = {
26531	to_json: write_json_stream,
26532	to_html: write_html_stream,
26533	to_csv: write_csv_stream,
26534	set_readable: set_readable
26535};
26536const version = XLSX.version;
26537XLSX.parse_xlscfb = parse_xlscfb;
26538XLSX.parse_zip = parse_zip;
26539XLSX.read = readSync;
26540XLSX.readFile = readFileSync;
26541XLSX.readFileSync = readFileSync;
26542XLSX.write = writeSync;
26543XLSX.writeFile = writeFileSync;
26544XLSX.writeFileSync = writeFileSync;
26545XLSX.writeFileAsync = writeFileAsync;
26546XLSX.writeFileXLSX = writeFileSyncXLSX;
26547XLSX.utils = utils1;
26548XLSX.set_fs = set_fs;
26549XLSX.set_cptable = set_cptable;
26550XLSX.stream = __stream;
26551XLSX.SSF = SSF;
26552XLSX.CFB = CFB;
26553
26554{/* export {
26555	parse_xlscfb,
26556	parse_zip,
26557	readSync as read,
26558	readFileSync as readFile,
26559	readFileSync,
26560	writeSync as write,
26561	writeFileSync as writeFile,
26562	writeFileSync,
26563	writeFileAsync,
26564	writeSyncXLSX as writeXLSX,
26565	writeFileSyncXLSX as writeFileXLSX,
26566	utils,
26567	set_fs,
26568	set_cptable,
26569	__stream as stream,
26570	SSF,
26571	CFB
26572}; */}
26573{/* export default {
26574	parse_xlscfb,
26575	parse_zip,
26576	read: readSync,
26577	readFile: readFileSync,
26578	readFileSync,
26579	write: writeSync,
26580	writeFile: writeFileSync,
26581	writeFileSync,
26582	writeFileAsync,
26583	writeXLSX: writeSyncXLSX,
26584	writeFileXLSX: writeFileSyncXLSX,
26585	utils,
26586	set_fs,
26587	set_cptable,
26588	stream: __stream,
26589	SSF,
26590	CFB
26591} */}
26592