forked from sheetjs/sheetjs
		
	Basic AutoFilter Read/Write
- XLSX/XLSB read/write autofilter - XLS/XLML/*ODS read autofilter - Workbook Properties override via Props option - XLSB write sheet protection Issues: - fixes #472 h/t @nishthasb - fixes #478 h/t @yonatannn - see #623 h/t @jcarvin
This commit is contained in:
		
							parent
							
								
									c13170fbd5
								
							
						
					
					
						commit
						5187bc0b63
					
				
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							| @ -598,6 +598,14 @@ In addition to the base sheet keys, worksheets also add: | ||||
| | `objects`             | Edit objects                                         | | ||||
| | `scenarios`           | Edit scenarios                                       | | ||||
| 
 | ||||
| - `ws['!autofilter']`: AutoFilter object following the schema: | ||||
| 
 | ||||
| ```typescript | ||||
| type AutoFilter = { | ||||
| 	ref:string; // A-1 based range representing the AutoFilter table range | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### Chartsheet Object | ||||
| 
 | ||||
| Chartsheets are represented as standard sheets.  They are distinguished with the | ||||
| @ -655,6 +663,12 @@ if(!wb.Custprops) wb.Custprops = {}; | ||||
| wb.Custprops["Custom Property"] = "Custom Value"; | ||||
| ``` | ||||
| 
 | ||||
| Writers will process the `Props` key of the options object: | ||||
| 
 | ||||
| ```js | ||||
| /* force the Author to be "SheetJS" */ | ||||
| XLSX.write(wb, {Props:{Author:"SheetJS"}}); | ||||
| ``` | ||||
| ### Document Features | ||||
| 
 | ||||
| Even for basic features like date storage, the official Excel formats store the | ||||
| @ -935,6 +949,7 @@ The exported `write` and `writeFile` functions accept an options argument: | ||||
| | bookType    | `"xlsx"` | Type of Workbook (see below for supported formats)  | | ||||
| | sheet       |     `""` | Name of Worksheet for single-sheet formats **       | | ||||
| | compression |  `false` | Use ZIP compression for ZIP-based formats **        | | ||||
| | Props       |          | Override workbook properties when writing **        | | ||||
| 
 | ||||
| - `bookSST` is slower and more memory intensive, but has better compatibility | ||||
|   with older versions of iOS Numbers | ||||
| @ -943,6 +958,8 @@ The exported `write` and `writeFile` functions accept an options argument: | ||||
| - `cellDates` only applies to XLSX output and is not guaranteed to work with | ||||
|   third-party readers.  Excel itself does not usually write cells with type `d` | ||||
|   so non-Excel tools may ignore the data or blow up in the presence of dates. | ||||
| - `Props` is an object mirroring the workbook `Props` field.  See the table from | ||||
|   the [Workbook File Properties](#workbook-file-properties) section. | ||||
| 
 | ||||
| ### Supported Output Formats | ||||
| 
 | ||||
| @ -1450,6 +1467,7 @@ Latest test files snapshot: | ||||
| <https://github.com/SheetJS/test_files/releases/download/20170409/test_files.zip> | ||||
| 
 | ||||
| Download and unzip to the `test_files` subdirectory. | ||||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| Please consult the attached LICENSE file for details.  All rights not explicitly | ||||
|  | ||||
| @ -58,15 +58,24 @@ function cp_doit(f, g, h, o, p) { | ||||
| 	o[o.length] = (h ? writextag(f,g,h) : writetag(f,g)); | ||||
| } | ||||
| 
 | ||||
| function write_core_props(cp, opts) { | ||||
| function write_core_props(cp, _opts) { | ||||
| 	var opts = _opts || {}; | ||||
| 	var o = [XML_HEADER, CORE_PROPS_XML_ROOT], p = {}; | ||||
| 	if(!cp) return o.join(""); | ||||
| 	if(!cp && !opts.Props) return o.join(""); | ||||
| 
 | ||||
| 	if(cp) { | ||||
| 		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); | ||||
| 		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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	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); | ||||
| 
 | ||||
| 	for(var i = 0; i != CORE_PROPS.length; ++i) { var f = CORE_PROPS[i]; cp_doit(f[0], cp[f[1]], null, o, p); } | ||||
| 	for(var i = 0; i != CORE_PROPS.length; ++i) { | ||||
| 		var f = CORE_PROPS[i]; | ||||
| 		var v = opts.Props && opts.Props[f[1]] != null ? opts.Props[f[1]] : cp ? cp[f[1]] : null; | ||||
| 		if(v === true) v = "1"; | ||||
| 		else if(v === false) v = "0"; | ||||
| 		else if(typeof v == "number") v = String(v); | ||||
| 		if(v != null) cp_doit(f[0], v, null, o, p); | ||||
| 	} | ||||
| 	if(o.length>2){ o[o.length] = ('</cp:coreProperties>'); o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
|  | ||||
| @ -89,7 +89,7 @@ function write_ext_props(cp, opts)/*:string*/ { | ||||
| 		if(cp[f[1]] === undefined) return; | ||||
| 		var v; | ||||
| 		switch(f[2]) { | ||||
| 			case 'string': v = cp[f[1]]; break; | ||||
| 			case 'string': v = String(cp[f[1]]); break; | ||||
| 			case 'bool': v = cp[f[1]] ? 'true' : 'false'; break; | ||||
| 		} | ||||
| 		if(v !== undefined) o[o.length] = (W(f[0], v)); | ||||
|  | ||||
| @ -1,50 +1,61 @@ | ||||
| /* Common Name -> XLML Name */ | ||||
| var XLMLDocPropsMap = { | ||||
| 	Category: 'Category', | ||||
| 	ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ | ||||
| 	Keywords: 'Keywords', | ||||
| 	LastAuthor: 'LastAuthor', | ||||
| 	LastPrinted: 'LastPrinted', | ||||
| 	RevNumber: 'Revision', | ||||
| 	Author: 'Author', | ||||
| 	Comments: 'Description', | ||||
| 	Identifier: 'Identifier', /* NOTE: missing from schema */ | ||||
| 	Language: 'Language', /* NOTE: missing from schema */ | ||||
| 	Subject: 'Subject', | ||||
| 	Title: 'Title', | ||||
| 	CreatedDate: 'Created', | ||||
| 	ModifiedDate: 'LastSaved', | ||||
| 
 | ||||
| 	Application: 'AppName', | ||||
| 	AppVersion: 'Version', | ||||
| 	TotalTime: 'TotalTime', | ||||
| 	Manager: 'Manager', | ||||
| 	Company: 'Company' | ||||
| }; | ||||
| var evert_XLMLDPM = evert(XLMLDocPropsMap); | ||||
| 
 | ||||
| function xlml_set_prop(Props, tag/*:string*/, val) { | ||||
| 	/* TODO: Normalize the properties */ | ||||
| 	switch(tag) { | ||||
| 		case 'Description': tag = 'Comments'; break; | ||||
| 		case 'Created': tag = 'CreatedDate'; break; | ||||
| 		case 'LastSaved': tag = 'ModifiedDate'; break; | ||||
| 	} | ||||
| 	tag = evert_XLMLDPM[tag] || tag; | ||||
| 	Props[tag] = val; | ||||
| } | ||||
| 
 | ||||
| var XLMLDocumentProperties = [ | ||||
| 	['Title', 'Title'], | ||||
| 	['Subject', 'Subject'], | ||||
| 	['Author', 'Author'], | ||||
| 	['Keywords', 'Keywords'], | ||||
| 	['Comments', 'Description'], | ||||
| 	['LastAuthor', 'LastAuthor'], | ||||
| 	['CreatedDate', 'Created', 'date'], | ||||
| 	['ModifiedDate', 'LastSaved', 'date'], | ||||
| 	['Category', 'Category'], | ||||
| 	['Manager', 'Manager'], | ||||
| 	['Company', 'Company'], | ||||
| 	['AppVersion', 'Version'] | ||||
| ]; | ||||
| 
 | ||||
| /* TODO: verify */ | ||||
| function xlml_write_docprops(Props) { | ||||
| 	var T = 'DocumentProperties'; | ||||
| function xlml_write_docprops(Props, opts) { | ||||
| 	var o = []; | ||||
| 	XLMLDocumentProperties.forEach(function(p) { | ||||
| 		if(!Props[p[0]]) return; | ||||
| 		var m = Props[p[0]]; | ||||
| 	CORE_PROPS.concat(EXT_PROPS).forEach(function(p) { | ||||
| 		if(Props[p[1]] == null) return; | ||||
| 		var m = opts && opts.Props && opts.Props[p[1]] != null ? opts.Props[p[1]] : Props[p[1]]; | ||||
| 		switch(p[2]) { | ||||
| 			case 'date': m = new Date(m).toISOString(); break; | ||||
| 		} | ||||
| 		o.push(writetag(p[1], m)); | ||||
| 		if(typeof m == 'number') m = String(m); | ||||
| 		else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } | ||||
| 		else if(m instanceof Date) m = new Date(m).toISOString(); | ||||
| 		o.push(writetag(XLMLDocPropsMap[p[1]] || p[1], m)); | ||||
| 	}); | ||||
| 	return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + '</' + T + '>'; | ||||
| 	return writextag('DocumentProperties', o.join(""), {xmlns:XLMLNS.o }); | ||||
| } | ||||
| function xlml_write_custprops(Props, Custprops) { | ||||
| function xlml_write_custprops(Props, Custprops, opts) { | ||||
| 	var BLACKLIST = ["Worksheets","SheetNames"]; | ||||
| 	var T = 'CustomDocumentProperties'; | ||||
| 	var o = []; | ||||
| 	if(Props) keys(Props).forEach(function(k) { | ||||
| 		/*:: if(!Props) return; */ | ||||
| 		if(!Props.hasOwnProperty(k)) return; | ||||
| 		for(var i = 0; i < XLMLDocumentProperties.length; ++i) | ||||
| 			if(k == XLMLDocumentProperties[i][0]) return; | ||||
| 		for(var i = 0; i < CORE_PROPS.length; ++i) if(k == CORE_PROPS[i][1]) return; | ||||
| 		for(i = 0; i < EXT_PROPS.length; ++i) if(k == EXT_PROPS[i][1]) return; | ||||
| 		for(i = 0; i < BLACKLIST.length; ++i) if(k == BLACKLIST[i]) return; | ||||
| 
 | ||||
| 		var m = Props[k]; | ||||
| 		var t = "string"; | ||||
| 		if(typeof m == 'number') { t = "float"; m = String(m); } | ||||
|  | ||||
| @ -452,9 +452,10 @@ function parse_Lbl(blob, length, opts) { | ||||
| 	var chKey = blob.read_shift(1); | ||||
| 	var cch = blob.read_shift(1); | ||||
| 	var cce = blob.read_shift(opts && opts.biff == 2 ? 1 : 2); | ||||
| 	var itab = 0; | ||||
| 	if(!opts || opts.biff >= 5) { | ||||
| 		blob.l += 2; | ||||
| 		var itab = blob.read_shift(2); | ||||
| 		itab = blob.read_shift(2); | ||||
| 		blob.l += 4; | ||||
| 	} | ||||
| 	var name = parse_XLUnicodeStringNoCch(blob, cch, opts); | ||||
| @ -463,6 +464,7 @@ function parse_Lbl(blob, length, opts) { | ||||
| 	return { | ||||
| 		chKey: chKey, | ||||
| 		Name: name, | ||||
| 		itab: itab, | ||||
| 		rgce: rgce | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| @ -19,3 +19,9 @@ function csf_to_ods_formula(f/*:string*/)/*:string*/ { | ||||
| 	/* TODO: something other than this */ | ||||
| 	return o.replace(/;/g, "|").replace(/,/g,";"); | ||||
| } | ||||
| 
 | ||||
| function ods_to_csf_range_3D(r/*:string*/) { | ||||
| 	var a = r.split(":"); | ||||
| 	var s = a[0].split(".")[0]; | ||||
| 	return [s, a[0].split(".")[1] + ":" + a[1].split(".")[1]]; | ||||
| } | ||||
|  | ||||
| @ -7,43 +7,54 @@ var sheetdataregex = /<(?:\w+:)?sheetData>([^\u2603]*)<\/(?:\w+:)?sheetData>/; | ||||
| var hlinkregex = /<(?:\w:)?hyperlink [^>]*>/mg; | ||||
| var dimregex = /"(\w*:\w*)"/; | ||||
| var colregex = /<(?:\w:)?col[^>]*[\/]?>/g; | ||||
| var afregex = /<(?:\w:)?autoFilter[^>]*([\/]|>([^\u2603]*)<\/(?:\w:)?autoFilter)>/g; | ||||
| /* 18.3 Worksheets */ | ||||
| function parse_ws_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 	if(!data) return data; | ||||
| 	if(DENSE != null && opts.dense == null) opts.dense = DENSE; | ||||
| 
 | ||||
| 	/* 18.3.1.99 worksheet CT_Worksheet */ | ||||
| 	var s = opts.dense ? ([]/*:any*/) : ({}/*:any*/); | ||||
| 	var refguess/*:Range*/ = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }/*:any*/); | ||||
| 
 | ||||
| 	var data1 = "", data2 = ""; | ||||
| 	var mtch=data.match(sheetdataregex); | ||||
| 	if(mtch) { | ||||
| 		data1 = data.substr(0, mtch.index); | ||||
| 		data2 = data.substr(mtch.index + mtch[0].length); | ||||
| 	} else data1 = data2 = data; | ||||
| 
 | ||||
| 	/* 18.3.1.35 dimension CT_SheetDimension ? */ | ||||
| 	// $FlowIgnore
 | ||||
| 	var ridx = (data.match(/<(?:\w*:)?dimension/)||{index:-1}).index; | ||||
| 	var ridx = (data1.match(/<(?:\w*:)?dimension/)||{index:-1}).index; | ||||
| 	if(ridx > 0) { | ||||
| 		var ref = data.substr(ridx,50).match(dimregex); | ||||
| 		if(ref != null) parse_ws_xml_dim(s, ref[1]); | ||||
| 		var ref = data1.substr(ridx,50).match(dimregex); | ||||
| 		if(ref) parse_ws_xml_dim(s, ref[1]); | ||||
| 	} | ||||
| 
 | ||||
| 	/* 18.3.1.55 mergeCells CT_MergeCells */ | ||||
| 	var mergecells = []; | ||||
| 	var merges = data.match(mergecregex); | ||||
| 	if(merges) for(ridx = 0; ridx != merges.length; ++ridx) | ||||
| 		mergecells[ridx] = safe_decode_range(merges[ridx].substr(merges[ridx].indexOf("\"")+1)); | ||||
| 
 | ||||
| 	/* 18.3.1.17 cols CT_Cols */ | ||||
| 	var columns = []; | ||||
| 	if(opts.cellStyles) { | ||||
| 		/* 18.3.1.13 col CT_Col */ | ||||
| 		var cols = data.match(colregex); | ||||
| 		var cols = data1.match(colregex); | ||||
| 		if(cols) parse_ws_xml_cols(columns, cols); | ||||
| 	} | ||||
| 
 | ||||
| 	var refguess/*:Range*/ = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }/*:any*/); | ||||
| 
 | ||||
| 	/* 18.3.1.80 sheetData CT_SheetData ? */ | ||||
| 	var mtch=data.match(sheetdataregex); | ||||
| 	if(mtch) parse_ws_xml_data(mtch[1], s, opts, refguess, themes, styles); | ||||
| 
 | ||||
| 	/* 18.3.1.2  autoFilter CT_AutoFilter */ | ||||
| 	var afilter = data2.match(afregex); | ||||
| 	if(afilter) s['!autofilter'] = parse_ws_xml_autofilter(afilter[0]); | ||||
| 
 | ||||
| 	/* 18.3.1.55 mergeCells CT_MergeCells */ | ||||
| 	var mergecells = []; | ||||
| 	var merges = data2.match(mergecregex); | ||||
| 	if(merges) for(ridx = 0; ridx != merges.length; ++ridx) | ||||
| 		mergecells[ridx] = safe_decode_range(merges[ridx].substr(merges[ridx].indexOf("\"")+1)); | ||||
| 
 | ||||
| 	/* 18.3.1.48 hyperlinks CT_Hyperlinks */ | ||||
| 	var hlink = data.match(hlinkregex); | ||||
| 	var hlink = data2.match(hlinkregex); | ||||
| 	if(hlink) parse_ws_xml_hlinks(s, hlink, rels); | ||||
| 
 | ||||
| 	if(!s["!ref"] && refguess.e.c >= refguess.s.c && refguess.e.r >= refguess.s.r) s["!ref"] = encode_range(refguess); | ||||
| @ -142,6 +153,14 @@ function write_ws_xml_cols(ws, cols)/*:string*/ { | ||||
| 	return o.join(""); | ||||
| } | ||||
| 
 | ||||
| function parse_ws_xml_autofilter(data) { | ||||
| 	var o = { ref: (data.match(/ref="([^"]*)"/)||[])[1]}; | ||||
| 	return o; | ||||
| } | ||||
| function write_ws_xml_autofilter(data)/*:string*/ { | ||||
| 	return writextag("autoFilter", null, {ref:data.ref}); | ||||
| } | ||||
| 
 | ||||
| function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { | ||||
| 	if(cell.v === undefined && cell.f === undefined || cell.t === 'z') return ""; | ||||
| 	var vv = ""; | ||||
| @ -261,7 +280,6 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 							p.F = arrayf[i][1]; | ||||
| 			} | ||||
| 
 | ||||
| 			/* SCHEMA IS ACTUALLY INCORRECT HERE.  IF A CELL HAS NO T, EMIT "" */ | ||||
| 			if(tag.t === undefined && p.v === undefined) { | ||||
| 				if(!opts.sheetStubs) continue; | ||||
| 				p.t = "z"; | ||||
| @ -293,7 +311,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 					cref = d.match(isregex); | ||||
| 					p.t = 's'; | ||||
| 					if(cref != null && (sstr = parse_si(cref[1]))) p.v = sstr.t; else p.v = ""; | ||||
| 					break; // inline string
 | ||||
| 					break; | ||||
| 				case 'b': p.v = parsexmlbool(p.v); break; | ||||
| 				case 'd': | ||||
| 					if(!opts.cellDates) { p.v = datenum(parseDate(p.v)); p.t = 'n'; } | ||||
| @ -361,19 +379,23 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 	var o = [XML_HEADER, WS_XML_ROOT]; | ||||
| 	var s = wb.SheetNames[idx], sidx = 0, rdata = ""; | ||||
| 	var ws = wb.Sheets[s]; | ||||
| 	if(ws === undefined) ws = {}; | ||||
| 	var ref = ws['!ref']; if(ref === undefined) ref = 'A1'; | ||||
| 	if(ws == null) ws = {}; | ||||
| 	var ref = ws['!ref']; if(ref == null) ref = 'A1'; | ||||
| 	if(!rels) rels = {}; | ||||
| 	ws['!comments'] = []; | ||||
| 	ws['!drawing'] = []; | ||||
| 
 | ||||
| 	o[o.length] = (writextag('sheetPr', null, {'codeName': escapexml(wb.SheetNames[idx])})); | ||||
| 
 | ||||
| 	o[o.length] = (writextag('dimension', null, {'ref': ref})); | ||||
| 
 | ||||
| 	/* sheetViews */ | ||||
| 
 | ||||
| 	/* TODO: store in WB, process styles */ | ||||
| 	if(opts.sheetFormat) o[o.length] = (writextag('sheetFormatPr', null, {defaultRowHeight:opts.sheetFormat.defaultRowHeight||'16', baseColWidth:opts.sheetFormat.baseColWidth||'10' })); | ||||
| 
 | ||||
| 	if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); | ||||
| 	if(ws['!cols'] != null && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); | ||||
| 
 | ||||
| 	o[sidx = o.length] = '<sheetData/>'; | ||||
| 	ws['!links'] = []; | ||||
| 	if(ws['!ref'] != null) { | ||||
| @ -382,10 +404,25 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 	} | ||||
| 	if(o.length>sidx+1) { o[o.length] = ('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); } | ||||
| 
 | ||||
| 	/* sheetCalcPr */ | ||||
| 
 | ||||
| 	if(ws['!protect'] != null) o[o.length] = write_ws_xml_protection(ws['!protect']); | ||||
| 
 | ||||
| 	/* protectedRanges */ | ||||
| 	/* scenarios */ | ||||
| 
 | ||||
| 	if(ws['!autofilter'] != null) o[o.length] = write_ws_xml_autofilter(ws['!autofilter']); | ||||
| 
 | ||||
| 	/* sortState */ | ||||
| 	/* dataConsolidate */ | ||||
| 	/* customSheetViews */ | ||||
| 
 | ||||
| 	if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges'])); | ||||
| 
 | ||||
| 	/* phoneticPr */ | ||||
| 	/* conditionalFormatting */ | ||||
| 	/* dataValidations */ | ||||
| 
 | ||||
| 	var relc = -1, rel, rId = -1; | ||||
| 	if(ws['!links'].length > 0) { | ||||
| 		o[o.length] = "<hyperlinks>"; | ||||
| @ -401,21 +438,41 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 	} | ||||
| 	delete ws['!links']; | ||||
| 
 | ||||
| 	/* printOptions */ | ||||
| 	/* pageMargins */ | ||||
| 	/* pageSetup */ | ||||
| 
 | ||||
| 	var hfidx = o.length; | ||||
| 	o[o.length] = ""; | ||||
| 
 | ||||
| 	/* rowBreaks */ | ||||
| 	/* colBreaks */ | ||||
| 	/* customProperties */ | ||||
| 	/* cellWatches */ | ||||
| 	/* ignoredErrors */ | ||||
| 	/* smartTags */ | ||||
| 
 | ||||
| 	if(ws['!drawing'].length > 0) { | ||||
| 		rId = add_rels(rels, -1, "../drawings/drawing" + (idx+1) + ".xml", RELS.DRAW); | ||||
| 		ws['!drawing'].rid = rId; | ||||
| 		o[o.length] = writextag("drawing", null, {"r:id":"rId" + rId}); | ||||
| 	} | ||||
| 	else delete ws['!drawing']; | ||||
| 
 | ||||
| 	if(ws['!comments'].length > 0) { | ||||
| 		rId = add_rels(rels, -1, "../drawings/vmlDrawing" + (idx+1) + ".vml", RELS.VML); | ||||
| 		o[o.length] = writextag("legacyDrawing", null, {"r:id":"rId" + rId}); | ||||
| 		ws['!legacy'] = rId; | ||||
| 	} | ||||
| 
 | ||||
| 	/* drawingHF */ | ||||
| 	/* picture */ | ||||
| 	/* oleObjects */ | ||||
| 	/* controls */ | ||||
| 	/* webPublishItems */ | ||||
| 	/* tableParts */ | ||||
| 	/* extList */ | ||||
| 
 | ||||
| 	if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
|  | ||||
| @ -289,6 +289,35 @@ function write_BrtColInfo(C/*:number*/, col, o) { | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.740 BrtSheetProtection */ | ||||
| function write_BrtSheetProtection(sp, o) { | ||||
| 	if(o == null) o = new_buf(16*4+2); | ||||
| 	o.write_shift(2, sp.password ? crypto_CreatePasswordVerifier_Method1(sp.password) : 0); | ||||
| 	o.write_shift(4, 1); // this record should not be written if no protection
 | ||||
| 	[ | ||||
| 		["objects",             false], // fObjects
 | ||||
| 		["scenarios",           false], // fScenarios
 | ||||
| 		["formatCells",          true], // fFormatCells
 | ||||
| 		["formatColumns",        true], // fFormatColumns
 | ||||
| 		["formatRows",           true], // fFormatRows
 | ||||
| 		["insertColumns",        true], // fInsertColumns
 | ||||
| 		["insertRows",           true], // fInsertRows
 | ||||
| 		["insertHyperlinks",     true], // fInsertHyperlinks
 | ||||
| 		["deleteColumns",        true], // fDeleteColumns
 | ||||
| 		["deleteRows",           true], // fDeleteRows
 | ||||
| 		["selectLockedCells",   false], // fSelLockedCells
 | ||||
| 		["sort",                 true], // fSort
 | ||||
| 		["autoFilter",           true], // fAutoFilter
 | ||||
| 		["pivotTables",          true], // fPivotTables
 | ||||
| 		["selectUnlockedCells", false]  // fSelUnlockedCells
 | ||||
| 	].forEach(function(n) { | ||||
| 		o.write_shift(4, 1); | ||||
| 		if(!n[1]) o.write_shift(4, sp[n] != null && sp[n] ? 1 : 0); | ||||
| 		else     o.write_shift(4, sp[n] != null && !sp[n] ? 0 : 1); | ||||
| 	}); | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.1.7.61 Worksheet */ | ||||
| function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 	if(!data) return data; | ||||
| @ -434,6 +463,10 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 				} | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x00A1: /* 'BrtBeginAFilter' */ | ||||
| 				s['!autofilter'] = { ref:encode_range(val) }; | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x00AF: /* 'BrtAFilterDateGroupItem' */ | ||||
| 			case 0x0284: /* 'BrtActiveX' */ | ||||
| 			case 0x0271: /* 'BrtBigName' */ | ||||
| @ -628,6 +661,21 @@ function write_LEGACYDRAWING(ba, ws/*:Worksheet*/, idx/*:number*/, rels) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function write_AUTOFILTER(ba, ws) { | ||||
| 	if(!ws['!autofilter']) return; | ||||
| 	write_record(ba, "BrtBeginAFilter", write_UncheckedRfX(decode_range(ws['!autofilter'].ref))); | ||||
| 	/* *FILTERCOLUMN */ | ||||
| 	/* [SORTSTATE] */ | ||||
| 	/* BrtEndAFilter */ | ||||
| 	write_record(ba, "BrtEndAFilter"); | ||||
| } | ||||
| 
 | ||||
| function write_SHEETPROTECT(ba, ws) { | ||||
| 	if(!ws['!protect']) return; | ||||
| 	/* [BrtSheetProtectionIso] */ | ||||
| 	write_record(ba, "BrtSheetProtection", write_BrtSheetProtection(ws['!protect'])); | ||||
| } | ||||
| 
 | ||||
| function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { | ||||
| 	var ba = buf_array(); | ||||
| 	var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {}; | ||||
| @ -643,10 +691,10 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { | ||||
| 	write_COLINFOS(ba, ws, idx, opts, wb); | ||||
| 	write_CELLTABLE(ba, ws, idx, opts, wb); | ||||
| 	/* [BrtSheetCalcProp] */ | ||||
| 	/* [[BrtSheetProtectionIso] BrtSheetProtection] */ | ||||
| 	write_SHEETPROTECT(ba, ws); | ||||
| 	/* *([BrtRangeProtectionIso] BrtRangeProtection) */ | ||||
| 	/* [SCENMAN] */ | ||||
| 	/* [AUTOFILTER] */ | ||||
| 	write_AUTOFILTER(ba, ws); | ||||
| 	/* [SORTSTATE] */ | ||||
| 	/* [DCON] */ | ||||
| 	/* [USERSHVIEWS] */ | ||||
|  | ||||
| @ -159,7 +159,15 @@ function safe1904(wb/*:Workbook*/)/*:string*/ { | ||||
| function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ { | ||||
| 	var o = [XML_HEADER]; | ||||
| 	o[o.length] = WB_XML_ROOT; | ||||
| 
 | ||||
| 	/* fileVersion */ | ||||
| 	/* fileSharing */ | ||||
| 
 | ||||
| 	o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb), codeName:"ThisWorkbook"})); | ||||
| 
 | ||||
| 	/* workbookProtection */ | ||||
| 	/* bookViews */ | ||||
| 
 | ||||
| 	o[o.length] = "<sheets>"; | ||||
| 	var sheets = wb.Workbook && wb.Workbook.Sheets || []; | ||||
| 	for(var i = 0; i != wb.SheetNames.length; ++i) { | ||||
| @ -173,6 +181,21 @@ function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ { | ||||
| 		o[o.length] = (writextag('sheet',null,sht)); | ||||
| 	} | ||||
| 	o[o.length] = "</sheets>"; | ||||
| 
 | ||||
| 	/* functionGroups */ | ||||
| 	/* externalReferences */ | ||||
| 	/* definedNames */ | ||||
| 	/* calcPr */ | ||||
| 	/* oleSize */ | ||||
| 	/* customWorkbookViews */ | ||||
| 	/* pivotCaches */ | ||||
| 	/* smartTagPr */ | ||||
| 	/* smartTagTypes */ | ||||
| 	/* webPublishing */ | ||||
| 	/* fileRecoveryPr */ | ||||
| 	/* webPublishObjects */ | ||||
| 	/* extLst */ | ||||
| 
 | ||||
| 	if(o.length>2){ o[o.length] = '</workbook>'; o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
|  | ||||
| @ -351,6 +351,9 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 		case 'TotalTime': | ||||
| 		case 'HyperlinkBase': | ||||
| 		case 'Manager': | ||||
| 		case 'ContentStatus': | ||||
| 		case 'Identifier': | ||||
| 		case 'Language': | ||||
| 			if(Rn[0].slice(-2) === "/>") break; | ||||
| 			else if(Rn[1]==="/") xlml_set_prop(Props, Rn[3], str.slice(pidx, Rn.index)); | ||||
| 			else pidx = Rn.index + Rn[0].length; | ||||
| @ -375,6 +378,15 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 			} | ||||
| 			break; | ||||
| 
 | ||||
| 		case 'AutoFilter': | ||||
| 			if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));} | ||||
| 			else if(Rn[0].charAt(Rn[0].length-2) !== '/') { | ||||
| 				var AutoFilter = xlml_parsexmltag(Rn[0]); | ||||
| 				cursheet['!autofilter'] = { ref:rc_to_a1(AutoFilter.Range).replace(/\$/g,"") }; | ||||
| 				state.push([Rn[3], true]); | ||||
| 			} | ||||
| 			break; | ||||
| 
 | ||||
| 		case 'Name': break; | ||||
| 
 | ||||
| 		case 'ComponentOptions': | ||||
| @ -388,7 +400,6 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 		case 'PageBreaks': | ||||
| 		case 'QueryTable': | ||||
| 		case 'DataValidation': | ||||
| 		case 'AutoFilter': | ||||
| 		case 'Sorting': | ||||
| 		case 'Schema': | ||||
| 		case 'data': | ||||
| @ -783,7 +794,7 @@ function parse_xlml(data, opts)/*:Workbook*/ { | ||||
| function write_props_xlml(wb, opts) { | ||||
| 	var o = []; | ||||
| 	/* DocumentProperties */ | ||||
| 	if(wb.Props) o.push(xlml_write_docprops(wb.Props)); | ||||
| 	if(wb.Props) o.push(xlml_write_docprops(wb.Props, opts)); | ||||
| 	/* CustomDocumentProperties */ | ||||
| 	if(wb.Custprops) o.push(xlml_write_custprops(wb.Props, wb.Custprops)); | ||||
| 	return o.join(""); | ||||
|  | ||||
| @ -177,6 +177,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { | ||||
| 	var last_Rn = ''; | ||||
| 	var file_depth = 0; /* TODO: make a real stack */ | ||||
| 	var BIFF2Fmt = 0; | ||||
| 	var FilterDatabases = []; /* TODO: sort out supbooks and process elsewhere */ | ||||
| 
 | ||||
| 	/* explicit override for some broken writers */ | ||||
| 	opts.codepage = 1200; | ||||
| @ -252,7 +253,14 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { | ||||
| 				case 'SupBook': supbooks[++sbc] = [val]; sbci = 0; break; | ||||
| 				case 'ExternName': supbooks[sbc][++sbci] = val; break; | ||||
| 				case 'Index': break; // TODO
 | ||||
| 				case 'Lbl': supbooks[0][++sbcli] = val; break; | ||||
| 				case 'Lbl': | ||||
| 					supbooks[0][++sbcli] = val; // TODO: local formula storage in stringify_formula
 | ||||
| 					if(!supbooks[val.itab]) supbooks[val.itab] = []; | ||||
| 					supbooks[val.itab].push(val); | ||||
| 					if(val.Name == "\r" && val.itab > 0) | ||||
| 						if(val.rgce && val.rgce[0] && val.rgce[0][0] && val.rgce[0][0][0] == 'PtgArea3d') | ||||
| 							FilterDatabases[val.itab - 1] = { ref: encode_range(val.rgce[0][0][1][2]) }; | ||||
| 					break; | ||||
| 				case 'ExternSheet': supbooks[sbc] = supbooks[sbc].concat(val); sbci += val.length; break; | ||||
| 
 | ||||
| 				case 'Protect': out["!protect"] = val; break; /* for sheet or book */ | ||||
| @ -712,6 +720,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { | ||||
| 	wb.Directory=sheetnamesraw; | ||||
| 	wb.SheetNames=sheetnamesraw; | ||||
| 	if(!options.bookSheets) wb.Sheets=Sheets; | ||||
| 	if(wb.Sheets) FilterDatabases.forEach(function(r,i) { wb.Sheets[wb.SheetNames[i]]['!autofilter'] = r; }); | ||||
| 	wb.Preamble=Preamble; | ||||
| 	wb.Strings = sst; | ||||
| 	wb.SSF = SSF.get_table(); | ||||
|  | ||||
| @ -94,7 +94,7 @@ var XLSBRecordEnum = { | ||||
| 	/*::[*/0x009E/*::]*/: { n:"BrtBookView", f:parsenoop }, | ||||
| 	/*::[*/0x009F/*::]*/: { n:"BrtBeginSst", f:parse_BrtBeginSst }, | ||||
| 	/*::[*/0x00A0/*::]*/: { n:"BrtEndSst", f:parsenoop }, | ||||
| 	/*::[*/0x00A1/*::]*/: { n:"BrtBeginAFilter", f:parsenoop }, | ||||
| 	/*::[*/0x00A1/*::]*/: { n:"BrtBeginAFilter", f:parse_UncheckedRfX }, | ||||
| 	/*::[*/0x00A2/*::]*/: { n:"BrtEndAFilter", f:parsenoop }, | ||||
| 	/*::[*/0x00A3/*::]*/: { n:"BrtBeginFilterColumn", f:parsenoop }, | ||||
| 	/*::[*/0x00A4/*::]*/: { n:"BrtEndFilterColumn", f:parsenoop }, | ||||
|  | ||||
| @ -306,6 +306,15 @@ var parse_content_xml = (function() { | ||||
| 				if(Rn[1]==='/') textp = (textp.length > 0 ? textp + "\n" : "") + parse_text_p(str.slice(textpidx,Rn.index), textptag); | ||||
| 				else { textptag = parsexmltag(Rn[0], false); textpidx = Rn.index + Rn[0].length; } | ||||
| 				break; // <text:p>
 | ||||
| 
 | ||||
| 			case 'database-range': // 9.4.15 <table:database-range>
 | ||||
| 				if(Rn[1]==='/') break; | ||||
| 				try { | ||||
| 					var AutoFilter = ods_to_csf_range_3D(parsexmltag(Rn[0])['target-range-address']); | ||||
| 					Sheets[AutoFilter[0]]['!autofilter'] = { ref: AutoFilter[1] }; | ||||
| 				} catch(e) { } | ||||
| 				break; | ||||
| 
 | ||||
| 			case 's': break; // <text:s>
 | ||||
| 			case 'date': break; // <*:date>
 | ||||
| 
 | ||||
| @ -320,7 +329,6 @@ var parse_content_xml = (function() { | ||||
| 			case 'content-validation': break; // 9.4.5 <table:
 | ||||
| 			case 'error-message': break; // 9.4.7 <table:
 | ||||
| 			case 'database-ranges': break; // 9.4.14 <table:database-ranges>
 | ||||
| 			case 'database-range': break; // 9.4.15 <table:database-range>
 | ||||
| 			case 'filter': break; // 9.5.2 <table:filter>
 | ||||
| 			case 'filter-and': break; // 9.5.3 <table:filter-and>
 | ||||
| 			case 'filter-or': break; // 9.5.4 <table:filter-or>
 | ||||
|  | ||||
| @ -35,6 +35,14 @@ In addition to the base sheet keys, worksheets also add: | ||||
| | `objects`             | Edit objects                                         | | ||||
| | `scenarios`           | Edit scenarios                                       | | ||||
| 
 | ||||
| - `ws['!autofilter']`: AutoFilter object following the schema: | ||||
| 
 | ||||
| ```typescript | ||||
| type AutoFilter = { | ||||
| 	ref:string; // A-1 based range representing the AutoFilter table range | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### Chartsheet Object | ||||
| 
 | ||||
| Chartsheets are represented as standard sheets.  They are distinguished with the | ||||
|  | ||||
| @ -30,3 +30,9 @@ if(!wb.Custprops) wb.Custprops = {}; | ||||
| wb.Custprops["Custom Property"] = "Custom Value"; | ||||
| ``` | ||||
| 
 | ||||
| Writers will process the `Props` key of the options object: | ||||
| 
 | ||||
| ```js | ||||
| /* force the Author to be "SheetJS" */ | ||||
| XLSX.write(wb, {Props:{Author:"SheetJS"}}); | ||||
| ``` | ||||
|  | ||||
| @ -10,6 +10,7 @@ The exported `write` and `writeFile` functions accept an options argument: | ||||
| | bookType    | `"xlsx"` | Type of Workbook (see below for supported formats)  | | ||||
| | sheet       |     `""` | Name of Worksheet for single-sheet formats **       | | ||||
| | compression |  `false` | Use ZIP compression for ZIP-based formats **        | | ||||
| | Props       |          | Override workbook properties when writing **        | | ||||
| 
 | ||||
| - `bookSST` is slower and more memory intensive, but has better compatibility | ||||
|   with older versions of iOS Numbers | ||||
| @ -18,6 +19,8 @@ The exported `write` and `writeFile` functions accept an options argument: | ||||
| - `cellDates` only applies to XLSX output and is not guaranteed to work with | ||||
|   third-party readers.  Excel itself does not usually write cells with type `d` | ||||
|   so non-Excel tools may ignore the data or blow up in the presence of dates. | ||||
| - `Props` is an object mirroring the workbook `Props` field.  See the table from | ||||
|   the [Workbook File Properties](#workbook-file-properties) section. | ||||
| 
 | ||||
| ### Supported Output Formats | ||||
| 
 | ||||
|  | ||||
| @ -45,3 +45,4 @@ Latest test files snapshot: | ||||
| <https://github.com/SheetJS/test_files/releases/download/20170409/test_files.zip> | ||||
| 
 | ||||
| Download and unzip to the `test_files` subdirectory. | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										63
									
								
								test.js
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										63
									
								
								test.js
									
									
									
									
									
								
							| @ -32,6 +32,11 @@ function fixjson(x) { return x.replace(/[\r\n]+$/,""); } | ||||
| var dir = "./test_files/"; | ||||
| 
 | ||||
| var paths = { | ||||
| 	afxls:  dir + 'AutoFilter.xls', | ||||
| 	afxml:  dir + 'AutoFilter.xml', | ||||
| 	afods:  dir + 'AutoFilter.ods', | ||||
| 	afxlsx:  dir + 'AutoFilter.xlsx', | ||||
| 	afxlsb:  dir + 'AutoFilter.xlsb', | ||||
| 	cpxls:  dir + 'custom_properties.xls', | ||||
| 	cpxml:  dir + 'custom_properties.xls.xml', | ||||
| 	cpxlsx:  dir + 'custom_properties.xlsx', | ||||
| @ -888,6 +893,23 @@ describe('parse features', function() { | ||||
| 		}); }); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('auto filter', function() { | ||||
| 		[ | ||||
| 			['xlsx', paths.afxlsx], | ||||
| 			['xlsb', paths.afxlsb], | ||||
| 			['xls', paths.afxls], | ||||
| 			['xlml', paths.afxml], | ||||
| 			['ods', paths.afods] | ||||
| 		].forEach(function(m) { it(m[0], function() { | ||||
| 			var wb = X.readFile(m[1]); | ||||
| 			assert(wb.Sheets[wb.SheetNames[0]]['!autofilter'] == null); | ||||
| 			for(var i = 1; i < wb.SheetNames.length; ++i) { | ||||
| 				assert(wb.Sheets[wb.SheetNames[i]]['!autofilter'] != null); | ||||
| 				assert.equal(wb.Sheets[wb.SheetNames[i]]['!autofilter'].ref,"A1:E22"); | ||||
| 			} | ||||
| 		}); }); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('should correctly handle styles', function() { | ||||
| 		var wsxls, wsxlsx, rn, rn2; | ||||
| 		var bef = (function() { | ||||
| @ -966,6 +988,47 @@ describe('parse features', function() { | ||||
| 	}); | ||||
| }); | ||||
| 
 | ||||
| describe('write features', function() { | ||||
| 	describe('props', function() { | ||||
| 		describe('core', function() { | ||||
| 			var ws, baseprops; | ||||
| 			var bef = (function() { | ||||
| 				X = require(modp); | ||||
| 				ws = X.utils.aoa_to_sheet([["a","b","c"],[1,2,3]]); | ||||
| 				baseprops = { | ||||
| 					Category: "C4tegory", | ||||
| 					ContentStatus: "C0ntentStatus", | ||||
| 					Keywords: "K3ywords", | ||||
| 					LastAuthor: "L4stAuthor", | ||||
| 					LastPrinted: "L4stPrinted", | ||||
| 					RevNumber: 6969, | ||||
| 					AppVersion: 69, | ||||
| 					Author: "4uth0r", | ||||
| 					Comments: "C0mments", | ||||
| 					Identifier: "1d", | ||||
| 					Language: "L4nguage", | ||||
| 					Subject: "Subj3ct", | ||||
| 					Title: "T1tle" | ||||
| 				} | ||||
| 			}); | ||||
| 			if(typeof before != 'undefined') before(bef); | ||||
| 			else it('before', bef); | ||||
| 			['xlml', 'xlsx', 'xlsb'].forEach(function(w) { it(w, function() { | ||||
| 				wb = { | ||||
| 					Props: {}, | ||||
| 					SheetNames: ["Sheet1"], | ||||
| 					Sheets: {Sheet1: ws} | ||||
| 				}; | ||||
| 				Object.keys(baseprops).forEach(function(k) { wb.Props[k] = baseprops[k]; }); | ||||
| 				var wb2 = X.read(X.write(wb, {bookType:w, type:"buffer"}), {type:"buffer"}); | ||||
| 				Object.keys(baseprops).forEach(function(k) { assert.equal(baseprops[k], wb2.Props[k]); }); | ||||
| 				var wb3 = X.read(X.write(wb2, {bookType:w, type:"buffer", Props: {Author:"SheetJS"}}), {type:"buffer"}); | ||||
| 				assert.equal("SheetJS", wb3.Props.Author); | ||||
| 			}); }); | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
| 
 | ||||
| function seq(end, start) { | ||||
| 	var s = start || 0; | ||||
| 	var o = new Array(end - s); | ||||
|  | ||||
| @ -41,7 +41,7 @@ if(typeof XLSX === "undefined") { try { XLSX = require('./'); } catch(e) { XLSX | ||||
| var wb = { SheetNames: [], Sheets: {} }; | ||||
| 
 | ||||
| /* convert an array of arrays in JS to a CSF spreadsheet */ | ||||
| var ws = XLSX.utils.aoa_to_sheet(data, {cellDates:true}); | ||||
| var ws = XLSX.utils.aoa_to_sheet(data, {cellDates:true, dense:false}); | ||||
| 
 | ||||
| /* TEST: add worksheet to workbook */ | ||||
| wb.SheetNames.push(ws_name); | ||||
|  | ||||
							
								
								
									
										312
									
								
								xlsx.flow.js
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										312
									
								
								xlsx.flow.js
									
									
									
									
									
								
							| @ -3040,15 +3040,24 @@ function cp_doit(f, g, h, o, p) { | ||||
| 	o[o.length] = (h ? writextag(f,g,h) : writetag(f,g)); | ||||
| } | ||||
| 
 | ||||
| function write_core_props(cp, opts) { | ||||
| function write_core_props(cp, _opts) { | ||||
| 	var opts = _opts || {}; | ||||
| 	var o = [XML_HEADER, CORE_PROPS_XML_ROOT], p = {}; | ||||
| 	if(!cp) return o.join(""); | ||||
| 	if(!cp && !opts.Props) return o.join(""); | ||||
| 
 | ||||
| 	if(cp) { | ||||
| 		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); | ||||
| 		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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	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); | ||||
| 
 | ||||
| 	for(var i = 0; i != CORE_PROPS.length; ++i) { var f = CORE_PROPS[i]; cp_doit(f[0], cp[f[1]], null, o, p); } | ||||
| 	for(var i = 0; i != CORE_PROPS.length; ++i) { | ||||
| 		var f = CORE_PROPS[i]; | ||||
| 		var v = opts.Props && opts.Props[f[1]] != null ? opts.Props[f[1]] : cp ? cp[f[1]] : null; | ||||
| 		if(v === true) v = "1"; | ||||
| 		else if(v === false) v = "0"; | ||||
| 		else if(typeof v == "number") v = String(v); | ||||
| 		if(v != null) cp_doit(f[0], v, null, o, p); | ||||
| 	} | ||||
| 	if(o.length>2){ o[o.length] = ('</cp:coreProperties>'); o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
| @ -3143,7 +3152,7 @@ function write_ext_props(cp, opts)/*:string*/ { | ||||
| 		if(cp[f[1]] === undefined) return; | ||||
| 		var v; | ||||
| 		switch(f[2]) { | ||||
| 			case 'string': v = cp[f[1]]; break; | ||||
| 			case 'string': v = String(cp[f[1]]); break; | ||||
| 			case 'bool': v = cp[f[1]] ? 'true' : 'false'; break; | ||||
| 		} | ||||
| 		if(v !== undefined) o[o.length] = (W(f[0], v)); | ||||
| @ -3223,53 +3232,64 @@ function write_cust_props(cp, opts)/*:string*/ { | ||||
| 	if(o.length>2){ o[o.length] = '</Properties>'; o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
| /* Common Name -> XLML Name */ | ||||
| var XLMLDocPropsMap = { | ||||
| 	Category: 'Category', | ||||
| 	ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ | ||||
| 	Keywords: 'Keywords', | ||||
| 	LastAuthor: 'LastAuthor', | ||||
| 	LastPrinted: 'LastPrinted', | ||||
| 	RevNumber: 'Revision', | ||||
| 	Author: 'Author', | ||||
| 	Comments: 'Description', | ||||
| 	Identifier: 'Identifier', /* NOTE: missing from schema */ | ||||
| 	Language: 'Language', /* NOTE: missing from schema */ | ||||
| 	Subject: 'Subject', | ||||
| 	Title: 'Title', | ||||
| 	CreatedDate: 'Created', | ||||
| 	ModifiedDate: 'LastSaved', | ||||
| 
 | ||||
| 	Application: 'AppName', | ||||
| 	AppVersion: 'Version', | ||||
| 	TotalTime: 'TotalTime', | ||||
| 	Manager: 'Manager', | ||||
| 	Company: 'Company' | ||||
| }; | ||||
| var evert_XLMLDPM = evert(XLMLDocPropsMap); | ||||
| 
 | ||||
| function xlml_set_prop(Props, tag/*:string*/, val) { | ||||
| 	/* TODO: Normalize the properties */ | ||||
| 	switch(tag) { | ||||
| 		case 'Description': tag = 'Comments'; break; | ||||
| 		case 'Created': tag = 'CreatedDate'; break; | ||||
| 		case 'LastSaved': tag = 'ModifiedDate'; break; | ||||
| 	} | ||||
| 	tag = evert_XLMLDPM[tag] || tag; | ||||
| 	Props[tag] = val; | ||||
| } | ||||
| 
 | ||||
| var XLMLDocumentProperties = [ | ||||
| 	['Title', 'Title'], | ||||
| 	['Subject', 'Subject'], | ||||
| 	['Author', 'Author'], | ||||
| 	['Keywords', 'Keywords'], | ||||
| 	['Comments', 'Description'], | ||||
| 	['LastAuthor', 'LastAuthor'], | ||||
| 	['CreatedDate', 'Created', 'date'], | ||||
| 	['ModifiedDate', 'LastSaved', 'date'], | ||||
| 	['Category', 'Category'], | ||||
| 	['Manager', 'Manager'], | ||||
| 	['Company', 'Company'], | ||||
| 	['AppVersion', 'Version'] | ||||
| ]; | ||||
| 
 | ||||
| /* TODO: verify */ | ||||
| function xlml_write_docprops(Props) { | ||||
| 	var T = 'DocumentProperties'; | ||||
| function xlml_write_docprops(Props, opts) { | ||||
| 	var o = []; | ||||
| 	XLMLDocumentProperties.forEach(function(p) { | ||||
| 		if(!Props[p[0]]) return; | ||||
| 		var m = Props[p[0]]; | ||||
| 	CORE_PROPS.concat(EXT_PROPS).forEach(function(p) { | ||||
| 		if(Props[p[1]] == null) return; | ||||
| 		var m = opts && opts.Props && opts.Props[p[1]] != null ? opts.Props[p[1]] : Props[p[1]]; | ||||
| 		switch(p[2]) { | ||||
| 			case 'date': m = new Date(m).toISOString(); break; | ||||
| 		} | ||||
| 		o.push(writetag(p[1], m)); | ||||
| 		if(typeof m == 'number') m = String(m); | ||||
| 		else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } | ||||
| 		else if(m instanceof Date) m = new Date(m).toISOString(); | ||||
| 		o.push(writetag(XLMLDocPropsMap[p[1]] || p[1], m)); | ||||
| 	}); | ||||
| 	return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + '</' + T + '>'; | ||||
| 	return writextag('DocumentProperties', o.join(""), {xmlns:XLMLNS.o }); | ||||
| } | ||||
| function xlml_write_custprops(Props, Custprops) { | ||||
| function xlml_write_custprops(Props, Custprops, opts) { | ||||
| 	var BLACKLIST = ["Worksheets","SheetNames"]; | ||||
| 	var T = 'CustomDocumentProperties'; | ||||
| 	var o = []; | ||||
| 	if(Props) keys(Props).forEach(function(k) { | ||||
| 		/*:: if(!Props) return; */ | ||||
| 		if(!Props.hasOwnProperty(k)) return; | ||||
| 		for(var i = 0; i < XLMLDocumentProperties.length; ++i) | ||||
| 			if(k == XLMLDocumentProperties[i][0]) return; | ||||
| 		for(var i = 0; i < CORE_PROPS.length; ++i) if(k == CORE_PROPS[i][1]) return; | ||||
| 		for(i = 0; i < EXT_PROPS.length; ++i) if(k == EXT_PROPS[i][1]) return; | ||||
| 		for(i = 0; i < BLACKLIST.length; ++i) if(k == BLACKLIST[i]) return; | ||||
| 
 | ||||
| 		var m = Props[k]; | ||||
| 		var t = "string"; | ||||
| 		if(typeof m == 'number') { t = "float"; m = String(m); } | ||||
| @ -4190,9 +4210,10 @@ function parse_Lbl(blob, length, opts) { | ||||
| 	var chKey = blob.read_shift(1); | ||||
| 	var cch = blob.read_shift(1); | ||||
| 	var cce = blob.read_shift(opts && opts.biff == 2 ? 1 : 2); | ||||
| 	var itab = 0; | ||||
| 	if(!opts || opts.biff >= 5) { | ||||
| 		blob.l += 2; | ||||
| 		var itab = blob.read_shift(2); | ||||
| 		itab = blob.read_shift(2); | ||||
| 		blob.l += 4; | ||||
| 	} | ||||
| 	var name = parse_XLUnicodeStringNoCch(blob, cch, opts); | ||||
| @ -4201,6 +4222,7 @@ function parse_Lbl(blob, length, opts) { | ||||
| 	return { | ||||
| 		chKey: chKey, | ||||
| 		Name: name, | ||||
| 		itab: itab, | ||||
| 		rgce: rgce | ||||
| 	}; | ||||
| } | ||||
| @ -9604,6 +9626,12 @@ function csf_to_ods_formula(f/*:string*/)/*:string*/ { | ||||
| 	/* TODO: something other than this */ | ||||
| 	return o.replace(/;/g, "|").replace(/,/g,";"); | ||||
| } | ||||
| 
 | ||||
| function ods_to_csf_range_3D(r/*:string*/) { | ||||
| 	var a = r.split(":"); | ||||
| 	var s = a[0].split(".")[0]; | ||||
| 	return [s, a[0].split(".")[1] + ":" + a[1].split(".")[1]]; | ||||
| } | ||||
| var strs = {}; // shared strings
 | ||||
| var _ssfopts = {}; // spreadsheet formatting options
 | ||||
| 
 | ||||
| @ -9687,43 +9715,54 @@ var sheetdataregex = /<(?:\w+:)?sheetData>([^\u2603]*)<\/(?:\w+:)?sheetData>/; | ||||
| var hlinkregex = /<(?:\w:)?hyperlink [^>]*>/mg; | ||||
| var dimregex = /"(\w*:\w*)"/; | ||||
| var colregex = /<(?:\w:)?col[^>]*[\/]?>/g; | ||||
| var afregex = /<(?:\w:)?autoFilter[^>]*([\/]|>([^\u2603]*)<\/(?:\w:)?autoFilter)>/g; | ||||
| /* 18.3 Worksheets */ | ||||
| function parse_ws_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 	if(!data) return data; | ||||
| 	if(DENSE != null && opts.dense == null) opts.dense = DENSE; | ||||
| 
 | ||||
| 	/* 18.3.1.99 worksheet CT_Worksheet */ | ||||
| 	var s = opts.dense ? ([]/*:any*/) : ({}/*:any*/); | ||||
| 	var refguess/*:Range*/ = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }/*:any*/); | ||||
| 
 | ||||
| 	var data1 = "", data2 = ""; | ||||
| 	var mtch=data.match(sheetdataregex); | ||||
| 	if(mtch) { | ||||
| 		data1 = data.substr(0, mtch.index); | ||||
| 		data2 = data.substr(mtch.index + mtch[0].length); | ||||
| 	} else data1 = data2 = data; | ||||
| 
 | ||||
| 	/* 18.3.1.35 dimension CT_SheetDimension ? */ | ||||
| 	// $FlowIgnore
 | ||||
| 	var ridx = (data.match(/<(?:\w*:)?dimension/)||{index:-1}).index; | ||||
| 	var ridx = (data1.match(/<(?:\w*:)?dimension/)||{index:-1}).index; | ||||
| 	if(ridx > 0) { | ||||
| 		var ref = data.substr(ridx,50).match(dimregex); | ||||
| 		if(ref != null) parse_ws_xml_dim(s, ref[1]); | ||||
| 		var ref = data1.substr(ridx,50).match(dimregex); | ||||
| 		if(ref) parse_ws_xml_dim(s, ref[1]); | ||||
| 	} | ||||
| 
 | ||||
| 	/* 18.3.1.55 mergeCells CT_MergeCells */ | ||||
| 	var mergecells = []; | ||||
| 	var merges = data.match(mergecregex); | ||||
| 	if(merges) for(ridx = 0; ridx != merges.length; ++ridx) | ||||
| 		mergecells[ridx] = safe_decode_range(merges[ridx].substr(merges[ridx].indexOf("\"")+1)); | ||||
| 
 | ||||
| 	/* 18.3.1.17 cols CT_Cols */ | ||||
| 	var columns = []; | ||||
| 	if(opts.cellStyles) { | ||||
| 		/* 18.3.1.13 col CT_Col */ | ||||
| 		var cols = data.match(colregex); | ||||
| 		var cols = data1.match(colregex); | ||||
| 		if(cols) parse_ws_xml_cols(columns, cols); | ||||
| 	} | ||||
| 
 | ||||
| 	var refguess/*:Range*/ = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }/*:any*/); | ||||
| 
 | ||||
| 	/* 18.3.1.80 sheetData CT_SheetData ? */ | ||||
| 	var mtch=data.match(sheetdataregex); | ||||
| 	if(mtch) parse_ws_xml_data(mtch[1], s, opts, refguess, themes, styles); | ||||
| 
 | ||||
| 	/* 18.3.1.2  autoFilter CT_AutoFilter */ | ||||
| 	var afilter = data2.match(afregex); | ||||
| 	if(afilter) s['!autofilter'] = parse_ws_xml_autofilter(afilter[0]); | ||||
| 
 | ||||
| 	/* 18.3.1.55 mergeCells CT_MergeCells */ | ||||
| 	var mergecells = []; | ||||
| 	var merges = data2.match(mergecregex); | ||||
| 	if(merges) for(ridx = 0; ridx != merges.length; ++ridx) | ||||
| 		mergecells[ridx] = safe_decode_range(merges[ridx].substr(merges[ridx].indexOf("\"")+1)); | ||||
| 
 | ||||
| 	/* 18.3.1.48 hyperlinks CT_Hyperlinks */ | ||||
| 	var hlink = data.match(hlinkregex); | ||||
| 	var hlink = data2.match(hlinkregex); | ||||
| 	if(hlink) parse_ws_xml_hlinks(s, hlink, rels); | ||||
| 
 | ||||
| 	if(!s["!ref"] && refguess.e.c >= refguess.s.c && refguess.e.r >= refguess.s.r) s["!ref"] = encode_range(refguess); | ||||
| @ -9822,6 +9861,14 @@ function write_ws_xml_cols(ws, cols)/*:string*/ { | ||||
| 	return o.join(""); | ||||
| } | ||||
| 
 | ||||
| function parse_ws_xml_autofilter(data) { | ||||
| 	var o = { ref: (data.match(/ref="([^"]*)"/)||[])[1]}; | ||||
| 	return o; | ||||
| } | ||||
| function write_ws_xml_autofilter(data)/*:string*/ { | ||||
| 	return writextag("autoFilter", null, {ref:data.ref}); | ||||
| } | ||||
| 
 | ||||
| function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { | ||||
| 	if(cell.v === undefined && cell.f === undefined || cell.t === 'z') return ""; | ||||
| 	var vv = ""; | ||||
| @ -9941,7 +9988,6 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 							p.F = arrayf[i][1]; | ||||
| 			} | ||||
| 
 | ||||
| 			/* SCHEMA IS ACTUALLY INCORRECT HERE.  IF A CELL HAS NO T, EMIT "" */ | ||||
| 			if(tag.t === undefined && p.v === undefined) { | ||||
| 				if(!opts.sheetStubs) continue; | ||||
| 				p.t = "z"; | ||||
| @ -9973,7 +10019,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 					cref = d.match(isregex); | ||||
| 					p.t = 's'; | ||||
| 					if(cref != null && (sstr = parse_si(cref[1]))) p.v = sstr.t; else p.v = ""; | ||||
| 					break; // inline string
 | ||||
| 					break; | ||||
| 				case 'b': p.v = parsexmlbool(p.v); break; | ||||
| 				case 'd': | ||||
| 					if(!opts.cellDates) { p.v = datenum(parseDate(p.v)); p.t = 'n'; } | ||||
| @ -10041,19 +10087,23 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 	var o = [XML_HEADER, WS_XML_ROOT]; | ||||
| 	var s = wb.SheetNames[idx], sidx = 0, rdata = ""; | ||||
| 	var ws = wb.Sheets[s]; | ||||
| 	if(ws === undefined) ws = {}; | ||||
| 	var ref = ws['!ref']; if(ref === undefined) ref = 'A1'; | ||||
| 	if(ws == null) ws = {}; | ||||
| 	var ref = ws['!ref']; if(ref == null) ref = 'A1'; | ||||
| 	if(!rels) rels = {}; | ||||
| 	ws['!comments'] = []; | ||||
| 	ws['!drawing'] = []; | ||||
| 
 | ||||
| 	o[o.length] = (writextag('sheetPr', null, {'codeName': escapexml(wb.SheetNames[idx])})); | ||||
| 
 | ||||
| 	o[o.length] = (writextag('dimension', null, {'ref': ref})); | ||||
| 
 | ||||
| 	/* sheetViews */ | ||||
| 
 | ||||
| 	/* TODO: store in WB, process styles */ | ||||
| 	if(opts.sheetFormat) o[o.length] = (writextag('sheetFormatPr', null, {defaultRowHeight:opts.sheetFormat.defaultRowHeight||'16', baseColWidth:opts.sheetFormat.baseColWidth||'10' })); | ||||
| 
 | ||||
| 	if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); | ||||
| 	if(ws['!cols'] != null && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); | ||||
| 
 | ||||
| 	o[sidx = o.length] = '<sheetData/>'; | ||||
| 	ws['!links'] = []; | ||||
| 	if(ws['!ref'] != null) { | ||||
| @ -10062,10 +10112,25 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 	} | ||||
| 	if(o.length>sidx+1) { o[o.length] = ('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); } | ||||
| 
 | ||||
| 	/* sheetCalcPr */ | ||||
| 
 | ||||
| 	if(ws['!protect'] != null) o[o.length] = write_ws_xml_protection(ws['!protect']); | ||||
| 
 | ||||
| 	/* protectedRanges */ | ||||
| 	/* scenarios */ | ||||
| 
 | ||||
| 	if(ws['!autofilter'] != null) o[o.length] = write_ws_xml_autofilter(ws['!autofilter']); | ||||
| 
 | ||||
| 	/* sortState */ | ||||
| 	/* dataConsolidate */ | ||||
| 	/* customSheetViews */ | ||||
| 
 | ||||
| 	if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges'])); | ||||
| 
 | ||||
| 	/* phoneticPr */ | ||||
| 	/* conditionalFormatting */ | ||||
| 	/* dataValidations */ | ||||
| 
 | ||||
| 	var relc = -1, rel, rId = -1; | ||||
| 	if(ws['!links'].length > 0) { | ||||
| 		o[o.length] = "<hyperlinks>"; | ||||
| @ -10081,21 +10146,41 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 	} | ||||
| 	delete ws['!links']; | ||||
| 
 | ||||
| 	/* printOptions */ | ||||
| 	/* pageMargins */ | ||||
| 	/* pageSetup */ | ||||
| 
 | ||||
| 	var hfidx = o.length; | ||||
| 	o[o.length] = ""; | ||||
| 
 | ||||
| 	/* rowBreaks */ | ||||
| 	/* colBreaks */ | ||||
| 	/* customProperties */ | ||||
| 	/* cellWatches */ | ||||
| 	/* ignoredErrors */ | ||||
| 	/* smartTags */ | ||||
| 
 | ||||
| 	if(ws['!drawing'].length > 0) { | ||||
| 		rId = add_rels(rels, -1, "../drawings/drawing" + (idx+1) + ".xml", RELS.DRAW); | ||||
| 		ws['!drawing'].rid = rId; | ||||
| 		o[o.length] = writextag("drawing", null, {"r:id":"rId" + rId}); | ||||
| 	} | ||||
| 	else delete ws['!drawing']; | ||||
| 
 | ||||
| 	if(ws['!comments'].length > 0) { | ||||
| 		rId = add_rels(rels, -1, "../drawings/vmlDrawing" + (idx+1) + ".vml", RELS.VML); | ||||
| 		o[o.length] = writextag("legacyDrawing", null, {"r:id":"rId" + rId}); | ||||
| 		ws['!legacy'] = rId; | ||||
| 	} | ||||
| 
 | ||||
| 	/* drawingHF */ | ||||
| 	/* picture */ | ||||
| 	/* oleObjects */ | ||||
| 	/* controls */ | ||||
| 	/* webPublishItems */ | ||||
| 	/* tableParts */ | ||||
| 	/* extList */ | ||||
| 
 | ||||
| 	if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
| @ -10390,6 +10475,35 @@ function write_BrtColInfo(C/*:number*/, col, o) { | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.740 BrtSheetProtection */ | ||||
| function write_BrtSheetProtection(sp, o) { | ||||
| 	if(o == null) o = new_buf(16*4+2); | ||||
| 	o.write_shift(2, sp.password ? crypto_CreatePasswordVerifier_Method1(sp.password) : 0); | ||||
| 	o.write_shift(4, 1); // this record should not be written if no protection
 | ||||
| 	[ | ||||
| 		["objects",             false], // fObjects
 | ||||
| 		["scenarios",           false], // fScenarios
 | ||||
| 		["formatCells",          true], // fFormatCells
 | ||||
| 		["formatColumns",        true], // fFormatColumns
 | ||||
| 		["formatRows",           true], // fFormatRows
 | ||||
| 		["insertColumns",        true], // fInsertColumns
 | ||||
| 		["insertRows",           true], // fInsertRows
 | ||||
| 		["insertHyperlinks",     true], // fInsertHyperlinks
 | ||||
| 		["deleteColumns",        true], // fDeleteColumns
 | ||||
| 		["deleteRows",           true], // fDeleteRows
 | ||||
| 		["selectLockedCells",   false], // fSelLockedCells
 | ||||
| 		["sort",                 true], // fSort
 | ||||
| 		["autoFilter",           true], // fAutoFilter
 | ||||
| 		["pivotTables",          true], // fPivotTables
 | ||||
| 		["selectUnlockedCells", false]  // fSelUnlockedCells
 | ||||
| 	].forEach(function(n) { | ||||
| 		o.write_shift(4, 1); | ||||
| 		if(!n[1]) o.write_shift(4, sp[n] != null && sp[n] ? 1 : 0); | ||||
| 		else     o.write_shift(4, sp[n] != null && !sp[n] ? 0 : 1); | ||||
| 	}); | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.1.7.61 Worksheet */ | ||||
| function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 	if(!data) return data; | ||||
| @ -10535,6 +10649,10 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 				} | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x00A1: /* 'BrtBeginAFilter' */ | ||||
| 				s['!autofilter'] = { ref:encode_range(val) }; | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x00AF: /* 'BrtAFilterDateGroupItem' */ | ||||
| 			case 0x0284: /* 'BrtActiveX' */ | ||||
| 			case 0x0271: /* 'BrtBigName' */ | ||||
| @ -10729,6 +10847,21 @@ function write_LEGACYDRAWING(ba, ws/*:Worksheet*/, idx/*:number*/, rels) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function write_AUTOFILTER(ba, ws) { | ||||
| 	if(!ws['!autofilter']) return; | ||||
| 	write_record(ba, "BrtBeginAFilter", write_UncheckedRfX(decode_range(ws['!autofilter'].ref))); | ||||
| 	/* *FILTERCOLUMN */ | ||||
| 	/* [SORTSTATE] */ | ||||
| 	/* BrtEndAFilter */ | ||||
| 	write_record(ba, "BrtEndAFilter"); | ||||
| } | ||||
| 
 | ||||
| function write_SHEETPROTECT(ba, ws) { | ||||
| 	if(!ws['!protect']) return; | ||||
| 	/* [BrtSheetProtectionIso] */ | ||||
| 	write_record(ba, "BrtSheetProtection", write_BrtSheetProtection(ws['!protect'])); | ||||
| } | ||||
| 
 | ||||
| function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { | ||||
| 	var ba = buf_array(); | ||||
| 	var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {}; | ||||
| @ -10744,10 +10877,10 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { | ||||
| 	write_COLINFOS(ba, ws, idx, opts, wb); | ||||
| 	write_CELLTABLE(ba, ws, idx, opts, wb); | ||||
| 	/* [BrtSheetCalcProp] */ | ||||
| 	/* [[BrtSheetProtectionIso] BrtSheetProtection] */ | ||||
| 	write_SHEETPROTECT(ba, ws); | ||||
| 	/* *([BrtRangeProtectionIso] BrtRangeProtection) */ | ||||
| 	/* [SCENMAN] */ | ||||
| 	/* [AUTOFILTER] */ | ||||
| 	write_AUTOFILTER(ba, ws); | ||||
| 	/* [SORTSTATE] */ | ||||
| 	/* [DCON] */ | ||||
| 	/* [USERSHVIEWS] */ | ||||
| @ -11185,7 +11318,15 @@ function safe1904(wb/*:Workbook*/)/*:string*/ { | ||||
| function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ { | ||||
| 	var o = [XML_HEADER]; | ||||
| 	o[o.length] = WB_XML_ROOT; | ||||
| 
 | ||||
| 	/* fileVersion */ | ||||
| 	/* fileSharing */ | ||||
| 
 | ||||
| 	o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb), codeName:"ThisWorkbook"})); | ||||
| 
 | ||||
| 	/* workbookProtection */ | ||||
| 	/* bookViews */ | ||||
| 
 | ||||
| 	o[o.length] = "<sheets>"; | ||||
| 	var sheets = wb.Workbook && wb.Workbook.Sheets || []; | ||||
| 	for(var i = 0; i != wb.SheetNames.length; ++i) { | ||||
| @ -11199,6 +11340,21 @@ function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ { | ||||
| 		o[o.length] = (writextag('sheet',null,sht)); | ||||
| 	} | ||||
| 	o[o.length] = "</sheets>"; | ||||
| 
 | ||||
| 	/* functionGroups */ | ||||
| 	/* externalReferences */ | ||||
| 	/* definedNames */ | ||||
| 	/* calcPr */ | ||||
| 	/* oleSize */ | ||||
| 	/* customWorkbookViews */ | ||||
| 	/* pivotCaches */ | ||||
| 	/* smartTagPr */ | ||||
| 	/* smartTagTypes */ | ||||
| 	/* webPublishing */ | ||||
| 	/* fileRecoveryPr */ | ||||
| 	/* webPublishObjects */ | ||||
| 	/* extLst */ | ||||
| 
 | ||||
| 	if(o.length>2){ o[o.length] = '</workbook>'; o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
| @ -11878,6 +12034,9 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 		case 'TotalTime': | ||||
| 		case 'HyperlinkBase': | ||||
| 		case 'Manager': | ||||
| 		case 'ContentStatus': | ||||
| 		case 'Identifier': | ||||
| 		case 'Language': | ||||
| 			if(Rn[0].slice(-2) === "/>") break; | ||||
| 			else if(Rn[1]==="/") xlml_set_prop(Props, Rn[3], str.slice(pidx, Rn.index)); | ||||
| 			else pidx = Rn.index + Rn[0].length; | ||||
| @ -11902,6 +12061,15 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 			} | ||||
| 			break; | ||||
| 
 | ||||
| 		case 'AutoFilter': | ||||
| 			if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));} | ||||
| 			else if(Rn[0].charAt(Rn[0].length-2) !== '/') { | ||||
| 				var AutoFilter = xlml_parsexmltag(Rn[0]); | ||||
| 				cursheet['!autofilter'] = { ref:rc_to_a1(AutoFilter.Range).replace(/\$/g,"") }; | ||||
| 				state.push([Rn[3], true]); | ||||
| 			} | ||||
| 			break; | ||||
| 
 | ||||
| 		case 'Name': break; | ||||
| 
 | ||||
| 		case 'ComponentOptions': | ||||
| @ -11915,7 +12083,6 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 		case 'PageBreaks': | ||||
| 		case 'QueryTable': | ||||
| 		case 'DataValidation': | ||||
| 		case 'AutoFilter': | ||||
| 		case 'Sorting': | ||||
| 		case 'Schema': | ||||
| 		case 'data': | ||||
| @ -12310,7 +12477,7 @@ function parse_xlml(data, opts)/*:Workbook*/ { | ||||
| function write_props_xlml(wb, opts) { | ||||
| 	var o = []; | ||||
| 	/* DocumentProperties */ | ||||
| 	if(wb.Props) o.push(xlml_write_docprops(wb.Props)); | ||||
| 	if(wb.Props) o.push(xlml_write_docprops(wb.Props, opts)); | ||||
| 	/* CustomDocumentProperties */ | ||||
| 	if(wb.Custprops) o.push(xlml_write_custprops(wb.Props, wb.Custprops)); | ||||
| 	return o.join(""); | ||||
| @ -12638,6 +12805,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { | ||||
| 	var last_Rn = ''; | ||||
| 	var file_depth = 0; /* TODO: make a real stack */ | ||||
| 	var BIFF2Fmt = 0; | ||||
| 	var FilterDatabases = []; /* TODO: sort out supbooks and process elsewhere */ | ||||
| 
 | ||||
| 	/* explicit override for some broken writers */ | ||||
| 	opts.codepage = 1200; | ||||
| @ -12713,7 +12881,14 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { | ||||
| 				case 'SupBook': supbooks[++sbc] = [val]; sbci = 0; break; | ||||
| 				case 'ExternName': supbooks[sbc][++sbci] = val; break; | ||||
| 				case 'Index': break; // TODO
 | ||||
| 				case 'Lbl': supbooks[0][++sbcli] = val; break; | ||||
| 				case 'Lbl': | ||||
| 					supbooks[0][++sbcli] = val; // TODO: local formula storage in stringify_formula
 | ||||
| 					if(!supbooks[val.itab]) supbooks[val.itab] = []; | ||||
| 					supbooks[val.itab].push(val); | ||||
| 					if(val.Name == "\r" && val.itab > 0) | ||||
| 						if(val.rgce && val.rgce[0] && val.rgce[0][0] && val.rgce[0][0][0] == 'PtgArea3d') | ||||
| 							FilterDatabases[val.itab - 1] = { ref: encode_range(val.rgce[0][0][1][2]) }; | ||||
| 					break; | ||||
| 				case 'ExternSheet': supbooks[sbc] = supbooks[sbc].concat(val); sbci += val.length; break; | ||||
| 
 | ||||
| 				case 'Protect': out["!protect"] = val; break; /* for sheet or book */ | ||||
| @ -13173,6 +13348,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { | ||||
| 	wb.Directory=sheetnamesraw; | ||||
| 	wb.SheetNames=sheetnamesraw; | ||||
| 	if(!options.bookSheets) wb.Sheets=Sheets; | ||||
| 	if(wb.Sheets) FilterDatabases.forEach(function(r,i) { wb.Sheets[wb.SheetNames[i]]['!autofilter'] = r; }); | ||||
| 	wb.Preamble=Preamble; | ||||
| 	wb.Strings = sst; | ||||
| 	wb.SSF = SSF.get_table(); | ||||
| @ -13330,7 +13506,7 @@ var XLSBRecordEnum = { | ||||
| 	/*::[*/0x009E/*::]*/: { n:"BrtBookView", f:parsenoop }, | ||||
| 	/*::[*/0x009F/*::]*/: { n:"BrtBeginSst", f:parse_BrtBeginSst }, | ||||
| 	/*::[*/0x00A0/*::]*/: { n:"BrtEndSst", f:parsenoop }, | ||||
| 	/*::[*/0x00A1/*::]*/: { n:"BrtBeginAFilter", f:parsenoop }, | ||||
| 	/*::[*/0x00A1/*::]*/: { n:"BrtBeginAFilter", f:parse_UncheckedRfX }, | ||||
| 	/*::[*/0x00A2/*::]*/: { n:"BrtEndAFilter", f:parsenoop }, | ||||
| 	/*::[*/0x00A3/*::]*/: { n:"BrtBeginFilterColumn", f:parsenoop }, | ||||
| 	/*::[*/0x00A4/*::]*/: { n:"BrtEndFilterColumn", f:parsenoop }, | ||||
| @ -14981,6 +15157,15 @@ var parse_content_xml = (function() { | ||||
| 				if(Rn[1]==='/') textp = (textp.length > 0 ? textp + "\n" : "") + parse_text_p(str.slice(textpidx,Rn.index), textptag); | ||||
| 				else { textptag = parsexmltag(Rn[0], false); textpidx = Rn.index + Rn[0].length; } | ||||
| 				break; // <text:p>
 | ||||
| 
 | ||||
| 			case 'database-range': // 9.4.15 <table:database-range>
 | ||||
| 				if(Rn[1]==='/') break; | ||||
| 				try { | ||||
| 					var AutoFilter = ods_to_csf_range_3D(parsexmltag(Rn[0])['target-range-address']); | ||||
| 					Sheets[AutoFilter[0]]['!autofilter'] = { ref: AutoFilter[1] }; | ||||
| 				} catch(e) { } | ||||
| 				break; | ||||
| 
 | ||||
| 			case 's': break; // <text:s>
 | ||||
| 			case 'date': break; // <*:date>
 | ||||
| 
 | ||||
| @ -14995,7 +15180,6 @@ var parse_content_xml = (function() { | ||||
| 			case 'content-validation': break; // 9.4.5 <table:
 | ||||
| 			case 'error-message': break; // 9.4.7 <table:
 | ||||
| 			case 'database-ranges': break; // 9.4.14 <table:database-ranges>
 | ||||
| 			case 'database-range': break; // 9.4.15 <table:database-range>
 | ||||
| 			case 'filter': break; // 9.5.2 <table:filter>
 | ||||
| 			case 'filter-and': break; // 9.5.3 <table:filter-and>
 | ||||
| 			case 'filter-or': break; // 9.5.4 <table:filter-or>
 | ||||
|  | ||||
							
								
								
									
										312
									
								
								xlsx.js
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										312
									
								
								xlsx.js
									
									
									
									
									
								
							| @ -2986,15 +2986,24 @@ function cp_doit(f, g, h, o, p) { | ||||
| 	o[o.length] = (h ? writextag(f,g,h) : writetag(f,g)); | ||||
| } | ||||
| 
 | ||||
| function write_core_props(cp, opts) { | ||||
| function write_core_props(cp, _opts) { | ||||
| 	var opts = _opts || {}; | ||||
| 	var o = [XML_HEADER, CORE_PROPS_XML_ROOT], p = {}; | ||||
| 	if(!cp) return o.join(""); | ||||
| 	if(!cp && !opts.Props) return o.join(""); | ||||
| 
 | ||||
| 	if(cp) { | ||||
| 		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); | ||||
| 		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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	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); | ||||
| 
 | ||||
| 	for(var i = 0; i != CORE_PROPS.length; ++i) { var f = CORE_PROPS[i]; cp_doit(f[0], cp[f[1]], null, o, p); } | ||||
| 	for(var i = 0; i != CORE_PROPS.length; ++i) { | ||||
| 		var f = CORE_PROPS[i]; | ||||
| 		var v = opts.Props && opts.Props[f[1]] != null ? opts.Props[f[1]] : cp ? cp[f[1]] : null; | ||||
| 		if(v === true) v = "1"; | ||||
| 		else if(v === false) v = "0"; | ||||
| 		else if(typeof v == "number") v = String(v); | ||||
| 		if(v != null) cp_doit(f[0], v, null, o, p); | ||||
| 	} | ||||
| 	if(o.length>2){ o[o.length] = ('</cp:coreProperties>'); o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
| @ -3089,7 +3098,7 @@ function write_ext_props(cp, opts) { | ||||
| 		if(cp[f[1]] === undefined) return; | ||||
| 		var v; | ||||
| 		switch(f[2]) { | ||||
| 			case 'string': v = cp[f[1]]; break; | ||||
| 			case 'string': v = String(cp[f[1]]); break; | ||||
| 			case 'bool': v = cp[f[1]] ? 'true' : 'false'; break; | ||||
| 		} | ||||
| 		if(v !== undefined) o[o.length] = (W(f[0], v)); | ||||
| @ -3169,52 +3178,63 @@ function write_cust_props(cp, opts) { | ||||
| 	if(o.length>2){ o[o.length] = '</Properties>'; o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
| /* Common Name -> XLML Name */ | ||||
| var XLMLDocPropsMap = { | ||||
| 	Category: 'Category', | ||||
| 	ContentStatus: 'ContentStatus', /* NOTE: missing from schema */ | ||||
| 	Keywords: 'Keywords', | ||||
| 	LastAuthor: 'LastAuthor', | ||||
| 	LastPrinted: 'LastPrinted', | ||||
| 	RevNumber: 'Revision', | ||||
| 	Author: 'Author', | ||||
| 	Comments: 'Description', | ||||
| 	Identifier: 'Identifier', /* NOTE: missing from schema */ | ||||
| 	Language: 'Language', /* NOTE: missing from schema */ | ||||
| 	Subject: 'Subject', | ||||
| 	Title: 'Title', | ||||
| 	CreatedDate: 'Created', | ||||
| 	ModifiedDate: 'LastSaved', | ||||
| 
 | ||||
| 	Application: 'AppName', | ||||
| 	AppVersion: 'Version', | ||||
| 	TotalTime: 'TotalTime', | ||||
| 	Manager: 'Manager', | ||||
| 	Company: 'Company' | ||||
| }; | ||||
| var evert_XLMLDPM = evert(XLMLDocPropsMap); | ||||
| 
 | ||||
| function xlml_set_prop(Props, tag, val) { | ||||
| 	/* TODO: Normalize the properties */ | ||||
| 	switch(tag) { | ||||
| 		case 'Description': tag = 'Comments'; break; | ||||
| 		case 'Created': tag = 'CreatedDate'; break; | ||||
| 		case 'LastSaved': tag = 'ModifiedDate'; break; | ||||
| 	} | ||||
| 	tag = evert_XLMLDPM[tag] || tag; | ||||
| 	Props[tag] = val; | ||||
| } | ||||
| 
 | ||||
| var XLMLDocumentProperties = [ | ||||
| 	['Title', 'Title'], | ||||
| 	['Subject', 'Subject'], | ||||
| 	['Author', 'Author'], | ||||
| 	['Keywords', 'Keywords'], | ||||
| 	['Comments', 'Description'], | ||||
| 	['LastAuthor', 'LastAuthor'], | ||||
| 	['CreatedDate', 'Created', 'date'], | ||||
| 	['ModifiedDate', 'LastSaved', 'date'], | ||||
| 	['Category', 'Category'], | ||||
| 	['Manager', 'Manager'], | ||||
| 	['Company', 'Company'], | ||||
| 	['AppVersion', 'Version'] | ||||
| ]; | ||||
| 
 | ||||
| /* TODO: verify */ | ||||
| function xlml_write_docprops(Props) { | ||||
| 	var T = 'DocumentProperties'; | ||||
| function xlml_write_docprops(Props, opts) { | ||||
| 	var o = []; | ||||
| 	XLMLDocumentProperties.forEach(function(p) { | ||||
| 		if(!Props[p[0]]) return; | ||||
| 		var m = Props[p[0]]; | ||||
| 	CORE_PROPS.concat(EXT_PROPS).forEach(function(p) { | ||||
| 		if(Props[p[1]] == null) return; | ||||
| 		var m = opts && opts.Props && opts.Props[p[1]] != null ? opts.Props[p[1]] : Props[p[1]]; | ||||
| 		switch(p[2]) { | ||||
| 			case 'date': m = new Date(m).toISOString(); break; | ||||
| 		} | ||||
| 		o.push(writetag(p[1], m)); | ||||
| 		if(typeof m == 'number') m = String(m); | ||||
| 		else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } | ||||
| 		else if(m instanceof Date) m = new Date(m).toISOString(); | ||||
| 		o.push(writetag(XLMLDocPropsMap[p[1]] || p[1], m)); | ||||
| 	}); | ||||
| 	return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + '</' + T + '>'; | ||||
| 	return writextag('DocumentProperties', o.join(""), {xmlns:XLMLNS.o }); | ||||
| } | ||||
| function xlml_write_custprops(Props, Custprops) { | ||||
| function xlml_write_custprops(Props, Custprops, opts) { | ||||
| 	var BLACKLIST = ["Worksheets","SheetNames"]; | ||||
| 	var T = 'CustomDocumentProperties'; | ||||
| 	var o = []; | ||||
| 	if(Props) keys(Props).forEach(function(k) { | ||||
| if(!Props.hasOwnProperty(k)) return; | ||||
| 		for(var i = 0; i < XLMLDocumentProperties.length; ++i) | ||||
| 			if(k == XLMLDocumentProperties[i][0]) return; | ||||
| 		for(var i = 0; i < CORE_PROPS.length; ++i) if(k == CORE_PROPS[i][1]) return; | ||||
| 		for(i = 0; i < EXT_PROPS.length; ++i) if(k == EXT_PROPS[i][1]) return; | ||||
| 		for(i = 0; i < BLACKLIST.length; ++i) if(k == BLACKLIST[i]) return; | ||||
| 
 | ||||
| 		var m = Props[k]; | ||||
| 		var t = "string"; | ||||
| 		if(typeof m == 'number') { t = "float"; m = String(m); } | ||||
| @ -4134,9 +4154,10 @@ function parse_Lbl(blob, length, opts) { | ||||
| 	var chKey = blob.read_shift(1); | ||||
| 	var cch = blob.read_shift(1); | ||||
| 	var cce = blob.read_shift(opts && opts.biff == 2 ? 1 : 2); | ||||
| 	var itab = 0; | ||||
| 	if(!opts || opts.biff >= 5) { | ||||
| 		blob.l += 2; | ||||
| 		var itab = blob.read_shift(2); | ||||
| 		itab = blob.read_shift(2); | ||||
| 		blob.l += 4; | ||||
| 	} | ||||
| 	var name = parse_XLUnicodeStringNoCch(blob, cch, opts); | ||||
| @ -4145,6 +4166,7 @@ function parse_Lbl(blob, length, opts) { | ||||
| 	return { | ||||
| 		chKey: chKey, | ||||
| 		Name: name, | ||||
| 		itab: itab, | ||||
| 		rgce: rgce | ||||
| 	}; | ||||
| } | ||||
| @ -9547,6 +9569,12 @@ function csf_to_ods_formula(f) { | ||||
| 	/* TODO: something other than this */ | ||||
| 	return o.replace(/;/g, "|").replace(/,/g,";"); | ||||
| } | ||||
| 
 | ||||
| function ods_to_csf_range_3D(r) { | ||||
| 	var a = r.split(":"); | ||||
| 	var s = a[0].split(".")[0]; | ||||
| 	return [s, a[0].split(".")[1] + ":" + a[1].split(".")[1]]; | ||||
| } | ||||
| var strs = {}; // shared strings
 | ||||
| var _ssfopts = {}; // spreadsheet formatting options
 | ||||
| 
 | ||||
| @ -9630,43 +9658,54 @@ var sheetdataregex = /<(?:\w+:)?sheetData>([^\u2603]*)<\/(?:\w+:)?sheetData>/; | ||||
| var hlinkregex = /<(?:\w:)?hyperlink [^>]*>/mg; | ||||
| var dimregex = /"(\w*:\w*)"/; | ||||
| var colregex = /<(?:\w:)?col[^>]*[\/]?>/g; | ||||
| var afregex = /<(?:\w:)?autoFilter[^>]*([\/]|>([^\u2603]*)<\/(?:\w:)?autoFilter)>/g; | ||||
| /* 18.3 Worksheets */ | ||||
| function parse_ws_xml(data, opts, rels, wb, themes, styles) { | ||||
| 	if(!data) return data; | ||||
| 	if(DENSE != null && opts.dense == null) opts.dense = DENSE; | ||||
| 
 | ||||
| 	/* 18.3.1.99 worksheet CT_Worksheet */ | ||||
| 	var s = opts.dense ? ([]) : ({}); | ||||
| 	var refguess = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }); | ||||
| 
 | ||||
| 	var data1 = "", data2 = ""; | ||||
| 	var mtch=data.match(sheetdataregex); | ||||
| 	if(mtch) { | ||||
| 		data1 = data.substr(0, mtch.index); | ||||
| 		data2 = data.substr(mtch.index + mtch[0].length); | ||||
| 	} else data1 = data2 = data; | ||||
| 
 | ||||
| 	/* 18.3.1.35 dimension CT_SheetDimension ? */ | ||||
| 	// $FlowIgnore
 | ||||
| 	var ridx = (data.match(/<(?:\w*:)?dimension/)||{index:-1}).index; | ||||
| 	var ridx = (data1.match(/<(?:\w*:)?dimension/)||{index:-1}).index; | ||||
| 	if(ridx > 0) { | ||||
| 		var ref = data.substr(ridx,50).match(dimregex); | ||||
| 		if(ref != null) parse_ws_xml_dim(s, ref[1]); | ||||
| 		var ref = data1.substr(ridx,50).match(dimregex); | ||||
| 		if(ref) parse_ws_xml_dim(s, ref[1]); | ||||
| 	} | ||||
| 
 | ||||
| 	/* 18.3.1.55 mergeCells CT_MergeCells */ | ||||
| 	var mergecells = []; | ||||
| 	var merges = data.match(mergecregex); | ||||
| 	if(merges) for(ridx = 0; ridx != merges.length; ++ridx) | ||||
| 		mergecells[ridx] = safe_decode_range(merges[ridx].substr(merges[ridx].indexOf("\"")+1)); | ||||
| 
 | ||||
| 	/* 18.3.1.17 cols CT_Cols */ | ||||
| 	var columns = []; | ||||
| 	if(opts.cellStyles) { | ||||
| 		/* 18.3.1.13 col CT_Col */ | ||||
| 		var cols = data.match(colregex); | ||||
| 		var cols = data1.match(colregex); | ||||
| 		if(cols) parse_ws_xml_cols(columns, cols); | ||||
| 	} | ||||
| 
 | ||||
| 	var refguess = ({s: {r:2000000, c:2000000}, e: {r:0, c:0} }); | ||||
| 
 | ||||
| 	/* 18.3.1.80 sheetData CT_SheetData ? */ | ||||
| 	var mtch=data.match(sheetdataregex); | ||||
| 	if(mtch) parse_ws_xml_data(mtch[1], s, opts, refguess, themes, styles); | ||||
| 
 | ||||
| 	/* 18.3.1.2  autoFilter CT_AutoFilter */ | ||||
| 	var afilter = data2.match(afregex); | ||||
| 	if(afilter) s['!autofilter'] = parse_ws_xml_autofilter(afilter[0]); | ||||
| 
 | ||||
| 	/* 18.3.1.55 mergeCells CT_MergeCells */ | ||||
| 	var mergecells = []; | ||||
| 	var merges = data2.match(mergecregex); | ||||
| 	if(merges) for(ridx = 0; ridx != merges.length; ++ridx) | ||||
| 		mergecells[ridx] = safe_decode_range(merges[ridx].substr(merges[ridx].indexOf("\"")+1)); | ||||
| 
 | ||||
| 	/* 18.3.1.48 hyperlinks CT_Hyperlinks */ | ||||
| 	var hlink = data.match(hlinkregex); | ||||
| 	var hlink = data2.match(hlinkregex); | ||||
| 	if(hlink) parse_ws_xml_hlinks(s, hlink, rels); | ||||
| 
 | ||||
| 	if(!s["!ref"] && refguess.e.c >= refguess.s.c && refguess.e.r >= refguess.s.r) s["!ref"] = encode_range(refguess); | ||||
| @ -9765,6 +9804,14 @@ function write_ws_xml_cols(ws, cols) { | ||||
| 	return o.join(""); | ||||
| } | ||||
| 
 | ||||
| function parse_ws_xml_autofilter(data) { | ||||
| 	var o = { ref: (data.match(/ref="([^"]*)"/)||[])[1]}; | ||||
| 	return o; | ||||
| } | ||||
| function write_ws_xml_autofilter(data) { | ||||
| 	return writextag("autoFilter", null, {ref:data.ref}); | ||||
| } | ||||
| 
 | ||||
| function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { | ||||
| 	if(cell.v === undefined && cell.f === undefined || cell.t === 'z') return ""; | ||||
| 	var vv = ""; | ||||
| @ -9884,7 +9931,6 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 							p.F = arrayf[i][1]; | ||||
| 			} | ||||
| 
 | ||||
| 			/* SCHEMA IS ACTUALLY INCORRECT HERE.  IF A CELL HAS NO T, EMIT "" */ | ||||
| 			if(tag.t === undefined && p.v === undefined) { | ||||
| 				if(!opts.sheetStubs) continue; | ||||
| 				p.t = "z"; | ||||
| @ -9916,7 +9962,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 					cref = d.match(isregex); | ||||
| 					p.t = 's'; | ||||
| 					if(cref != null && (sstr = parse_si(cref[1]))) p.v = sstr.t; else p.v = ""; | ||||
| 					break; // inline string
 | ||||
| 					break; | ||||
| 				case 'b': p.v = parsexmlbool(p.v); break; | ||||
| 				case 'd': | ||||
| 					if(!opts.cellDates) { p.v = datenum(parseDate(p.v)); p.t = 'n'; } | ||||
| @ -9984,19 +10030,23 @@ function write_ws_xml(idx, opts, wb, rels) { | ||||
| 	var o = [XML_HEADER, WS_XML_ROOT]; | ||||
| 	var s = wb.SheetNames[idx], sidx = 0, rdata = ""; | ||||
| 	var ws = wb.Sheets[s]; | ||||
| 	if(ws === undefined) ws = {}; | ||||
| 	var ref = ws['!ref']; if(ref === undefined) ref = 'A1'; | ||||
| 	if(ws == null) ws = {}; | ||||
| 	var ref = ws['!ref']; if(ref == null) ref = 'A1'; | ||||
| 	if(!rels) rels = {}; | ||||
| 	ws['!comments'] = []; | ||||
| 	ws['!drawing'] = []; | ||||
| 
 | ||||
| 	o[o.length] = (writextag('sheetPr', null, {'codeName': escapexml(wb.SheetNames[idx])})); | ||||
| 
 | ||||
| 	o[o.length] = (writextag('dimension', null, {'ref': ref})); | ||||
| 
 | ||||
| 	/* sheetViews */ | ||||
| 
 | ||||
| 	/* TODO: store in WB, process styles */ | ||||
| 	if(opts.sheetFormat) o[o.length] = (writextag('sheetFormatPr', null, {defaultRowHeight:opts.sheetFormat.defaultRowHeight||'16', baseColWidth:opts.sheetFormat.baseColWidth||'10' })); | ||||
| 
 | ||||
| 	if(ws['!cols'] !== undefined && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); | ||||
| 	if(ws['!cols'] != null && ws['!cols'].length > 0) o[o.length] = (write_ws_xml_cols(ws, ws['!cols'])); | ||||
| 
 | ||||
| 	o[sidx = o.length] = '<sheetData/>'; | ||||
| 	ws['!links'] = []; | ||||
| 	if(ws['!ref'] != null) { | ||||
| @ -10005,10 +10055,25 @@ function write_ws_xml(idx, opts, wb, rels) { | ||||
| 	} | ||||
| 	if(o.length>sidx+1) { o[o.length] = ('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); } | ||||
| 
 | ||||
| 	/* sheetCalcPr */ | ||||
| 
 | ||||
| 	if(ws['!protect'] != null) o[o.length] = write_ws_xml_protection(ws['!protect']); | ||||
| 
 | ||||
| 	/* protectedRanges */ | ||||
| 	/* scenarios */ | ||||
| 
 | ||||
| 	if(ws['!autofilter'] != null) o[o.length] = write_ws_xml_autofilter(ws['!autofilter']); | ||||
| 
 | ||||
| 	/* sortState */ | ||||
| 	/* dataConsolidate */ | ||||
| 	/* customSheetViews */ | ||||
| 
 | ||||
| 	if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges'])); | ||||
| 
 | ||||
| 	/* phoneticPr */ | ||||
| 	/* conditionalFormatting */ | ||||
| 	/* dataValidations */ | ||||
| 
 | ||||
| 	var relc = -1, rel, rId = -1; | ||||
| 	if(ws['!links'].length > 0) { | ||||
| 		o[o.length] = "<hyperlinks>"; | ||||
| @ -10024,21 +10089,41 @@ function write_ws_xml(idx, opts, wb, rels) { | ||||
| 	} | ||||
| 	delete ws['!links']; | ||||
| 
 | ||||
| 	/* printOptions */ | ||||
| 	/* pageMargins */ | ||||
| 	/* pageSetup */ | ||||
| 
 | ||||
| 	var hfidx = o.length; | ||||
| 	o[o.length] = ""; | ||||
| 
 | ||||
| 	/* rowBreaks */ | ||||
| 	/* colBreaks */ | ||||
| 	/* customProperties */ | ||||
| 	/* cellWatches */ | ||||
| 	/* ignoredErrors */ | ||||
| 	/* smartTags */ | ||||
| 
 | ||||
| 	if(ws['!drawing'].length > 0) { | ||||
| 		rId = add_rels(rels, -1, "../drawings/drawing" + (idx+1) + ".xml", RELS.DRAW); | ||||
| 		ws['!drawing'].rid = rId; | ||||
| 		o[o.length] = writextag("drawing", null, {"r:id":"rId" + rId}); | ||||
| 	} | ||||
| 	else delete ws['!drawing']; | ||||
| 
 | ||||
| 	if(ws['!comments'].length > 0) { | ||||
| 		rId = add_rels(rels, -1, "../drawings/vmlDrawing" + (idx+1) + ".vml", RELS.VML); | ||||
| 		o[o.length] = writextag("legacyDrawing", null, {"r:id":"rId" + rId}); | ||||
| 		ws['!legacy'] = rId; | ||||
| 	} | ||||
| 
 | ||||
| 	/* drawingHF */ | ||||
| 	/* picture */ | ||||
| 	/* oleObjects */ | ||||
| 	/* controls */ | ||||
| 	/* webPublishItems */ | ||||
| 	/* tableParts */ | ||||
| 	/* extList */ | ||||
| 
 | ||||
| 	if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
| @ -10333,6 +10418,35 @@ function write_BrtColInfo(C, col, o) { | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.740 BrtSheetProtection */ | ||||
| function write_BrtSheetProtection(sp, o) { | ||||
| 	if(o == null) o = new_buf(16*4+2); | ||||
| 	o.write_shift(2, sp.password ? crypto_CreatePasswordVerifier_Method1(sp.password) : 0); | ||||
| 	o.write_shift(4, 1); // this record should not be written if no protection
 | ||||
| 	[ | ||||
| 		["objects",             false], // fObjects
 | ||||
| 		["scenarios",           false], // fScenarios
 | ||||
| 		["formatCells",          true], // fFormatCells
 | ||||
| 		["formatColumns",        true], // fFormatColumns
 | ||||
| 		["formatRows",           true], // fFormatRows
 | ||||
| 		["insertColumns",        true], // fInsertColumns
 | ||||
| 		["insertRows",           true], // fInsertRows
 | ||||
| 		["insertHyperlinks",     true], // fInsertHyperlinks
 | ||||
| 		["deleteColumns",        true], // fDeleteColumns
 | ||||
| 		["deleteRows",           true], // fDeleteRows
 | ||||
| 		["selectLockedCells",   false], // fSelLockedCells
 | ||||
| 		["sort",                 true], // fSort
 | ||||
| 		["autoFilter",           true], // fAutoFilter
 | ||||
| 		["pivotTables",          true], // fPivotTables
 | ||||
| 		["selectUnlockedCells", false]  // fSelUnlockedCells
 | ||||
| 	].forEach(function(n) { | ||||
| 		o.write_shift(4, 1); | ||||
| 		if(!n[1]) o.write_shift(4, sp[n] != null && sp[n] ? 1 : 0); | ||||
| 		else     o.write_shift(4, sp[n] != null && !sp[n] ? 0 : 1); | ||||
| 	}); | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.1.7.61 Worksheet */ | ||||
| function parse_ws_bin(data, _opts, rels, wb, themes, styles) { | ||||
| 	if(!data) return data; | ||||
| @ -10478,6 +10592,10 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles) { | ||||
| 				} | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x00A1: /* 'BrtBeginAFilter' */ | ||||
| 				s['!autofilter'] = { ref:encode_range(val) }; | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x00AF: /* 'BrtAFilterDateGroupItem' */ | ||||
| 			case 0x0284: /* 'BrtActiveX' */ | ||||
| 			case 0x0271: /* 'BrtBigName' */ | ||||
| @ -10672,6 +10790,21 @@ function write_LEGACYDRAWING(ba, ws, idx, rels) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function write_AUTOFILTER(ba, ws) { | ||||
| 	if(!ws['!autofilter']) return; | ||||
| 	write_record(ba, "BrtBeginAFilter", write_UncheckedRfX(decode_range(ws['!autofilter'].ref))); | ||||
| 	/* *FILTERCOLUMN */ | ||||
| 	/* [SORTSTATE] */ | ||||
| 	/* BrtEndAFilter */ | ||||
| 	write_record(ba, "BrtEndAFilter"); | ||||
| } | ||||
| 
 | ||||
| function write_SHEETPROTECT(ba, ws) { | ||||
| 	if(!ws['!protect']) return; | ||||
| 	/* [BrtSheetProtectionIso] */ | ||||
| 	write_record(ba, "BrtSheetProtection", write_BrtSheetProtection(ws['!protect'])); | ||||
| } | ||||
| 
 | ||||
| function write_ws_bin(idx, opts, wb, rels) { | ||||
| 	var ba = buf_array(); | ||||
| 	var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {}; | ||||
| @ -10687,10 +10820,10 @@ function write_ws_bin(idx, opts, wb, rels) { | ||||
| 	write_COLINFOS(ba, ws, idx, opts, wb); | ||||
| 	write_CELLTABLE(ba, ws, idx, opts, wb); | ||||
| 	/* [BrtSheetCalcProp] */ | ||||
| 	/* [[BrtSheetProtectionIso] BrtSheetProtection] */ | ||||
| 	write_SHEETPROTECT(ba, ws); | ||||
| 	/* *([BrtRangeProtectionIso] BrtRangeProtection) */ | ||||
| 	/* [SCENMAN] */ | ||||
| 	/* [AUTOFILTER] */ | ||||
| 	write_AUTOFILTER(ba, ws); | ||||
| 	/* [SORTSTATE] */ | ||||
| 	/* [DCON] */ | ||||
| 	/* [USERSHVIEWS] */ | ||||
| @ -11128,7 +11261,15 @@ function safe1904(wb) { | ||||
| function write_wb_xml(wb, opts) { | ||||
| 	var o = [XML_HEADER]; | ||||
| 	o[o.length] = WB_XML_ROOT; | ||||
| 
 | ||||
| 	/* fileVersion */ | ||||
| 	/* fileSharing */ | ||||
| 
 | ||||
| 	o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb), codeName:"ThisWorkbook"})); | ||||
| 
 | ||||
| 	/* workbookProtection */ | ||||
| 	/* bookViews */ | ||||
| 
 | ||||
| 	o[o.length] = "<sheets>"; | ||||
| 	var sheets = wb.Workbook && wb.Workbook.Sheets || []; | ||||
| 	for(var i = 0; i != wb.SheetNames.length; ++i) { | ||||
| @ -11142,6 +11283,21 @@ function write_wb_xml(wb, opts) { | ||||
| 		o[o.length] = (writextag('sheet',null,sht)); | ||||
| 	} | ||||
| 	o[o.length] = "</sheets>"; | ||||
| 
 | ||||
| 	/* functionGroups */ | ||||
| 	/* externalReferences */ | ||||
| 	/* definedNames */ | ||||
| 	/* calcPr */ | ||||
| 	/* oleSize */ | ||||
| 	/* customWorkbookViews */ | ||||
| 	/* pivotCaches */ | ||||
| 	/* smartTagPr */ | ||||
| 	/* smartTagTypes */ | ||||
| 	/* webPublishing */ | ||||
| 	/* fileRecoveryPr */ | ||||
| 	/* webPublishObjects */ | ||||
| 	/* extLst */ | ||||
| 
 | ||||
| 	if(o.length>2){ o[o.length] = '</workbook>'; o[1]=o[1].replace("/>",">"); } | ||||
| 	return o.join(""); | ||||
| } | ||||
| @ -11818,6 +11974,9 @@ for(var cma = c; cma <= cc; ++cma) { | ||||
| 		case 'TotalTime': | ||||
| 		case 'HyperlinkBase': | ||||
| 		case 'Manager': | ||||
| 		case 'ContentStatus': | ||||
| 		case 'Identifier': | ||||
| 		case 'Language': | ||||
| 			if(Rn[0].slice(-2) === "/>") break; | ||||
| 			else if(Rn[1]==="/") xlml_set_prop(Props, Rn[3], str.slice(pidx, Rn.index)); | ||||
| 			else pidx = Rn.index + Rn[0].length; | ||||
| @ -11842,6 +12001,15 @@ for(var cma = c; cma <= cc; ++cma) { | ||||
| 			} | ||||
| 			break; | ||||
| 
 | ||||
| 		case 'AutoFilter': | ||||
| 			if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw new Error("Bad state: "+tmp.join("|"));} | ||||
| 			else if(Rn[0].charAt(Rn[0].length-2) !== '/') { | ||||
| 				var AutoFilter = xlml_parsexmltag(Rn[0]); | ||||
| 				cursheet['!autofilter'] = { ref:rc_to_a1(AutoFilter.Range).replace(/\$/g,"") }; | ||||
| 				state.push([Rn[3], true]); | ||||
| 			} | ||||
| 			break; | ||||
| 
 | ||||
| 		case 'Name': break; | ||||
| 
 | ||||
| 		case 'ComponentOptions': | ||||
| @ -11855,7 +12023,6 @@ for(var cma = c; cma <= cc; ++cma) { | ||||
| 		case 'PageBreaks': | ||||
| 		case 'QueryTable': | ||||
| 		case 'DataValidation': | ||||
| 		case 'AutoFilter': | ||||
| 		case 'Sorting': | ||||
| 		case 'Schema': | ||||
| 		case 'data': | ||||
| @ -12249,7 +12416,7 @@ function parse_xlml(data, opts) { | ||||
| function write_props_xlml(wb, opts) { | ||||
| 	var o = []; | ||||
| 	/* DocumentProperties */ | ||||
| 	if(wb.Props) o.push(xlml_write_docprops(wb.Props)); | ||||
| 	if(wb.Props) o.push(xlml_write_docprops(wb.Props, opts)); | ||||
| 	/* CustomDocumentProperties */ | ||||
| 	if(wb.Custprops) o.push(xlml_write_custprops(wb.Props, wb.Custprops)); | ||||
| 	return o.join(""); | ||||
| @ -12577,6 +12744,7 @@ function parse_workbook(blob, options) { | ||||
| 	var last_Rn = ''; | ||||
| 	var file_depth = 0; /* TODO: make a real stack */ | ||||
| 	var BIFF2Fmt = 0; | ||||
| 	var FilterDatabases = []; /* TODO: sort out supbooks and process elsewhere */ | ||||
| 
 | ||||
| 	/* explicit override for some broken writers */ | ||||
| 	opts.codepage = 1200; | ||||
| @ -12652,7 +12820,14 @@ function parse_workbook(blob, options) { | ||||
| 				case 'SupBook': supbooks[++sbc] = [val]; sbci = 0; break; | ||||
| 				case 'ExternName': supbooks[sbc][++sbci] = val; break; | ||||
| 				case 'Index': break; // TODO
 | ||||
| 				case 'Lbl': supbooks[0][++sbcli] = val; break; | ||||
| 				case 'Lbl': | ||||
| 					supbooks[0][++sbcli] = val; // TODO: local formula storage in stringify_formula
 | ||||
| 					if(!supbooks[val.itab]) supbooks[val.itab] = []; | ||||
| 					supbooks[val.itab].push(val); | ||||
| 					if(val.Name == "\r" && val.itab > 0) | ||||
| 						if(val.rgce && val.rgce[0] && val.rgce[0][0] && val.rgce[0][0][0] == 'PtgArea3d') | ||||
| 							FilterDatabases[val.itab - 1] = { ref: encode_range(val.rgce[0][0][1][2]) }; | ||||
| 					break; | ||||
| 				case 'ExternSheet': supbooks[sbc] = supbooks[sbc].concat(val); sbci += val.length; break; | ||||
| 
 | ||||
| 				case 'Protect': out["!protect"] = val; break; /* for sheet or book */ | ||||
| @ -13112,6 +13287,7 @@ function parse_workbook(blob, options) { | ||||
| 	wb.Directory=sheetnamesraw; | ||||
| 	wb.SheetNames=sheetnamesraw; | ||||
| 	if(!options.bookSheets) wb.Sheets=Sheets; | ||||
| 	if(wb.Sheets) FilterDatabases.forEach(function(r,i) { wb.Sheets[wb.SheetNames[i]]['!autofilter'] = r; }); | ||||
| 	wb.Preamble=Preamble; | ||||
| 	wb.Strings = sst; | ||||
| 	wb.SSF = SSF.get_table(); | ||||
| @ -13269,7 +13445,7 @@ var XLSBRecordEnum = { | ||||
| 0x009E: { n:"BrtBookView", f:parsenoop }, | ||||
| 0x009F: { n:"BrtBeginSst", f:parse_BrtBeginSst }, | ||||
| 0x00A0: { n:"BrtEndSst", f:parsenoop }, | ||||
| 0x00A1: { n:"BrtBeginAFilter", f:parsenoop }, | ||||
| 0x00A1: { n:"BrtBeginAFilter", f:parse_UncheckedRfX }, | ||||
| 0x00A2: { n:"BrtEndAFilter", f:parsenoop }, | ||||
| 0x00A3: { n:"BrtBeginFilterColumn", f:parsenoop }, | ||||
| 0x00A4: { n:"BrtEndFilterColumn", f:parsenoop }, | ||||
| @ -14920,6 +15096,15 @@ var parse_content_xml = (function() { | ||||
| 				if(Rn[1]==='/') textp = (textp.length > 0 ? textp + "\n" : "") + parse_text_p(str.slice(textpidx,Rn.index), textptag); | ||||
| 				else { textptag = parsexmltag(Rn[0], false); textpidx = Rn.index + Rn[0].length; } | ||||
| 				break; // <text:p>
 | ||||
| 
 | ||||
| 			case 'database-range': // 9.4.15 <table:database-range>
 | ||||
| 				if(Rn[1]==='/') break; | ||||
| 				try { | ||||
| 					var AutoFilter = ods_to_csf_range_3D(parsexmltag(Rn[0])['target-range-address']); | ||||
| 					Sheets[AutoFilter[0]]['!autofilter'] = { ref: AutoFilter[1] }; | ||||
| 				} catch(e) { } | ||||
| 				break; | ||||
| 
 | ||||
| 			case 's': break; // <text:s>
 | ||||
| 			case 'date': break; // <*:date>
 | ||||
| 
 | ||||
| @ -14934,7 +15119,6 @@ var parse_content_xml = (function() { | ||||
| 			case 'content-validation': break; // 9.4.5 <table:
 | ||||
| 			case 'error-message': break; // 9.4.7 <table:
 | ||||
| 			case 'database-ranges': break; // 9.4.14 <table:database-ranges>
 | ||||
| 			case 'database-range': break; // 9.4.15 <table:database-range>
 | ||||
| 			case 'filter': break; // 9.5.2 <table:filter>
 | ||||
| 			case 'filter-and': break; // 9.5.3 <table:filter-and>
 | ||||
| 			case 'filter-or': break; // 9.5.4 <table:filter-or>
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user