forked from sheetjs/sheetjs
		
	row and column size and visibility
- XLSX/XLSB/XLS/XLML/SYLK rows and columns - corrected pixel/point calculations using PPI - XLSX/XLSB generate sheet view - clarified sheet protection default behavior - fixed eslintrc semi check
This commit is contained in:
		
							parent
							
								
									c6f96c3df7
								
							
						
					
					
						commit
						dcee744e4e
					
				| @ -13,6 +13,7 @@ | ||||
| 		"curly": 0, | ||||
| 		"comma-style": [ 2, "last" ], | ||||
| 		"no-trailing-spaces": 2, | ||||
| 		"semi": [ 2, "always" ], | ||||
| 		"comma-dangle": [ 2, "never" ] | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										96
									
								
								README.md
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										96
									
								
								README.md
									
									
									
									
									
								
							| @ -57,6 +57,7 @@ enhancements and additional features by request. | ||||
|   * [Document Features](#document-features) | ||||
|     + [Formulae](#formulae) | ||||
|     + [Column Properties](#column-properties) | ||||
|     + [Row Properties](#row-properties) | ||||
|     + [Hyperlinks](#hyperlinks) | ||||
|     + [Cell Comments](#cell-comments) | ||||
|     + [Sheet Visibility](#sheet-visibility) | ||||
| @ -97,6 +98,7 @@ enhancements and additional features by request. | ||||
|   * [Tested Environments](#tested-environments) | ||||
|   * [Test Files](#test-files) | ||||
| - [Contributing](#contributing) | ||||
|   * [Tests](#tests) | ||||
|   * [OSX/Linux](#osxlinux) | ||||
|   * [Windows](#windows) | ||||
| - [License](#license) | ||||
| @ -630,33 +632,37 @@ In addition to the base sheet keys, worksheets also add: | ||||
|   parsed, the column objects store the pixel width in the `wpx` field, character | ||||
|   width in the `wch` field, and the maximum digit width in the `MDW` field. | ||||
| 
 | ||||
| - `ws['!rows']`: array of row properties objects as explained later in the docs. | ||||
|   Each row object encodes properties including row height and visibility. | ||||
| 
 | ||||
| - `ws['!merges']`: array of range objects corresponding to the merged cells in | ||||
|   the worksheet.  Plaintext utilities are unaware of merge cells.  CSV export | ||||
|   will write all cells in the merge range if they exist, so be sure that only | ||||
|   the first cell (upper-left) in the range is set. | ||||
| 
 | ||||
| - `ws['protect']`: object of write sheet protection properties.  The `password` | ||||
| - `ws['!protect']`: object of write sheet protection properties.  The `password` | ||||
|   key specifies the password for formats that support password-protected sheets | ||||
|   (XLSX/XLSB/XLS).  The writer uses the XOR obfuscation method.  The following | ||||
|   keys control the sheet protection (same as ECMA-376 18.3.1.85): | ||||
|   keys control the sheet protection -- set to `false` to enable a feature when | ||||
|   sheet is locked or set to `true` to disable a feature: | ||||
| 
 | ||||
| | key                   | functionality disabled if value is true              | | ||||
| |:----------------------|:-----------------------------------------------------| | ||||
| | `selectLockedCells`   | Select locked cells                                  | | ||||
| | `selectUnlockedCells` | Select unlocked cells                                | | ||||
| | `formatCells`         | Format cells                                         | | ||||
| | `formatColumns`       | Format columns                                       | | ||||
| | `formatRows`          | Format rows                                          | | ||||
| | `insertColumns`       | Insert columns                                       | | ||||
| | `insertRows`          | Insert rows                                          | | ||||
| | `insertHyperlinks`    | Insert hyperlinks                                    | | ||||
| | `deleteColumns`       | Delete columns                                       | | ||||
| | `deleteRows`          | Delete rows                                          | | ||||
| | `sort`                | Sort                                                 | | ||||
| | `autoFilter`          | Filter                                               | | ||||
| | `pivotTables`         | Use PivotTable reports                               | | ||||
| | `objects`             | Edit objects                                         | | ||||
| | `scenarios`           | Edit scenarios                                       | | ||||
| | key                   | feature (true=disabled / false=enabled) | default    | | ||||
| |:----------------------|:----------------------------------------|:-----------| | ||||
| | `selectLockedCells`   | Select locked cells                     | enabled    | | ||||
| | `selectUnlockedCells` | Select unlocked cells                   | enabled    | | ||||
| | `formatCells`         | Format cells                            | disabled   | | ||||
| | `formatColumns`       | Format columns                          | disabled   | | ||||
| | `formatRows`          | Format rows                             | disabled   | | ||||
| | `insertColumns`       | Insert columns                          | disabled   | | ||||
| | `insertRows`          | Insert rows                             | disabled   | | ||||
| | `insertHyperlinks`    | Insert hyperlinks                       | disabled   | | ||||
| | `deleteColumns`       | Delete columns                          | disabled   | | ||||
| | `deleteRows`          | Delete rows                             | disabled   | | ||||
| | `sort`                | Sort                                    | disabled   | | ||||
| | `autoFilter`          | Filter                                  | disabled   | | ||||
| | `pivotTables`         | Use PivotTable reports                  | disabled   | | ||||
| | `objects`             | Edit objects                            | enabled    | | ||||
| | `scenarios`           | Edit scenarios                          | enabled    | | ||||
| 
 | ||||
| - `ws['!autofilter']`: AutoFilter object following the schema: | ||||
| 
 | ||||
| @ -835,6 +841,7 @@ Since Excel prohibits named cells from colliding with names of A1 or RC style | ||||
| cell references, a (not-so-simple) regex conversion is possible.  BIFF Parsed | ||||
| formulae have to be explicitly unwound.  OpenFormula formulae can be converted | ||||
| with regexes for the most part. | ||||
| 
 | ||||
| #### Column Properties | ||||
| 
 | ||||
| Excel internally stores column widths in a nebulous "Max Digit Width" form.  The | ||||
| @ -853,10 +860,11 @@ objects which have the following properties: | ||||
| 
 | ||||
| ```typescript | ||||
| type ColInfo = { | ||||
| 	MDW?:number;  // Excel's "Max Digit Width" unit, always integral | ||||
| 	width:number; // width in Excel's "Max Digit Width", width*256 is integral | ||||
| 	wpx?:number;  // width in screen pixels | ||||
| 	wch?:number;  // intermediate character calculation | ||||
| 	MDW?:number;     // Excel's "Max Digit Width" unit, always integral | ||||
| 	width:number;    // width in Excel's "Max Digit Width", width*256 is integral | ||||
| 	wpx?:number;     // width in screen pixels | ||||
| 	wch?:number;     // intermediate character calculation | ||||
| 	hidden:?boolean; // if true, the column is hidden | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| @ -867,6 +875,29 @@ follow the priority order: | ||||
| 2) use `wpx` pixel width if available | ||||
| 3) use `wch` character count if available | ||||
| 
 | ||||
| #### Row Properties | ||||
| 
 | ||||
| Excel internally stores row heights in points.  The default resolution is 72 DPI | ||||
| or 96 PPI, so the pixel and point size should agree.  For different resolutions | ||||
| they may not agree, so the library separates the concepts. | ||||
| 
 | ||||
| The `!rows` array in each worksheet, if present, is a collection of `RowInfo` | ||||
| objects which have the following properties: | ||||
| 
 | ||||
| ```typescript | ||||
| type RowInfo = { | ||||
| 	hpx?:number;     // height in screen pixels | ||||
| 	hpt?:number;     // height in points | ||||
| 	hidden:?boolean; // if true, the row is hidden | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| Even though all of the information is made available, writers are expected to | ||||
| follow the priority order: | ||||
| 
 | ||||
| 1) use `hpx` pixel height if available | ||||
| 2) use `hpt` point height if available | ||||
| 
 | ||||
| #### Hyperlinks | ||||
| 
 | ||||
| Hyperlinks are stored in the `l` key of cell objects.  The `Target` field of the | ||||
| @ -1520,6 +1551,25 @@ Running `make init` will refresh the `test_files` submodule and get the files. | ||||
| Due to the precarious nature of the Open Specifications Promise, it is very | ||||
| important to ensure code is cleanroom.  Consult CONTRIBUTING.md | ||||
| 
 | ||||
| ### Tests | ||||
| 
 | ||||
| The `test_misc` target (`make test_misc` on Linux/OSX / `make misc` on Windows) | ||||
| runs the targeted feature tests.  It should take 5-10 seconds to perform feature | ||||
| tests without testing against the entire test battery.  New features should be | ||||
| accompanied with tests for the relevant file formats and features. | ||||
| 
 | ||||
| For tests involving the read side, an appropriate feature test would involve | ||||
| reading an existing file and checking the resulting workbook object.  If a | ||||
| parameter is involved, files should be read with different values for the param | ||||
| to verify that the feature is working as expected. | ||||
| 
 | ||||
| For tests involving a new write feature which can already be parsed, appropriate | ||||
| feature tests would involve writing a workbook with the feature and then opening | ||||
| and verifying that the feature is preserved. | ||||
| 
 | ||||
| For tests involving a new write feature without an existing read ability, please | ||||
| add a feature test to the kitchen sink `tests/write.js`. | ||||
| 
 | ||||
| ### OSX/Linux | ||||
| 
 | ||||
| The xlsx.js file is constructed from the files in the `bits` subdirectory. The | ||||
|  | ||||
| @ -210,14 +210,19 @@ function parse_ExtSST(blob, length) { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* 2.4.221 TODO*/ | ||||
| /* 2.4.221 TODO: check BIFF2-4 */ | ||||
| function parse_Row(blob, length) { | ||||
| 	var rw = blob.read_shift(2), col = blob.read_shift(2), Col = blob.read_shift(2), rht = blob.read_shift(2); | ||||
| 	blob.read_shift(4); // reserved(2), unused(2)
 | ||||
| 	var z = ({}/*:any*/); | ||||
| 	z.r = blob.read_shift(2); | ||||
| 	z.c = blob.read_shift(2); | ||||
| 	z.cnt = blob.read_shift(2) - z.c; | ||||
| 	var miyRw = blob.read_shift(2); | ||||
| 	blob.l += 4; // reserved(2), unused(2)
 | ||||
| 	var flags = blob.read_shift(1); // various flags
 | ||||
| 	blob.read_shift(1); // reserved
 | ||||
| 	blob.read_shift(2); //ixfe, other flags
 | ||||
| 	return {r:rw, c:col, cnt:Col-col}; | ||||
| 	blob.l += 3; // reserved(8), ixfe(12), flags(4)
 | ||||
| 	if(flags & 0x20) z.hidden = true; | ||||
| 	if(flags & 0x40) z.hpt = miyRw / 20; | ||||
| 	return z; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -206,19 +206,19 @@ var SYLK = (function() { | ||||
| 		var records = str.split(/[\n\r]+/), R = -1, C = -1, ri = 0, rj = 0, arr = []; | ||||
| 		var formats = []; | ||||
| 		var next_cell_format = null; | ||||
| 		var sht = {}, rowinfo = [], colinfo = [], cw = []; | ||||
| 		var Mval = 0, j; | ||||
| 		for (; ri !== records.length; ++ri) { | ||||
| 			Mval = 0; | ||||
| 			var record = records[ri].trim().split(";"); | ||||
| 			var RT = record[0], val; | ||||
| 			if(RT === 'P') for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 				case 'P': | ||||
| 					formats.push(record[rj].substr(1)); | ||||
| 					break; | ||||
| 			} | ||||
| 			else if(RT !== 'C' && RT !== 'F') continue; | ||||
| 			else for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 			switch(RT) { | ||||
| 			case 'P': if(record[1].charAt(0) == 'P') formats.push(records[ri].trim().substr(3).replace(/;;/g, ";")); | ||||
| 				break; | ||||
| 			case 'C': case 'F': for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 				case 'Y': | ||||
| 					R = parseInt(record[rj].substr(1))-1; C = 0; | ||||
| 					for(var j = arr.length; j <= R; ++j) arr[j] = []; | ||||
| 					for(j = arr.length; j <= R; ++j) arr[j] = []; | ||||
| 					break; | ||||
| 				case 'X': C = parseInt(record[rj].substr(1))-1; break; | ||||
| 				case 'K': | ||||
| @ -228,7 +228,7 @@ var SYLK = (function() { | ||||
| 					else if(val === 'FALSE') val = false; | ||||
| 					else if(+val === +val) { | ||||
| 						val = +val; | ||||
| 						if(next_cell_format !== null && next_cell_format.match(/[ymdhmsYMDHMS]/)) val = numdate(val); | ||||
| 						if(next_cell_format !== null && SSF.is_date(next_cell_format)) val = numdate(val); | ||||
| 					} | ||||
| 					arr[R][C] = val; | ||||
| 					next_cell_format = null; | ||||
| @ -236,12 +236,37 @@ var SYLK = (function() { | ||||
| 				case 'P': | ||||
| 					if(RT !== 'F') break; | ||||
| 					next_cell_format = formats[parseInt(record[rj].substr(1))]; | ||||
| 					break; | ||||
| 				case 'M': Mval = parseInt(record[rj].substr(1)) / 20; break; | ||||
| 				case 'W': | ||||
| 					if(RT !== 'F') break; | ||||
| 					cw = record[rj].substr(1).split(" "); | ||||
| 					for(j = parseInt(cw[0], 10); j <= parseInt(cw[1], 10); ++j) { | ||||
| 						Mval = parseInt(cw[2], 10); | ||||
| 						colinfo[j-1] = Mval == 0 ? {hidden:true}: {wch:Mval}; process_col(colinfo[j-1]); | ||||
| 					} break; | ||||
| 				case 'R': | ||||
| 					R = parseInt(record[rj].substr(1))-1; | ||||
| 					rowinfo[R] = {}; | ||||
| 					if(Mval > 0) { rowinfo[R].hpt = Mval; rowinfo[R].hpx = pt2px(Mval); } | ||||
| 					else if(Mval == 0) rowinfo[R].hidden = true; | ||||
| 			} break; | ||||
| 			default: break; | ||||
| 			} | ||||
| 		} | ||||
| 		if(rowinfo.length > 0) sht['!rows'] = rowinfo; | ||||
| 		if(colinfo.length > 0) sht['!cols'] = colinfo; | ||||
| 		arr[arr.length] = sht; | ||||
| 		return arr; | ||||
| 	} | ||||
| 
 | ||||
| 	function sylk_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(sylk_to_aoa(str, opts), opts); } | ||||
| 	function sylk_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { | ||||
| 		var aoa = sylk_to_aoa(str, opts); | ||||
| 		var ws = aoa.pop(); | ||||
| 		var o = aoa_to_sheet(aoa, opts); | ||||
| 		keys(ws).forEach(function(k) { o[k] = ws[k]; }); | ||||
| 		return o; | ||||
| 	} | ||||
| 
 | ||||
| 	function sylk_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(sylk_to_sheet(str, opts), opts); } | ||||
| 
 | ||||
| @ -257,11 +282,40 @@ var SYLK = (function() { | ||||
| 		return o; | ||||
| 	} | ||||
| 
 | ||||
| 	function write_ws_cols_sylk(out, cols) { | ||||
| 		cols.forEach(function(col, i) { | ||||
| 			var rec = "F;W" + (i+1) + " " + (i+1) + " "; | ||||
| 			if(col.hidden) rec += "0"; | ||||
| 			else { | ||||
| 				if(typeof col.width == 'number') col.wpx = width2px(col.width); | ||||
| 				if(typeof col.wpx == 'number') col.wch = px2char(col.wpx); | ||||
| 				if(typeof col.wch == 'number') rec += Math.round(col.wch); | ||||
| 			} | ||||
| 			if(rec.charAt(rec.length - 1) != " ") out.push(rec); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	function write_ws_rows_sylk(out, rows) { | ||||
| 		rows.forEach(function(row, i) { | ||||
| 			var rec = "F;"; | ||||
| 			if(row.hidden) rec += "M0;"; | ||||
| 			else if(row.hpt) rec += "M" + 20 * row.hpt + ";"; | ||||
| 			else if(row.hpx) rec += "M" + 20 * px2pt(row.hpx) + ";"; | ||||
| 			if(rec.length > 2) out.push(rec + "R" + (i+1)); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ { | ||||
| 		var preamble/*:Array<string>*/ = ["ID;PWXL;N;E"], o/*:Array<string>*/ = []; | ||||
| 		preamble.push("P;PGeneral"); | ||||
| 		var r = decode_range(ws['!ref']), cell/*:Cell*/; | ||||
| 		var dense = Array.isArray(ws); | ||||
| 		var RS = "\r\n"; | ||||
| 
 | ||||
| 		preamble.push("P;PGeneral"); | ||||
| 		preamble.push("F;P0;DG0G8;M255"); | ||||
| 		if(ws['!cols']) write_ws_cols_sylk(preamble, ws['!cols']); | ||||
| 		if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']); | ||||
| 
 | ||||
| 		for(var R = r.s.r; R <= r.e.r; ++R) { | ||||
| 			for(var C = r.s.c; C <= r.e.c; ++C) { | ||||
| 				var coord = encode_cell({r:R,c:C}); | ||||
| @ -270,8 +324,6 @@ var SYLK = (function() { | ||||
| 				o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); | ||||
| 			} | ||||
| 		} | ||||
| 		preamble.push("F;P0;DG0G8;M255"); | ||||
| 		var RS = "\r\n"; | ||||
| 		return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS; | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -85,13 +85,17 @@ function process_col(coll/*:ColInfo*/) { | ||||
| 		coll.wch = px2char(coll.wpx); | ||||
| 		coll.width = char2width(coll.wch); | ||||
| 		coll.MDW = MDW; | ||||
| 	} else if(typeof coll.wch == 'number') { | ||||
| 		coll.width = char2width(coll.wch); | ||||
| 		coll.wpx = width2px(coll.width); | ||||
| 		coll.MDW = MDW; | ||||
| 	} | ||||
| 	if(coll.customWidth) delete coll.customWidth; | ||||
| } | ||||
| 
 | ||||
| var DEF_DPI = 96, DPI = DEF_DPI; | ||||
| function px2pt(px) { return px * 72 / DPI; } | ||||
| function pt2px(pt) { return pt * DPI / 72; } | ||||
| var DEF_PPI = 96, PPI = DEF_PPI; | ||||
| function px2pt(px) { return px * 96 / PPI; } | ||||
| function pt2px(pt) { return pt * PPI / 96; } | ||||
| 
 | ||||
| /* [MS-EXSPXML3] 2.4.54 ST_enmPattern */ | ||||
| var XLMLPatternTypeMap = { | ||||
|  | ||||
| @ -14,13 +14,14 @@ function get_sst_id(sst/*:SST*/, str/*:string*/)/*:number*/ { | ||||
| function col_obj_w(C/*:number*/, col) { | ||||
| 	var p = ({min:C+1,max:C+1}/*:any*/); | ||||
| 	/* wch (chars), wpx (pixels) */ | ||||
| 	var width = -1; | ||||
| 	var wch = -1; | ||||
| 	if(col.MDW) MDW = col.MDW; | ||||
| 	if(col.width != null) p.customWidth = 1; | ||||
| 	else if(col.wpx != null) width = px2char(col.wpx); | ||||
| 	else if(col.wch != null) width = col.wch; | ||||
| 	if(width > -1) { p.width = char2width(width); p.customWidth = 1; } | ||||
| 	else p.width = col.width; | ||||
| 	else if(col.wpx != null) wch = px2char(col.wpx); | ||||
| 	else if(col.wch != null) wch = col.wch; | ||||
| 	if(wch > -1) { p.width = char2width(wch); p.customWidth = 1; } | ||||
| 	else if(col.width != null) p.width = col.width; | ||||
| 	if(col.hidden) p.hidden = true; | ||||
| 	return p; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -152,6 +152,7 @@ function parse_ws_xml_cols(columns, cols) { | ||||
| 	var seencol = false; | ||||
| 	for(var coli = 0; coli != cols.length; ++coli) { | ||||
| 		var coll = parsexmltag(cols[coli], true); | ||||
| 		if(coll.hidden) coll.hidden = parsexmlbool(coll.hidden); | ||||
| 		var colm=parseInt(coll.min, 10)-1, colM=parseInt(coll.max,10)-1; | ||||
| 		delete coll.min; delete coll.max; coll.width = +coll.width; | ||||
| 		if(!seencol && coll.width) { seencol = true; find_mdw_colw(coll.width); } | ||||
| @ -178,6 +179,12 @@ function write_ws_xml_autofilter(data)/*:string*/ { | ||||
| 	return writextag("autoFilter", null, {ref:data.ref}); | ||||
| } | ||||
| 
 | ||||
| /* 18.3.1.88 sheetViews CT_SheetViews */ | ||||
| /* 18.3.1.87 sheetView CT_SheetView */ | ||||
| function write_ws_xml_sheetviews(ws, opts, idx, wb)/*:string*/ { | ||||
| 	return writextag("sheetViews", writextag("sheetView", null, {workbookViewId:"0"}), {}); | ||||
| } | ||||
| 
 | ||||
| function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { | ||||
| 	if(cell.v === undefined && cell.f === undefined || cell.t === 'z') return ""; | ||||
| 	var vv = ""; | ||||
| @ -229,13 +236,14 @@ var parse_ws_xml_data = (function parse_ws_xml_data_factory() { | ||||
| 	var match_v = matchtag("v"), match_f = matchtag("f"); | ||||
| 
 | ||||
| return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 	var ri = 0, x = "", cells = [], cref = [], idx = 0, i=0, cc=0, d="", p/*:any*/; | ||||
| 	var ri = 0, x = "", cells = [], cref = [], idx=0, i=0, cc=0, d="", p/*:any*/; | ||||
| 	var tag, tagr = 0, tagc = 0; | ||||
| 	var sstr, ftag; | ||||
| 	var fmtid = 0, fillid = 0, do_format = Array.isArray(styles.CellXf), cf; | ||||
| 	var arrayf = []; | ||||
| 	var sharedf = []; | ||||
| 	var dense = Array.isArray(s); | ||||
| 	var rows = [], rowobj = {}, rowrite = false; | ||||
| 	for(var marr = sdata.split(rowregex), mt = 0, marrlen = marr.length; mt != marrlen; ++mt) { | ||||
| 		x = marr[mt].trim(); | ||||
| 		var xlen = x.length; | ||||
| @ -249,6 +257,13 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 		if(guess.s.r > tagr - 1) guess.s.r = tagr - 1; | ||||
| 		if(guess.e.r < tagr - 1) guess.e.r = tagr - 1; | ||||
| 
 | ||||
| 		if(opts && opts.cellStyles) { | ||||
| 			rowobj = {}; rowrite = false; | ||||
| 			if(tag.ht) { rowrite = true; rowobj.hpt = parseFloat(tag.ht); rowobj.hpx = pt2px(rowobj.hpt); } | ||||
| 			if(tag.hidden == "1") { rowrite = true; rowobj.hidden = true; } | ||||
| 			if(rowrite) rows[tagr-1] = rowobj; | ||||
| 		} | ||||
| 
 | ||||
| 		/* 18.3.1.4 c CT_Cell */ | ||||
| 		cells = x.substr(ri).split(cellregex); | ||||
| 		for(ri = 0; ri != cells.length; ++ri) { | ||||
| @ -357,6 +372,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 			} else s[tag.r] = p; | ||||
| 		} | ||||
| 	} | ||||
| 	if(rows.length > 0) s['!rows'] = rows; | ||||
| }; })(); | ||||
| 
 | ||||
| function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| @ -407,7 +423,7 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 
 | ||||
| 	o[o.length] = (writextag('dimension', null, {'ref': ref})); | ||||
| 
 | ||||
| 	/* sheetViews */ | ||||
| 	o[o.length] = write_ws_xml_sheetviews(ws, opts, idx, wb); | ||||
| 
 | ||||
| 	/* 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' })); | ||||
| @ -457,7 +473,7 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 	delete ws['!links']; | ||||
| 
 | ||||
| 	/* printOptions */ | ||||
| 	if (ws['!margins'] != null) o[o.length] =  write_ws_xml_margins(ws['!margins']) | ||||
| 	if (ws['!margins'] != null) o[o.length] =  write_ws_xml_margins(ws['!margins']); | ||||
| 	/* pageSetup */ | ||||
| 
 | ||||
| 	var hfidx = o.length; | ||||
|  | ||||
| @ -1,20 +1,38 @@ | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.718 BrtRowHdr */ | ||||
| function parse_BrtRowHdr(data, length) { | ||||
| 	var z = ([]/*:any*/); | ||||
| 	var z = ({}/*:any*/); | ||||
| 	var tgt = data.l + length; | ||||
| 	z.r = data.read_shift(4); | ||||
| 	data.l += length-4; | ||||
| 	data.l += 4; // TODO: ixfe
 | ||||
| 	var miyRw = data.read_shift(2); | ||||
| 	data.l += 1; // TODO: top/bot padding
 | ||||
| 	var flags = data.read_shift(1); | ||||
| 	data.l = tgt; | ||||
| 	if(flags & 0x10) z.hidden = true; | ||||
| 	if(flags & 0x20) z.hpt = miyRw / 20; | ||||
| 	return z; | ||||
| } | ||||
| function write_BrtRowHdr(R/*:number*/, range, ws) { | ||||
| 	var o = new_buf(17+8*16); | ||||
| 	var row = (ws['!rows']||[])[R]||{}; | ||||
| 	o.write_shift(4, R); | ||||
| 
 | ||||
| 	/* TODO: flags styles */ | ||||
| 	o.write_shift(4, 0); | ||||
| 	o.write_shift(2, 0x0140); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(1, 0); | ||||
| 	o.write_shift(4, 0); /* TODO: ixfe */ | ||||
| 
 | ||||
| 	var miyRw = 0x0140; | ||||
| 	if(row.hpx) miyRw = px2pt(row.hpx) * 20; | ||||
| 	else if(row.hpt) miyRw = row.hpt * 20; | ||||
| 	o.write_shift(2, miyRw); | ||||
| 
 | ||||
| 	o.write_shift(1, 0); /* top/bot padding */ | ||||
| 
 | ||||
| 	var flags = 0x0; | ||||
| 	if(row.hidden) flags |= 0x10; | ||||
| 	if(row.hpx || row.hpt) flags |= 0x20; | ||||
| 	o.write_shift(1, flags); | ||||
| 
 | ||||
| 	o.write_shift(1, 0); /* phonetic guide */ | ||||
| 
 | ||||
| 	/* [MS-XLSB] 2.5.8 BrtColSpan explains the mechanism */ | ||||
| 	var ncolspan = 0, lcs = o.l; | ||||
| @ -282,9 +300,12 @@ function write_BrtColInfo(C/*:number*/, col, o) { | ||||
| 	var p = col_obj_w(C, col); | ||||
| 	o.write_shift(-4, C); | ||||
| 	o.write_shift(-4, C); | ||||
| 	o.write_shift(4, p.width * 256); | ||||
| 	o.write_shift(4, (p.width || 10) * 256); | ||||
| 	o.write_shift(4, 0/*ixfe*/); // style
 | ||||
| 	o.write_shift(1, 2); // bit flag
 | ||||
| 	var flags = 0; | ||||
| 	if(col.hidden) flags |= 0x01; | ||||
| 	if(typeof p.width == 'number') flags |= 0x02; | ||||
| 	o.write_shift(1, flags); // bit flag
 | ||||
| 	o.write_shift(1, 0); // bit flag
 | ||||
| 	return o; | ||||
| } | ||||
| @ -312,6 +333,24 @@ function write_BrtMargins(margins, o) { | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.292 BrtBeginWsView */ | ||||
| function write_BrtBeginWsView(ws, o) { | ||||
| 	if(o == null) o = new_buf(30); | ||||
| 	o.write_shift(2, 924); // bit flag
 | ||||
| 	o.write_shift(4, 0); | ||||
| 	o.write_shift(4, 0); // view first row
 | ||||
| 	o.write_shift(4, 0); // view first col
 | ||||
| 	o.write_shift(1, 0); // gridline color ICV
 | ||||
| 	o.write_shift(1, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 100); // zoom scale
 | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(4, 0); // workbook view id
 | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.740 BrtSheetProtection */ | ||||
| function write_BrtSheetProtection(sp, o) { | ||||
| 	if(o == null) o = new_buf(16*4+2); | ||||
| @ -334,9 +373,8 @@ function write_BrtSheetProtection(sp, o) { | ||||
| 		["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); | ||||
| 		if(n[1]) o.write_shift(4, sp[n[0]] != null && !sp[n[0]] ? 1 : 0); | ||||
| 		else      o.write_shift(4, sp[n[0]] != null && sp[n[0]] ? 0 : 1); | ||||
| 	}); | ||||
| 	return o; | ||||
| } | ||||
| @ -383,6 +421,10 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 				if(opts.sheetRows && opts.sheetRows <= row.r) end=true; | ||||
| 				rr = encode_row(R = row.r); | ||||
| 				opts['!row'] = row.r; | ||||
| 				if(val.hidden || val.hpt) { | ||||
| 					if(val.hpt) val.hpx = pt2px(val.hpt); | ||||
| 					rowinfo[val.r] = val; | ||||
| 				} | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x0002: /* 'BrtCellRk' */ | ||||
| @ -480,7 +522,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 			case 0x003C: /* 'BrtColInfo' */ | ||||
| 				if(!opts.cellStyles) break; | ||||
| 				while(val.e >= val.s) { | ||||
| 					colinfo[val.e--] = { width: val.w/256 }; | ||||
| 					colinfo[val.e--] = { width: val.w/256, hidden: !!(val.flags & 0x01) }; | ||||
| 					if(!seencol) { seencol = true; find_mdw_colw(val.w/256); } | ||||
| 					process_col(colinfo[val.e+1]); | ||||
| 				} | ||||
| @ -696,6 +738,21 @@ function write_AUTOFILTER(ba, ws) { | ||||
| 	write_record(ba, "BrtEndAFilter"); | ||||
| } | ||||
| 
 | ||||
| function write_WSVIEWS2(ba, ws) { | ||||
| 	write_record(ba, "BrtBeginWsViews"); | ||||
| 	{ /* 1*WSVIEW2 */ | ||||
| 		/* [ACUID] */ | ||||
| 		write_record(ba, "BrtBeginWsView", write_BrtBeginWsView(ws)); | ||||
| 		/* [BrtPane] */ | ||||
| 		/* *4BrtSel */ | ||||
| 		/* *4SXSELECT */ | ||||
| 		/* *FRT */ | ||||
| 		write_record(ba, "BrtEndWsView"); | ||||
| 	} | ||||
| 	/* *FRT */ | ||||
| 	write_record(ba, "BrtEndWsViews"); | ||||
| } | ||||
| 
 | ||||
| function write_SHEETPROTECT(ba, ws) { | ||||
| 	if(!ws['!protect']) return; | ||||
| 	/* [BrtSheetProtectionIso] */ | ||||
| @ -712,7 +769,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { | ||||
| 	write_record(ba, "BrtBeginSheet"); | ||||
| 	write_record(ba, "BrtWsProp", write_BrtWsProp(s)); | ||||
| 	write_record(ba, "BrtWsDim", write_BrtWsDim(r)); | ||||
| 	/* [WSVIEWS2] */ | ||||
| 	write_WSVIEWS2(ba, ws); | ||||
| 	/* [WSFMTINFO] */ | ||||
| 	write_COLINFOS(ba, ws, idx, opts, wb); | ||||
| 	write_CELLTABLE(ba, ws, idx, opts, wb); | ||||
|  | ||||
| @ -189,7 +189,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 	var comments = [], comment = {}; | ||||
| 	var cstys = [], csty, seencol = false; | ||||
| 	var arrayf = []; | ||||
| 	var rowinfo = []; | ||||
| 	var rowinfo = [], rowobj = {}; | ||||
| 	var Workbook = { Sheets:[] }, wsprops = {}; | ||||
| 	xlmlregex.lastIndex = 0; | ||||
| 	str = str.replace(/<!--([^\u2603]*?)-->/mg,""); | ||||
| @ -254,6 +254,12 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 			} else { | ||||
| 				row = xlml_parsexmltag(Rn[0]); | ||||
| 				if(row.Index) r = +row.Index - 1; | ||||
| 				rowobj = {}; | ||||
| 				if(row.AutoFitHeight == "0") { | ||||
| 					rowobj.hpx = parseInt(row.Height, 10); rowobj.hpt = px2pt(rowobj.hpx); | ||||
| 					rowinfo[r] = rowobj; | ||||
| 				} | ||||
| 				if(row.Hidden == "1") { rowobj.hidden = true; rowinfo[r] = rowobj; } | ||||
| 			} | ||||
| 			break; | ||||
| 		case 'Worksheet': /* TODO: read range from FullRows/FullColumns */ | ||||
| @ -304,9 +310,10 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 		case 'Column': | ||||
| 			if(state[state.length-1][0] !== 'Table') break; | ||||
| 			csty = xlml_parsexmltag(Rn[0]); | ||||
| 			csty.wpx = parseInt(csty.Width, 10); | ||||
| 			if(csty.Hidden) { csty.hidden = true; delete csty.Hidden; } | ||||
| 			if(csty.Width) csty.wpx = parseInt(csty.Width, 10); | ||||
| 			if(!seencol && csty.wpx > 10) { | ||||
| 				seencol = true; find_mdw_wpx(csty.wpx); | ||||
| 				seencol = true; MDW = DEF_MDW; //find_mdw_wpx(csty.wpx);
 | ||||
| 				for(var _col = 0; _col < cstys.length; ++_col) if(cstys[_col]) process_col(cstys[_col]); | ||||
| 			} | ||||
| 			if(seencol) process_col(csty); | ||||
| @ -443,7 +450,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 					case 'Color': break; | ||||
| 					case 'Index': break; | ||||
| 					case 'RGB': break; | ||||
| 					case 'PixelsPerInch': break; | ||||
| 					case 'PixelsPerInch': break; // TODO: set PPI
 | ||||
| 					case 'TargetScreenSize': break; | ||||
| 					case 'ReadOnlyRecommended': break; | ||||
| 					default: seen = false; | ||||
| @ -995,6 +1002,15 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{ | ||||
| 
 | ||||
| 	return writextag("Cell", m, attr); | ||||
| } | ||||
| function write_ws_xlml_row(R/*:number*/, row)/*:string*/ { | ||||
| 	var o = '<Row ss:Index="' + (R+1) + '"'; | ||||
| 	if(row) { | ||||
| 		if(row.hpt && !row.hpx) row.hpx = pt2px(row.hpt); | ||||
| 		if(row.hpx) o += ' ss:AutoFitHeight="0" ss:Height="' + row.hpx + '"'; | ||||
| 		if(row.hidden) o += ' ss:Hidden="1"'; | ||||
| 	} | ||||
| 	return o + '>'; | ||||
| } | ||||
| /* TODO */ | ||||
| function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ { | ||||
| 	if(!ws['!ref']) return ""; | ||||
| @ -1002,12 +1018,17 @@ function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbo | ||||
| 	var marr = ws['!merges'] || [], mi = 0; | ||||
| 	var o = []; | ||||
| 	if(ws['!cols']) ws['!cols'].forEach(function(n, i) { | ||||
| 		process_col(n); | ||||
| 		var w = !!n.width; | ||||
| 		var p = col_obj_w(i, n); | ||||
| 		o.push(writextag("Column",null, {"ss:Index":i+1, "ss:Width":width2px(p.width)})); | ||||
| 		var k = {"ss:Index":i+1}; | ||||
| 		if(w) k['ss:Width'] = width2px(p.width); | ||||
| 		if(n.hidden) k['ss:Hidden']="1"; | ||||
| 		o.push(writextag("Column",null,k)); | ||||
| 	}); | ||||
| 	var dense = Array.isArray(ws); | ||||
| 	for(var R = range.s.r; R <= range.e.r; ++R) { | ||||
| 		var row = ['<Row ss:Index="' + (R+1) + '">']; | ||||
| 		var row = [write_ws_xlml_row(R, (ws['!rows']||[])[R])]; | ||||
| 		for(var C = range.s.c; C <= range.e.c; ++C) { | ||||
| 			var skip = false; | ||||
| 			for(mi = 0; mi != marr.length; ++mi) { | ||||
|  | ||||
| @ -502,7 +502,14 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { | ||||
| 						process_col(colinfo[val.e+1]); | ||||
| 					} | ||||
| 				} break; | ||||
| 				case 'Row': break; // TODO
 | ||||
| 				case 'Row': { | ||||
| 					var rowobj = {}; | ||||
| 					if(val.hidden) { rowinfo[val.r] = rowobj; rowobj.hidden = true; } | ||||
| 					if(val.hpt) { | ||||
| 						rowinfo[val.r] = rowobj; | ||||
| 						rowobj.hpt = val.hpt; rowobj.hpx = pt2px(val.hpt); | ||||
| 					} | ||||
| 				} break; | ||||
| 
 | ||||
| 				case 'LeftMargin': | ||||
| 				case 'RightMargin': | ||||
|  | ||||
| @ -8,33 +8,37 @@ In addition to the base sheet keys, worksheets also add: | ||||
|   parsed, the column objects store the pixel width in the `wpx` field, character | ||||
|   width in the `wch` field, and the maximum digit width in the `MDW` field. | ||||
| 
 | ||||
| - `ws['!rows']`: array of row properties objects as explained later in the docs. | ||||
|   Each row object encodes properties including row height and visibility. | ||||
| 
 | ||||
| - `ws['!merges']`: array of range objects corresponding to the merged cells in | ||||
|   the worksheet.  Plaintext utilities are unaware of merge cells.  CSV export | ||||
|   will write all cells in the merge range if they exist, so be sure that only | ||||
|   the first cell (upper-left) in the range is set. | ||||
| 
 | ||||
| - `ws['protect']`: object of write sheet protection properties.  The `password` | ||||
| - `ws['!protect']`: object of write sheet protection properties.  The `password` | ||||
|   key specifies the password for formats that support password-protected sheets | ||||
|   (XLSX/XLSB/XLS).  The writer uses the XOR obfuscation method.  The following | ||||
|   keys control the sheet protection (same as ECMA-376 18.3.1.85): | ||||
|   keys control the sheet protection -- set to `false` to enable a feature when | ||||
|   sheet is locked or set to `true` to disable a feature: | ||||
| 
 | ||||
| | key                   | functionality disabled if value is true              | | ||||
| |:----------------------|:-----------------------------------------------------| | ||||
| | `selectLockedCells`   | Select locked cells                                  | | ||||
| | `selectUnlockedCells` | Select unlocked cells                                | | ||||
| | `formatCells`         | Format cells                                         | | ||||
| | `formatColumns`       | Format columns                                       | | ||||
| | `formatRows`          | Format rows                                          | | ||||
| | `insertColumns`       | Insert columns                                       | | ||||
| | `insertRows`          | Insert rows                                          | | ||||
| | `insertHyperlinks`    | Insert hyperlinks                                    | | ||||
| | `deleteColumns`       | Delete columns                                       | | ||||
| | `deleteRows`          | Delete rows                                          | | ||||
| | `sort`                | Sort                                                 | | ||||
| | `autoFilter`          | Filter                                               | | ||||
| | `pivotTables`         | Use PivotTable reports                               | | ||||
| | `objects`             | Edit objects                                         | | ||||
| | `scenarios`           | Edit scenarios                                       | | ||||
| | key                   | feature (true=disabled / false=enabled) | default    | | ||||
| |:----------------------|:----------------------------------------|:-----------| | ||||
| | `selectLockedCells`   | Select locked cells                     | enabled    | | ||||
| | `selectUnlockedCells` | Select unlocked cells                   | enabled    | | ||||
| | `formatCells`         | Format cells                            | disabled   | | ||||
| | `formatColumns`       | Format columns                          | disabled   | | ||||
| | `formatRows`          | Format rows                             | disabled   | | ||||
| | `insertColumns`       | Insert columns                          | disabled   | | ||||
| | `insertRows`          | Insert rows                             | disabled   | | ||||
| | `insertHyperlinks`    | Insert hyperlinks                       | disabled   | | ||||
| | `deleteColumns`       | Delete columns                          | disabled   | | ||||
| | `deleteRows`          | Delete rows                             | disabled   | | ||||
| | `sort`                | Sort                                    | disabled   | | ||||
| | `autoFilter`          | Filter                                  | disabled   | | ||||
| | `pivotTables`         | Use PivotTable reports                  | disabled   | | ||||
| | `objects`             | Edit objects                            | enabled    | | ||||
| | `scenarios`           | Edit scenarios                          | enabled    | | ||||
| 
 | ||||
| - `ws['!autofilter']`: AutoFilter object following the schema: | ||||
| 
 | ||||
|  | ||||
| @ -77,3 +77,4 @@ Since Excel prohibits named cells from colliding with names of A1 or RC style | ||||
| cell references, a (not-so-simple) regex conversion is possible.  BIFF Parsed | ||||
| formulae have to be explicitly unwound.  OpenFormula formulae can be converted | ||||
| with regexes for the most part. | ||||
| 
 | ||||
|  | ||||
| @ -16,10 +16,11 @@ objects which have the following properties: | ||||
| 
 | ||||
| ```typescript | ||||
| type ColInfo = { | ||||
| 	MDW?:number;  // Excel's "Max Digit Width" unit, always integral | ||||
| 	width:number; // width in Excel's "Max Digit Width", width*256 is integral | ||||
| 	wpx?:number;  // width in screen pixels | ||||
| 	wch?:number;  // intermediate character calculation | ||||
| 	MDW?:number;     // Excel's "Max Digit Width" unit, always integral | ||||
| 	width:number;    // width in Excel's "Max Digit Width", width*256 is integral | ||||
| 	wpx?:number;     // width in screen pixels | ||||
| 	wch?:number;     // intermediate character calculation | ||||
| 	hidden:?boolean; // if true, the column is hidden | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| @ -30,3 +31,26 @@ follow the priority order: | ||||
| 2) use `wpx` pixel width if available | ||||
| 3) use `wch` character count if available | ||||
| 
 | ||||
| #### Row Properties | ||||
| 
 | ||||
| Excel internally stores row heights in points.  The default resolution is 72 DPI | ||||
| or 96 PPI, so the pixel and point size should agree.  For different resolutions | ||||
| they may not agree, so the library separates the concepts. | ||||
| 
 | ||||
| The `!rows` array in each worksheet, if present, is a collection of `RowInfo` | ||||
| objects which have the following properties: | ||||
| 
 | ||||
| ```typescript | ||||
| type RowInfo = { | ||||
| 	hpx?:number;     // height in screen pixels | ||||
| 	hpt?:number;     // height in points | ||||
| 	hidden:?boolean; // if true, the row is hidden | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| Even though all of the information is made available, writers are expected to | ||||
| follow the priority order: | ||||
| 
 | ||||
| 1) use `hpx` pixel height if available | ||||
| 2) use `hpt` point height if available | ||||
| 
 | ||||
| @ -3,6 +3,25 @@ | ||||
| Due to the precarious nature of the Open Specifications Promise, it is very | ||||
| important to ensure code is cleanroom.  Consult CONTRIBUTING.md | ||||
| 
 | ||||
| ### Tests | ||||
| 
 | ||||
| The `test_misc` target (`make test_misc` on Linux/OSX / `make misc` on Windows) | ||||
| runs the targeted feature tests.  It should take 5-10 seconds to perform feature | ||||
| tests without testing against the entire test battery.  New features should be | ||||
| accompanied with tests for the relevant file formats and features. | ||||
| 
 | ||||
| For tests involving the read side, an appropriate feature test would involve | ||||
| reading an existing file and checking the resulting workbook object.  If a | ||||
| parameter is involved, files should be read with different values for the param | ||||
| to verify that the feature is working as expected. | ||||
| 
 | ||||
| For tests involving a new write feature which can already be parsed, appropriate | ||||
| feature tests would involve writing a workbook with the feature and then opening | ||||
| and verifying that the feature is preserved. | ||||
| 
 | ||||
| For tests involving a new write feature without an existing read ability, please | ||||
| add a feature test to the kitchen sink `tests/write.js`. | ||||
| 
 | ||||
| ### OSX/Linux | ||||
| 
 | ||||
| The xlsx.js file is constructed from the files in the `bits` subdirectory. The | ||||
|  | ||||
| @ -29,6 +29,7 @@ | ||||
|   * [Document Features](README.md#document-features) | ||||
|     + [Formulae](README.md#formulae) | ||||
|     + [Column Properties](README.md#column-properties) | ||||
|     + [Row Properties](README.md#row-properties) | ||||
|     + [Hyperlinks](README.md#hyperlinks) | ||||
|     + [Cell Comments](README.md#cell-comments) | ||||
|     + [Sheet Visibility](README.md#sheet-visibility) | ||||
| @ -69,6 +70,7 @@ | ||||
|   * [Tested Environments](README.md#tested-environments) | ||||
|   * [Test Files](README.md#test-files) | ||||
| - [Contributing](README.md#contributing) | ||||
|   * [Tests](README.md#tests) | ||||
|   * [OSX/Linux](README.md#osxlinux) | ||||
|   * [Windows](README.md#windows) | ||||
| - [License](README.md#license) | ||||
|  | ||||
							
								
								
									
										117
									
								
								test.js
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										117
									
								
								test.js
									
									
									
									
									
								
							| @ -54,11 +54,12 @@ var paths = { | ||||
| 	cstxlsb: dir + 'comments_stress_test.xlsb', | ||||
| 	cstods: dir + 'comments_stress_test.ods', | ||||
| 
 | ||||
| 	cwxls:  dir + 'column_width.xlsx', | ||||
| 	cwxls:  dir + 'column_width.xls', | ||||
| 	cwxls5:  dir + 'column_width.biff5', | ||||
| 	cwxml:  dir + 'column_width.xml', | ||||
| 	cwxlsx:  dir + 'column_width.xlsx', | ||||
| 	cwxlsb:  dir + 'column_width.xlsx', | ||||
| 	cwxlsb:  dir + 'column_width.xlsb', | ||||
| 	cwslk:  dir + 'column_width.slk', | ||||
| 
 | ||||
| 	dnsxls: dir + 'defined_names_simple.xls', | ||||
| 	dnsxml: dir + 'defined_names_simple.xml', | ||||
| @ -101,6 +102,13 @@ var paths = { | ||||
| 	pmxlsx: dir + 'page_margins_2016.xlsx', | ||||
| 	pmxlsb: dir + 'page_margins_2016.xlsb', | ||||
| 
 | ||||
| 	rhxls:  dir + 'row_height.xls', | ||||
| 	rhxls5:  dir + 'row_height.biff5', | ||||
| 	rhxml:  dir + 'row_height.xml', | ||||
| 	rhxlsx:  dir + 'row_height.xlsx', | ||||
| 	rhxlsb:  dir + 'row_height.xlsb', | ||||
| 	rhslk:  dir + 'row_height.slk', | ||||
| 
 | ||||
| 	svxls:  dir + 'sheet_visibility.xls', | ||||
| 	svxls5: dir + 'sheet_visibility.xls', | ||||
| 	svxml:  dir + 'sheet_visibility.xml', | ||||
| @ -113,6 +121,10 @@ var paths = { | ||||
| 	swcxlsb: dir + '2013/apachepoi_SimpleWithComments.xlsx.xlsb' | ||||
| }; | ||||
| 
 | ||||
| var FSTPaths = [paths.fstxls, paths.fstxml, paths.fstxlsx, paths.fstxlsb, paths.fstods]; | ||||
| var NFPaths = [paths.nfxls, paths.nfxml, paths.nfxlsx, paths.nfxlsb]; | ||||
| var DTPaths = [paths.dtxls, paths.dtxml, paths.dtxlsx, paths.dtxlsb]; | ||||
| 
 | ||||
| var N1 = 'XLSX'; | ||||
| var N2 = 'XLSB'; | ||||
| var N3 = 'XLS'; | ||||
| @ -144,7 +156,7 @@ function parsetest(x, wb, full, ext) { | ||||
| 	describe(x + ext + ' should generate JSON', function() { | ||||
| 		wb.SheetNames.forEach(function(ws, i) { | ||||
| 			it('#' + i + ' (' + ws + ')', function() { | ||||
| 				X.utils.sheet_to_row_object_array(wb.Sheets[ws]); | ||||
| 				X.utils.sheet_to_json(wb.Sheets[ws]); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| @ -284,7 +296,6 @@ describe('parse options', function() { | ||||
| 	if(typeof before != 'undefined') before(bef); | ||||
| 	else it('before', bef); | ||||
| 	describe('cell', function() { | ||||
| 		var FSTPaths = [paths.fstxls, paths.fstxml, paths.fstxlsx, paths.fstxlsb, paths.fstods]; | ||||
| 		it('XLSX should generate HTML by default', function() { | ||||
| 			var wb = X.readFile(paths.cstxlsx); | ||||
| 			var ws = wb.Sheets.Sheet1; | ||||
| @ -348,7 +359,7 @@ describe('parse options', function() { | ||||
| 			}); | ||||
| 		}); | ||||
| 		it('should not generate number formats by default', function() { | ||||
| 			[paths.nfxls, paths.nfxlsx, paths.nfxlsb].forEach(function(p) { | ||||
| 			NFPaths.forEach(function(p) { | ||||
| 				var wb = X.readFile(p); | ||||
| 				wb.SheetNames.forEach(function(s) { | ||||
| 					var ws = wb.Sheets[s]; | ||||
| @ -359,7 +370,7 @@ describe('parse options', function() { | ||||
| 			}); | ||||
| 		}); | ||||
| 		it('should generate number formats when requested', function() { | ||||
| 			[paths.nfxls, paths.nfxlsx, paths.nfxlsb].forEach(function(p) { | ||||
| 			NFPaths.forEach(function(p) { | ||||
| 				var wb = X.readFile(p, {cellNF: true}); | ||||
| 				wb.SheetNames.forEach(function(s) { | ||||
| 					var ws = wb.Sheets[s]; | ||||
| @ -395,7 +406,7 @@ describe('parse options', function() { | ||||
| 			}); | ||||
| 		}); | ||||
| 		it('should not generate cell dates by default', function() { | ||||
| 			[paths.dtxlsx, paths.dtxlsb, paths.dtxls, paths.dtxml].forEach(function(p) { | ||||
| 			DTPaths.forEach(function(p) { | ||||
| 				var wb = X.readFile(p); | ||||
| 				wb.SheetNames.forEach(function(s) { | ||||
| 					var ws = wb.Sheets[s]; | ||||
| @ -405,8 +416,8 @@ describe('parse options', function() { | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 		it('XLSX should generate cell dates when requested', function() { | ||||
| 			[paths.dtxlsx, paths.dtxlsb, paths.dtxls, paths.dtxml].forEach(function(p) { | ||||
| 		it('should generate cell dates when requested', function() { | ||||
| 			DTPaths.forEach(function(p) { | ||||
| 				var wb = X.readFile(paths.dtxlsx, {cellDates: true}); | ||||
| 				var found = false; | ||||
| 				wb.SheetNames.forEach(function(s) { | ||||
| @ -818,7 +829,7 @@ describe('parse features', function() { | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('column properties', function() { | ||||
| 		var wb1, wb2, wb3, wb4, wb5; | ||||
| 		var wb1, wb2, wb3, wb4, wb5, wb6; | ||||
| 		var bef = (function() { | ||||
| 			X = require(modp); | ||||
| 			wb1 = X.readFile(paths.cwxlsx, {cellStyles:true}); | ||||
| @ -826,21 +837,21 @@ describe('parse features', function() { | ||||
| 			wb3 = X.readFile(paths.cwxls, {cellStyles:true}); | ||||
| 			wb4 = X.readFile(paths.cwxls5, {cellStyles:true}); | ||||
| 			wb5 = X.readFile(paths.cwxml, {cellStyles:true}); | ||||
| 			wb6 = X.readFile(paths.cwslk, {cellStyles:true}); | ||||
| 		}); | ||||
| 		if(typeof before != 'undefined') before(bef); | ||||
| 		else it('before', bef); | ||||
| 		it('should have "!cols"', function() { | ||||
| 			assert(wb1.Sheets.Sheet1['!cols']); | ||||
| 			assert(wb2.Sheets.Sheet1['!cols']); | ||||
| 			assert(wb3.Sheets.Sheet1['!cols']); | ||||
| 			assert(wb4.Sheets.Sheet1['!cols']); | ||||
| 			assert(wb5.Sheets.Sheet1['!cols']); | ||||
| 			[wb1, wb2, wb3, wb4, wb5, wb6].forEach(function(wb) { assert(wb.Sheets.Sheet1['!cols']); }); | ||||
| 		}); | ||||
| 		it('should have correct widths', function() { | ||||
| 			/* SYLK rounds wch so skip non-integral */ | ||||
| 			[wb1, wb2, wb3, wb4, wb5].map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) { | ||||
| 				assert.equal(x[1].width, 0.1640625); | ||||
| 				assert.equal(x[2].width, 16.6640625); | ||||
| 				assert.equal(x[3].width, 1.6640625); | ||||
| 			}); | ||||
| 			[wb1, wb2, wb3, wb4, wb5, wb6].map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) { | ||||
| 				assert.equal(x[4].width, 4.83203125); | ||||
| 				assert.equal(x[5].width, 8.83203125); | ||||
| 				assert.equal(x[6].width, 12.83203125); | ||||
| @ -848,10 +859,13 @@ describe('parse features', function() { | ||||
| 			}); | ||||
| 		}); | ||||
| 		it('should have correct pixels', function() { | ||||
| 			/* SYLK rounds wch so skip non-integral */ | ||||
| 			[wb1, wb2, wb3, wb4, wb5].map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) { | ||||
| 				assert.equal(x[1].wpx, 1); | ||||
| 				assert.equal(x[2].wpx, 100); | ||||
| 				assert.equal(x[3].wpx, 10); | ||||
| 			}); | ||||
| 			[wb1, wb2, wb3, wb4, wb5, wb6].map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) { | ||||
| 				assert.equal(x[4].wpx, 29); | ||||
| 				assert.equal(x[5].wpx, 53); | ||||
| 				assert.equal(x[6].wpx, 77); | ||||
| @ -860,6 +874,39 @@ describe('parse features', function() { | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('row properties', function() { | ||||
| 		var wb1, wb2, wb3, wb4, wb5, wb6; | ||||
| 		var bef = (function() { | ||||
| 			X = require(modp); | ||||
| 			wb1 = X.readFile(paths.rhxlsx, {cellStyles:true}); | ||||
| 			wb2 = X.readFile(paths.rhxlsb, {cellStyles:true}); | ||||
| 			wb3 = X.readFile(paths.rhxls, {cellStyles:true}); | ||||
| 			wb4 = X.readFile(paths.rhxls5, {cellStyles:true}); | ||||
| 			wb5 = X.readFile(paths.rhxml, {cellStyles:true}); | ||||
| 			wb6 = X.readFile(paths.rhslk, {cellStyles:true}); | ||||
| 		}); | ||||
| 		if(typeof before != 'undefined') before(bef); | ||||
| 		else it('before', bef); | ||||
| 		it('should have "!rows"', function() { | ||||
| 			[wb1, wb2, wb3, wb4, wb5, wb6].forEach(function(wb) { assert(wb.Sheets.Sheet1['!rows']); }); | ||||
| 		}); | ||||
| 		it('should have correct points', function() { | ||||
| 			[wb1, wb2, wb3, wb4, wb5, wb6].map(function(x) { return x.Sheets.Sheet1['!rows']; }).forEach(function(x) { | ||||
| 				assert.equal(x[1].hpt, 1); | ||||
| 				assert.equal(x[2].hpt, 10); | ||||
| 				assert.equal(x[3].hpt, 100); | ||||
| 			}); | ||||
| 		}); | ||||
| 		it('should have correct pixels', function() { | ||||
| 			[wb1, wb2, wb3, wb4, wb5, wb6].map(function(x) { return x.Sheets.Sheet1['!rows']; }).forEach(function(x) { | ||||
| 				/* note: at 96 PPI hpt == hpx */ | ||||
| 				assert.equal(x[1].hpx, 1); | ||||
| 				assert.equal(x[2].hpx, 10); | ||||
| 				assert.equal(x[3].hpx, 100); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('merge cells',function() { | ||||
| 		var wb1, wb2, wb3, wb4, wb5; | ||||
| 		var bef = (function() { | ||||
| @ -910,7 +957,7 @@ describe('parse features', function() { | ||||
| 			var sheetName = 'Sheet1'; | ||||
| 			wb = X.readFile(paths.dtxlsx); | ||||
| 			ws = wb.Sheets[sheetName]; | ||||
| 			var sheet = X.utils.sheet_to_row_object_array(ws); | ||||
| 			var sheet = X.utils.sheet_to_json(ws); | ||||
| 			assert.equal(sheet[3]['てすと'], '2/14/14'); | ||||
| 		}); | ||||
| 		it('cellDates should not affect formatted text', function() { | ||||
| @ -1210,9 +1257,8 @@ describe('roundtrip features', function() { | ||||
| 		}); | ||||
| 	}); }); | ||||
| 
 | ||||
| 	describe('should preserve features', function() { | ||||
| 		it('merge cells', function() { | ||||
| 		["xlsx", "xlsb", "xlml", "ods"].forEach(function(f) { | ||||
| 	describe('should preserve merge cells', function() { | ||||
| 		["xlsx", "xlsb", "xlml", "ods"].forEach(function(f) { it(f, function() { | ||||
| 			var wb1 = X.readFile(paths.mcxlsx); | ||||
| 			var wb2 = X.read(X.write(wb1,{bookType:f,type:'binary'}),{type:'binary'}); | ||||
| 			var m1 = wb1.Sheets.Merge['!merges'].map(X.utils.encode_range); | ||||
| @ -1311,6 +1357,39 @@ describe('roundtrip features', function() { | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('should preserve column properties', function() { [ | ||||
| 			'xlml', /*'biff2', */ 'xlsx', 'xlsb', 'slk' | ||||
| 		].forEach(function(w) { it(w, function() { | ||||
| 				var ws1 = X.utils.aoa_to_sheet([["hpx12", "hpt24", "hpx48", "hidden"]]); | ||||
| 				ws1['!cols'] = [{wch:9},{wpx:100},{width:80},{hidden:true}]; | ||||
| 				var wb1 = {SheetNames:["Sheet1"], Sheets:{Sheet1:ws1}}; | ||||
| 				var wb2 = X.read(X.write(wb1, {bookType:w, type:"buffer"}), {type:"buffer", cellStyles:true}); | ||||
| 				var ws2 = wb2.Sheets.Sheet1; | ||||
| 				assert.equal(ws2['!cols'][3].hidden, true); | ||||
| 				assert.equal(ws2['!cols'][0].wch, 9); | ||||
| 				if(w == 'slk') return; | ||||
| 				assert.equal(ws2['!cols'][1].wpx, 100); | ||||
| 				/* xlml stores integral pixels -> approximate width */ | ||||
| 				if(w == 'xlml') assert.equal(Math.round(ws2['!cols'][2].width), 80); | ||||
| 				else assert.equal(ws2['!cols'][2].width, 80); | ||||
| 		}); }); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('should preserve row properties', function() { [ | ||||
| 			'xlml', /*'biff2', */ 'xlsx', 'xlsb', 'slk' | ||||
| 		].forEach(function(w) { it(w, function() { | ||||
| 				var ws1 = X.utils.aoa_to_sheet([["hpx12"],["hpt24"],["hpx48"],["hidden"]]); | ||||
| 				ws1['!rows'] = [{hpx:12},{hpt:24},{hpx:48},{hidden:true}]; | ||||
| 				var wb1 = {SheetNames:["Sheet1"], Sheets:{Sheet1:ws1}}; | ||||
| 				var wb2 = X.read(X.write(wb1, {bookType:w, type:"buffer"}), {type:"buffer", cellStyles:true}); | ||||
| 				var ws2 = wb2.Sheets.Sheet1; | ||||
| 				assert.equal(ws2['!rows'][0].hpx, 12); | ||||
| 				assert.equal(ws2['!rows'][1].hpt, 24); | ||||
| 				assert.equal(ws2['!rows'][2].hpx, 48); | ||||
| 				assert.equal(ws2['!rows'][3].hidden, true); | ||||
| 		}); }); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('should preserve cell comments', function() { [ | ||||
| 			['xlsx', paths.cstxlsx], | ||||
| 			['xlsb', paths.cstxlsb], | ||||
|  | ||||
							
								
								
									
										114
									
								
								tests/core.js
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										114
									
								
								tests/core.js
									
									
									
									
									
								
							| @ -80,6 +80,17 @@ var N2 = 'XLSB'; | ||||
| var N3 = 'XLS'; | ||||
| var N4 = 'XML'; | ||||
| 
 | ||||
| function get_cell(ws/*:Worksheet*/, addr/*:string*/) { | ||||
| 	if(!Array.isArray(ws)) return ws[addr]; | ||||
| 	var a = X.utils.decode_cell(addr); | ||||
| 	return (ws[a.r]||[])[a.c]; | ||||
| } | ||||
| 
 | ||||
| function each_cell(ws, f) { | ||||
| 	if(Array.isArray(ws)) ws.forEach(function(row) { if(row) row.forEach(f); }); | ||||
| 	else Object.keys(ws).forEach(function(addr) { if(addr[0] === "!" || !ws.hasOwnProperty(addr)) return; f(ws[addr]); }); | ||||
| } | ||||
| 
 | ||||
| /* comments_stress_test family */ | ||||
| function check_comments(wb) { | ||||
| 	var ws0 = wb.Sheets.Sheet2; | ||||
| @ -516,12 +527,12 @@ describe('parse features', function() { | ||||
| 			var wb4=X.read(fs.readFileSync(paths.swcxml), {type:"binary"}); | ||||
| 
 | ||||
| 			[wb1,wb2,wb3,wb4].map(function(wb) { return wb.Sheets[sheet]; }).forEach(function(ws, i) { | ||||
| 				assert.equal(ws.B1.c.length, 1,"must have 1 comment"); | ||||
| 				assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author"); | ||||
| 				assert.equal(ws.B1.c[0].t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"), "Yegor Kozlov:\nfirst cell", "must have the concatenated texts"); | ||||
| 				assert.equal(get_cell(ws, "B1").c.length, 1,"must have 1 comment"); | ||||
| 				assert.equal(get_cell(ws, "B1").c[0].a, "Yegor Kozlov","must have the same author"); | ||||
| 				assert.equal(get_cell(ws, "B1").c[0].t, "Yegor Kozlov:\nfirst cell", "must have the concatenated texts"); | ||||
| 				if(i > 0) return; | ||||
| 				assert.equal(ws.B1.c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation"); | ||||
| 				assert.equal(ws.B1.c[0].h, '<span style="font-weight: bold;">Yegor Kozlov:</span><span style=""><br/>first cell</span>', "must have the html representation"); | ||||
| 				assert.equal(get_cell(ws, "B1").c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation"); | ||||
| 				assert.equal(get_cell(ws, "B1").c[0].h, '<span style="font-size:8;"><b>Yegor Kozlov:</b></span><span style="font-size:8;"><br/>first cell</span>', "must have the html representation"); | ||||
| 			}); | ||||
| 		}); | ||||
| 		[ | ||||
| @ -612,11 +623,7 @@ describe('parse features', function() { | ||||
| 		if(typeof before != 'undefined') before(bef); | ||||
| 		else it('before', bef); | ||||
| 		it('should have "!cols"', function() { | ||||
| 			assert(wb1.Sheets.Sheet1['!cols']); | ||||
| 			assert(wb2.Sheets.Sheet1['!cols']); | ||||
| 			assert(wb3.Sheets.Sheet1['!cols']); | ||||
| 			assert(wb4.Sheets.Sheet1['!cols']); | ||||
| 			assert(wb5.Sheets.Sheet1['!cols']); | ||||
| 			[wb1, wb2, wb3, wb4, wb5].forEach(function(wb) { assert(wb.Sheets.Sheet1['!cols']); }); | ||||
| 		}); | ||||
| 		it('should have correct widths', function() { | ||||
| 			[wb1, wb2, wb3, wb4, wb5].map(function(x) { return x.Sheets.Sheet1['!cols']; }).forEach(function(x) { | ||||
| @ -809,6 +816,56 @@ 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:"binary"}), {type:"binary"}); | ||||
| 				Object.keys(baseprops).forEach(function(k) { assert.equal(baseprops[k], wb2.Props[k]); }); | ||||
| 				var wb3 = X.read(X.write(wb2, {bookType:w, type:"binary", Props: {Author:"SheetJS"}}), {type:"binary"}); | ||||
| 				assert.equal("SheetJS", wb3.Props.Author); | ||||
| 			}); }); | ||||
| 		}); | ||||
| 	}); | ||||
| 	describe('HTML', function() { | ||||
| 		it('should use `h` value when present', function() { | ||||
| 			var sheet = X.utils.aoa_to_sheet([["abc"]]); | ||||
| 			get_cell(sheet, "A1").h = "<b>abc</b>"; | ||||
| 			var wb = {SheetNames:["Sheet1"], Sheets:{Sheet1:sheet}}; | ||||
| 			var str = X.write(wb, {bookType:"html", type:"binary"}); | ||||
| 			assert(str.indexOf("<b>abc</b>") > 0); | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
| 
 | ||||
| function seq(end, start) { | ||||
| 	var s = start || 0; | ||||
| 	var o = new Array(end - s); | ||||
| @ -895,8 +952,8 @@ describe('roundtrip features', function() { | ||||
| 
 | ||||
| 	describe('should preserve hyperlink', function() { [ | ||||
| 			['xlml', paths.hlxml], | ||||
| 			//['xlsx', paths.hlxlsx], // TODO
 | ||||
| 			//['xlsb', paths.hlxlsb] // TODO
 | ||||
| 			['xlsx', paths.hlxlsx], | ||||
| 			['xlsb', paths.hlxlsb] | ||||
| 		].forEach(function(w) { | ||||
| 			it(w[0], function() { | ||||
| 				var wb1 = X.read(fs.readFileSync(w[1]), {type:"binary"}); | ||||
| @ -926,6 +983,39 @@ describe('roundtrip features', function() { | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('should preserve column properties', function() { [ | ||||
| 			'xlml', /*'biff2', */ 'xlsx', 'xlsb', 'slk' | ||||
| 		].forEach(function(w) { it(w, function() { | ||||
| 				var ws1 = X.utils.aoa_to_sheet([["hpx12", "hpt24", "hpx48", "hidden"]]); | ||||
| 				ws1['!cols'] = [{wch:9},{wpx:100},{width:80},{hidden:true}]; | ||||
| 				var wb1 = {SheetNames:["Sheet1"], Sheets:{Sheet1:ws1}}; | ||||
| 				var wb2 = X.read(X.write(wb1, {bookType:w, type:"binary"}), {type:"binary", cellStyles:true}); | ||||
| 				var ws2 = wb2.Sheets.Sheet1; | ||||
| 				assert.equal(ws2['!cols'][3].hidden, true); | ||||
| 				assert.equal(ws2['!cols'][0].wch, 9); | ||||
| 				if(w == 'slk') return; | ||||
| 				assert.equal(ws2['!cols'][1].wpx, 100); | ||||
| 				/* xlml stores integral pixels -> approximate width */ | ||||
| 				if(w == 'xlml') assert.equal(Math.round(ws2['!cols'][2].width), 80); | ||||
| 				else assert.equal(ws2['!cols'][2].width, 80); | ||||
| 		}); }); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('should preserve row properties', function() { [ | ||||
| 			'xlml', /*'biff2', */ 'xlsx', 'xlsb', 'slk' | ||||
| 		].forEach(function(w) { it(w, function() { | ||||
| 				var ws1 = X.utils.aoa_to_sheet([["hpx12"],["hpt24"],["hpx48"],["hidden"]]); | ||||
| 				ws1['!rows'] = [{hpx:12},{hpt:24},{hpx:48},{hidden:true}]; | ||||
| 				var wb1 = {SheetNames:["Sheet1"], Sheets:{Sheet1:ws1}}; | ||||
| 				var wb2 = X.read(X.write(wb1, {bookType:w, type:"binary"}), {type:"binary", cellStyles:true}); | ||||
| 				var ws2 = wb2.Sheets.Sheet1; | ||||
| 				assert.equal(ws2['!rows'][0].hpx, 12); | ||||
| 				assert.equal(ws2['!rows'][1].hpt, 24); | ||||
| 				assert.equal(ws2['!rows'][2].hpx, 48); | ||||
| 				assert.equal(ws2['!rows'][3].hidden, true); | ||||
| 		}); }); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe('should preserve cell comments', function() { [ | ||||
| 			['xlsx', paths.cstxlsx], | ||||
| 			['xlsb', paths.cstxlsb], | ||||
|  | ||||
| @ -16,14 +16,15 @@ var ws_name = "SheetJS"; | ||||
| var wscols = [ | ||||
| 	{wch:6}, // "characters"
 | ||||
| 	{wpx:50}, // "pixels"
 | ||||
| 	{wch:10}, | ||||
| 	{wpx:125} | ||||
| 	/*{wch:10}*/, | ||||
| 	{hidden:true} | ||||
| ]; | ||||
| 
 | ||||
| /* At 96 PPI, 1 pt = 1 px */ | ||||
| var wsrows = []; | ||||
| wsrows[0] = {hpt: 12}; // "points"
 | ||||
| wsrows[1] = {hpx: 16}; // "pixels"
 | ||||
| wsrows[2] = {hpt: 18}; | ||||
| //wsrows[2] = {hpt: 18};
 | ||||
| wsrows[3] = {hpx: 24}; | ||||
| wsrows[4] = {hidden:true}; // hide row
 | ||||
| wsrows[5] = {hidden:false}; | ||||
| @ -108,6 +109,10 @@ ws['A4'].c.push({a:"SheetJS",t:"I'm a little comment, short and stout!\n\nWell, | ||||
| /* TEST: sheet protection */ | ||||
| ws['!protect'] = { | ||||
| 	password:"password", | ||||
| 	/* enable formatting rows and columns */ | ||||
| 	formatRows:0, | ||||
| 	formatColumns:0, | ||||
| 	/* disable editing objects and scenarios */ | ||||
| 	objects:1, | ||||
| 	scenarios:1 | ||||
| }; | ||||
|  | ||||
							
								
								
									
										265
									
								
								xlsx.flow.js
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										265
									
								
								xlsx.flow.js
									
									
									
									
									
								
							| @ -4069,14 +4069,19 @@ function parse_ExtSST(blob, length) { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* 2.4.221 TODO*/ | ||||
| /* 2.4.221 TODO: check BIFF2-4 */ | ||||
| function parse_Row(blob, length) { | ||||
| 	var rw = blob.read_shift(2), col = blob.read_shift(2), Col = blob.read_shift(2), rht = blob.read_shift(2); | ||||
| 	blob.read_shift(4); // reserved(2), unused(2)
 | ||||
| 	var z = ({}/*:any*/); | ||||
| 	z.r = blob.read_shift(2); | ||||
| 	z.c = blob.read_shift(2); | ||||
| 	z.cnt = blob.read_shift(2) - z.c; | ||||
| 	var miyRw = blob.read_shift(2); | ||||
| 	blob.l += 4; // reserved(2), unused(2)
 | ||||
| 	var flags = blob.read_shift(1); // various flags
 | ||||
| 	blob.read_shift(1); // reserved
 | ||||
| 	blob.read_shift(2); //ixfe, other flags
 | ||||
| 	return {r:rw, c:col, cnt:Col-col}; | ||||
| 	blob.l += 3; // reserved(8), ixfe(12), flags(4)
 | ||||
| 	if(flags & 0x20) z.hidden = true; | ||||
| 	if(flags & 0x40) z.hpt = miyRw / 20; | ||||
| 	return z; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -5113,19 +5118,19 @@ var SYLK = (function() { | ||||
| 		var records = str.split(/[\n\r]+/), R = -1, C = -1, ri = 0, rj = 0, arr = []; | ||||
| 		var formats = []; | ||||
| 		var next_cell_format = null; | ||||
| 		var sht = {}, rowinfo = [], colinfo = [], cw = []; | ||||
| 		var Mval = 0, j; | ||||
| 		for (; ri !== records.length; ++ri) { | ||||
| 			Mval = 0; | ||||
| 			var record = records[ri].trim().split(";"); | ||||
| 			var RT = record[0], val; | ||||
| 			if(RT === 'P') for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 				case 'P': | ||||
| 					formats.push(record[rj].substr(1)); | ||||
| 					break; | ||||
| 			} | ||||
| 			else if(RT !== 'C' && RT !== 'F') continue; | ||||
| 			else for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 			switch(RT) { | ||||
| 			case 'P': if(record[1].charAt(0) == 'P') formats.push(records[ri].trim().substr(3).replace(/;;/g, ";")); | ||||
| 				break; | ||||
| 			case 'C': case 'F': for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 				case 'Y': | ||||
| 					R = parseInt(record[rj].substr(1))-1; C = 0; | ||||
| 					for(var j = arr.length; j <= R; ++j) arr[j] = []; | ||||
| 					for(j = arr.length; j <= R; ++j) arr[j] = []; | ||||
| 					break; | ||||
| 				case 'X': C = parseInt(record[rj].substr(1))-1; break; | ||||
| 				case 'K': | ||||
| @ -5135,7 +5140,7 @@ var SYLK = (function() { | ||||
| 					else if(val === 'FALSE') val = false; | ||||
| 					else if(+val === +val) { | ||||
| 						val = +val; | ||||
| 						if(next_cell_format !== null && next_cell_format.match(/[ymdhmsYMDHMS]/)) val = numdate(val); | ||||
| 						if(next_cell_format !== null && SSF.is_date(next_cell_format)) val = numdate(val); | ||||
| 					} | ||||
| 					arr[R][C] = val; | ||||
| 					next_cell_format = null; | ||||
| @ -5143,12 +5148,37 @@ var SYLK = (function() { | ||||
| 				case 'P': | ||||
| 					if(RT !== 'F') break; | ||||
| 					next_cell_format = formats[parseInt(record[rj].substr(1))]; | ||||
| 					break; | ||||
| 				case 'M': Mval = parseInt(record[rj].substr(1)) / 20; break; | ||||
| 				case 'W': | ||||
| 					if(RT !== 'F') break; | ||||
| 					cw = record[rj].substr(1).split(" "); | ||||
| 					for(j = parseInt(cw[0], 10); j <= parseInt(cw[1], 10); ++j) { | ||||
| 						Mval = parseInt(cw[2], 10); | ||||
| 						colinfo[j-1] = Mval == 0 ? {hidden:true}: {wch:Mval}; process_col(colinfo[j-1]); | ||||
| 					} break; | ||||
| 				case 'R': | ||||
| 					R = parseInt(record[rj].substr(1))-1; | ||||
| 					rowinfo[R] = {}; | ||||
| 					if(Mval > 0) { rowinfo[R].hpt = Mval; rowinfo[R].hpx = pt2px(Mval); } | ||||
| 					else if(Mval == 0) rowinfo[R].hidden = true; | ||||
| 			} break; | ||||
| 			default: break; | ||||
| 			} | ||||
| 		} | ||||
| 		if(rowinfo.length > 0) sht['!rows'] = rowinfo; | ||||
| 		if(colinfo.length > 0) sht['!cols'] = colinfo; | ||||
| 		arr[arr.length] = sht; | ||||
| 		return arr; | ||||
| 	} | ||||
| 
 | ||||
| 	function sylk_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(sylk_to_aoa(str, opts), opts); } | ||||
| 	function sylk_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { | ||||
| 		var aoa = sylk_to_aoa(str, opts); | ||||
| 		var ws = aoa.pop(); | ||||
| 		var o = aoa_to_sheet(aoa, opts); | ||||
| 		keys(ws).forEach(function(k) { o[k] = ws[k]; }); | ||||
| 		return o; | ||||
| 	} | ||||
| 
 | ||||
| 	function sylk_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(sylk_to_sheet(str, opts), opts); } | ||||
| 
 | ||||
| @ -5164,11 +5194,40 @@ var SYLK = (function() { | ||||
| 		return o; | ||||
| 	} | ||||
| 
 | ||||
| 	function write_ws_cols_sylk(out, cols) { | ||||
| 		cols.forEach(function(col, i) { | ||||
| 			var rec = "F;W" + (i+1) + " " + (i+1) + " "; | ||||
| 			if(col.hidden) rec += "0"; | ||||
| 			else { | ||||
| 				if(typeof col.width == 'number') col.wpx = width2px(col.width); | ||||
| 				if(typeof col.wpx == 'number') col.wch = px2char(col.wpx); | ||||
| 				if(typeof col.wch == 'number') rec += Math.round(col.wch); | ||||
| 			} | ||||
| 			if(rec.charAt(rec.length - 1) != " ") out.push(rec); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	function write_ws_rows_sylk(out, rows) { | ||||
| 		rows.forEach(function(row, i) { | ||||
| 			var rec = "F;"; | ||||
| 			if(row.hidden) rec += "M0;"; | ||||
| 			else if(row.hpt) rec += "M" + 20 * row.hpt + ";"; | ||||
| 			else if(row.hpx) rec += "M" + 20 * px2pt(row.hpx) + ";"; | ||||
| 			if(rec.length > 2) out.push(rec + "R" + (i+1)); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ { | ||||
| 		var preamble/*:Array<string>*/ = ["ID;PWXL;N;E"], o/*:Array<string>*/ = []; | ||||
| 		preamble.push("P;PGeneral"); | ||||
| 		var r = decode_range(ws['!ref']), cell/*:Cell*/; | ||||
| 		var dense = Array.isArray(ws); | ||||
| 		var RS = "\r\n"; | ||||
| 
 | ||||
| 		preamble.push("P;PGeneral"); | ||||
| 		preamble.push("F;P0;DG0G8;M255"); | ||||
| 		if(ws['!cols']) write_ws_cols_sylk(preamble, ws['!cols']); | ||||
| 		if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']); | ||||
| 
 | ||||
| 		for(var R = r.s.r; R <= r.e.r; ++R) { | ||||
| 			for(var C = r.s.c; C <= r.e.c; ++C) { | ||||
| 				var coord = encode_cell({r:R,c:C}); | ||||
| @ -5177,8 +5236,6 @@ var SYLK = (function() { | ||||
| 				o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); | ||||
| 			} | ||||
| 		} | ||||
| 		preamble.push("F;P0;DG0G8;M255"); | ||||
| 		var RS = "\r\n"; | ||||
| 		return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS; | ||||
| 	} | ||||
| 
 | ||||
| @ -6388,13 +6445,17 @@ function process_col(coll/*:ColInfo*/) { | ||||
| 		coll.wch = px2char(coll.wpx); | ||||
| 		coll.width = char2width(coll.wch); | ||||
| 		coll.MDW = MDW; | ||||
| 	} else if(typeof coll.wch == 'number') { | ||||
| 		coll.width = char2width(coll.wch); | ||||
| 		coll.wpx = width2px(coll.width); | ||||
| 		coll.MDW = MDW; | ||||
| 	} | ||||
| 	if(coll.customWidth) delete coll.customWidth; | ||||
| } | ||||
| 
 | ||||
| var DEF_DPI = 96, DPI = DEF_DPI; | ||||
| function px2pt(px) { return px * 72 / DPI; } | ||||
| function pt2px(pt) { return pt * DPI / 72; } | ||||
| var DEF_PPI = 96, PPI = DEF_PPI; | ||||
| function px2pt(px) { return px * 96 / PPI; } | ||||
| function pt2px(pt) { return pt * PPI / 96; } | ||||
| 
 | ||||
| /* [MS-EXSPXML3] 2.4.54 ST_enmPattern */ | ||||
| var XLMLPatternTypeMap = { | ||||
| @ -9922,13 +9983,14 @@ function get_sst_id(sst/*:SST*/, str/*:string*/)/*:number*/ { | ||||
| function col_obj_w(C/*:number*/, col) { | ||||
| 	var p = ({min:C+1,max:C+1}/*:any*/); | ||||
| 	/* wch (chars), wpx (pixels) */ | ||||
| 	var width = -1; | ||||
| 	var wch = -1; | ||||
| 	if(col.MDW) MDW = col.MDW; | ||||
| 	if(col.width != null) p.customWidth = 1; | ||||
| 	else if(col.wpx != null) width = px2char(col.wpx); | ||||
| 	else if(col.wch != null) width = col.wch; | ||||
| 	if(width > -1) { p.width = char2width(width); p.customWidth = 1; } | ||||
| 	else p.width = col.width; | ||||
| 	else if(col.wpx != null) wch = px2char(col.wpx); | ||||
| 	else if(col.wch != null) wch = col.wch; | ||||
| 	if(wch > -1) { p.width = char2width(wch); p.customWidth = 1; } | ||||
| 	else if(col.width != null) p.width = col.width; | ||||
| 	if(col.hidden) p.hidden = true; | ||||
| 	return p; | ||||
| } | ||||
| 
 | ||||
| @ -10148,6 +10210,7 @@ function parse_ws_xml_cols(columns, cols) { | ||||
| 	var seencol = false; | ||||
| 	for(var coli = 0; coli != cols.length; ++coli) { | ||||
| 		var coll = parsexmltag(cols[coli], true); | ||||
| 		if(coll.hidden) coll.hidden = parsexmlbool(coll.hidden); | ||||
| 		var colm=parseInt(coll.min, 10)-1, colM=parseInt(coll.max,10)-1; | ||||
| 		delete coll.min; delete coll.max; coll.width = +coll.width; | ||||
| 		if(!seencol && coll.width) { seencol = true; find_mdw_colw(coll.width); } | ||||
| @ -10174,6 +10237,12 @@ function write_ws_xml_autofilter(data)/*:string*/ { | ||||
| 	return writextag("autoFilter", null, {ref:data.ref}); | ||||
| } | ||||
| 
 | ||||
| /* 18.3.1.88 sheetViews CT_SheetViews */ | ||||
| /* 18.3.1.87 sheetView CT_SheetView */ | ||||
| function write_ws_xml_sheetviews(ws, opts, idx, wb)/*:string*/ { | ||||
| 	return writextag("sheetViews", writextag("sheetView", null, {workbookViewId:"0"}), {}); | ||||
| } | ||||
| 
 | ||||
| function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { | ||||
| 	if(cell.v === undefined && cell.f === undefined || cell.t === 'z') return ""; | ||||
| 	var vv = ""; | ||||
| @ -10225,13 +10294,14 @@ var parse_ws_xml_data = (function parse_ws_xml_data_factory() { | ||||
| 	var match_v = matchtag("v"), match_f = matchtag("f"); | ||||
| 
 | ||||
| return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 	var ri = 0, x = "", cells = [], cref = [], idx = 0, i=0, cc=0, d="", p/*:any*/; | ||||
| 	var ri = 0, x = "", cells = [], cref = [], idx=0, i=0, cc=0, d="", p/*:any*/; | ||||
| 	var tag, tagr = 0, tagc = 0; | ||||
| 	var sstr, ftag; | ||||
| 	var fmtid = 0, fillid = 0, do_format = Array.isArray(styles.CellXf), cf; | ||||
| 	var arrayf = []; | ||||
| 	var sharedf = []; | ||||
| 	var dense = Array.isArray(s); | ||||
| 	var rows = [], rowobj = {}, rowrite = false; | ||||
| 	for(var marr = sdata.split(rowregex), mt = 0, marrlen = marr.length; mt != marrlen; ++mt) { | ||||
| 		x = marr[mt].trim(); | ||||
| 		var xlen = x.length; | ||||
| @ -10245,6 +10315,13 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 		if(guess.s.r > tagr - 1) guess.s.r = tagr - 1; | ||||
| 		if(guess.e.r < tagr - 1) guess.e.r = tagr - 1; | ||||
| 
 | ||||
| 		if(opts && opts.cellStyles) { | ||||
| 			rowobj = {}; rowrite = false; | ||||
| 			if(tag.ht) { rowrite = true; rowobj.hpt = parseFloat(tag.ht); rowobj.hpx = pt2px(rowobj.hpt); } | ||||
| 			if(tag.hidden == "1") { rowrite = true; rowobj.hidden = true; } | ||||
| 			if(rowrite) rows[tagr-1] = rowobj; | ||||
| 		} | ||||
| 
 | ||||
| 		/* 18.3.1.4 c CT_Cell */ | ||||
| 		cells = x.substr(ri).split(cellregex); | ||||
| 		for(ri = 0; ri != cells.length; ++ri) { | ||||
| @ -10353,6 +10430,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 			} else s[tag.r] = p; | ||||
| 		} | ||||
| 	} | ||||
| 	if(rows.length > 0) s['!rows'] = rows; | ||||
| }; })(); | ||||
| 
 | ||||
| function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| @ -10403,7 +10481,7 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 
 | ||||
| 	o[o.length] = (writextag('dimension', null, {'ref': ref})); | ||||
| 
 | ||||
| 	/* sheetViews */ | ||||
| 	o[o.length] = write_ws_xml_sheetviews(ws, opts, idx, wb); | ||||
| 
 | ||||
| 	/* 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' })); | ||||
| @ -10453,7 +10531,7 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 	delete ws['!links']; | ||||
| 
 | ||||
| 	/* printOptions */ | ||||
| 	if (ws['!margins'] != null) o[o.length] =  write_ws_xml_margins(ws['!margins']) | ||||
| 	if (ws['!margins'] != null) o[o.length] =  write_ws_xml_margins(ws['!margins']); | ||||
| 	/* pageSetup */ | ||||
| 
 | ||||
| 	var hfidx = o.length; | ||||
| @ -10493,20 +10571,38 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ { | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.718 BrtRowHdr */ | ||||
| function parse_BrtRowHdr(data, length) { | ||||
| 	var z = ([]/*:any*/); | ||||
| 	var z = ({}/*:any*/); | ||||
| 	var tgt = data.l + length; | ||||
| 	z.r = data.read_shift(4); | ||||
| 	data.l += length-4; | ||||
| 	data.l += 4; // TODO: ixfe
 | ||||
| 	var miyRw = data.read_shift(2); | ||||
| 	data.l += 1; // TODO: top/bot padding
 | ||||
| 	var flags = data.read_shift(1); | ||||
| 	data.l = tgt; | ||||
| 	if(flags & 0x10) z.hidden = true; | ||||
| 	if(flags & 0x20) z.hpt = miyRw / 20; | ||||
| 	return z; | ||||
| } | ||||
| function write_BrtRowHdr(R/*:number*/, range, ws) { | ||||
| 	var o = new_buf(17+8*16); | ||||
| 	var row = (ws['!rows']||[])[R]||{}; | ||||
| 	o.write_shift(4, R); | ||||
| 
 | ||||
| 	/* TODO: flags styles */ | ||||
| 	o.write_shift(4, 0); | ||||
| 	o.write_shift(2, 0x0140); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(1, 0); | ||||
| 	o.write_shift(4, 0); /* TODO: ixfe */ | ||||
| 
 | ||||
| 	var miyRw = 0x0140; | ||||
| 	if(row.hpx) miyRw = px2pt(row.hpx) * 20; | ||||
| 	else if(row.hpt) miyRw = row.hpt * 20; | ||||
| 	o.write_shift(2, miyRw); | ||||
| 
 | ||||
| 	o.write_shift(1, 0); /* top/bot padding */ | ||||
| 
 | ||||
| 	var flags = 0x0; | ||||
| 	if(row.hidden) flags |= 0x10; | ||||
| 	if(row.hpx || row.hpt) flags |= 0x20; | ||||
| 	o.write_shift(1, flags); | ||||
| 
 | ||||
| 	o.write_shift(1, 0); /* phonetic guide */ | ||||
| 
 | ||||
| 	/* [MS-XLSB] 2.5.8 BrtColSpan explains the mechanism */ | ||||
| 	var ncolspan = 0, lcs = o.l; | ||||
| @ -10774,9 +10870,12 @@ function write_BrtColInfo(C/*:number*/, col, o) { | ||||
| 	var p = col_obj_w(C, col); | ||||
| 	o.write_shift(-4, C); | ||||
| 	o.write_shift(-4, C); | ||||
| 	o.write_shift(4, p.width * 256); | ||||
| 	o.write_shift(4, (p.width || 10) * 256); | ||||
| 	o.write_shift(4, 0/*ixfe*/); // style
 | ||||
| 	o.write_shift(1, 2); // bit flag
 | ||||
| 	var flags = 0; | ||||
| 	if(col.hidden) flags |= 0x01; | ||||
| 	if(typeof p.width == 'number') flags |= 0x02; | ||||
| 	o.write_shift(1, flags); // bit flag
 | ||||
| 	o.write_shift(1, 0); // bit flag
 | ||||
| 	return o; | ||||
| } | ||||
| @ -10804,6 +10903,24 @@ function write_BrtMargins(margins, o) { | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.292 BrtBeginWsView */ | ||||
| function write_BrtBeginWsView(ws, o) { | ||||
| 	if(o == null) o = new_buf(30); | ||||
| 	o.write_shift(2, 924); // bit flag
 | ||||
| 	o.write_shift(4, 0); | ||||
| 	o.write_shift(4, 0); // view first row
 | ||||
| 	o.write_shift(4, 0); // view first col
 | ||||
| 	o.write_shift(1, 0); // gridline color ICV
 | ||||
| 	o.write_shift(1, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 100); // zoom scale
 | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(4, 0); // workbook view id
 | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.740 BrtSheetProtection */ | ||||
| function write_BrtSheetProtection(sp, o) { | ||||
| 	if(o == null) o = new_buf(16*4+2); | ||||
| @ -10826,9 +10943,8 @@ function write_BrtSheetProtection(sp, o) { | ||||
| 		["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); | ||||
| 		if(n[1]) o.write_shift(4, sp[n[0]] != null && !sp[n[0]] ? 1 : 0); | ||||
| 		else      o.write_shift(4, sp[n[0]] != null && sp[n[0]] ? 0 : 1); | ||||
| 	}); | ||||
| 	return o; | ||||
| } | ||||
| @ -10875,6 +10991,10 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 				if(opts.sheetRows && opts.sheetRows <= row.r) end=true; | ||||
| 				rr = encode_row(R = row.r); | ||||
| 				opts['!row'] = row.r; | ||||
| 				if(val.hidden || val.hpt) { | ||||
| 					if(val.hpt) val.hpx = pt2px(val.hpt); | ||||
| 					rowinfo[val.r] = val; | ||||
| 				} | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x0002: /* 'BrtCellRk' */ | ||||
| @ -10972,7 +11092,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ { | ||||
| 			case 0x003C: /* 'BrtColInfo' */ | ||||
| 				if(!opts.cellStyles) break; | ||||
| 				while(val.e >= val.s) { | ||||
| 					colinfo[val.e--] = { width: val.w/256 }; | ||||
| 					colinfo[val.e--] = { width: val.w/256, hidden: !!(val.flags & 0x01) }; | ||||
| 					if(!seencol) { seencol = true; find_mdw_colw(val.w/256); } | ||||
| 					process_col(colinfo[val.e+1]); | ||||
| 				} | ||||
| @ -11188,6 +11308,21 @@ function write_AUTOFILTER(ba, ws) { | ||||
| 	write_record(ba, "BrtEndAFilter"); | ||||
| } | ||||
| 
 | ||||
| function write_WSVIEWS2(ba, ws) { | ||||
| 	write_record(ba, "BrtBeginWsViews"); | ||||
| 	{ /* 1*WSVIEW2 */ | ||||
| 		/* [ACUID] */ | ||||
| 		write_record(ba, "BrtBeginWsView", write_BrtBeginWsView(ws)); | ||||
| 		/* [BrtPane] */ | ||||
| 		/* *4BrtSel */ | ||||
| 		/* *4SXSELECT */ | ||||
| 		/* *FRT */ | ||||
| 		write_record(ba, "BrtEndWsView"); | ||||
| 	} | ||||
| 	/* *FRT */ | ||||
| 	write_record(ba, "BrtEndWsViews"); | ||||
| } | ||||
| 
 | ||||
| function write_SHEETPROTECT(ba, ws) { | ||||
| 	if(!ws['!protect']) return; | ||||
| 	/* [BrtSheetProtectionIso] */ | ||||
| @ -11204,7 +11339,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { | ||||
| 	write_record(ba, "BrtBeginSheet"); | ||||
| 	write_record(ba, "BrtWsProp", write_BrtWsProp(s)); | ||||
| 	write_record(ba, "BrtWsDim", write_BrtWsDim(r)); | ||||
| 	/* [WSVIEWS2] */ | ||||
| 	write_WSVIEWS2(ba, ws); | ||||
| 	/* [WSFMTINFO] */ | ||||
| 	write_COLINFOS(ba, ws, idx, opts, wb); | ||||
| 	write_CELLTABLE(ba, ws, idx, opts, wb); | ||||
| @ -12224,7 +12359,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 	var comments = [], comment = {}; | ||||
| 	var cstys = [], csty, seencol = false; | ||||
| 	var arrayf = []; | ||||
| 	var rowinfo = []; | ||||
| 	var rowinfo = [], rowobj = {}; | ||||
| 	var Workbook = { Sheets:[] }, wsprops = {}; | ||||
| 	xlmlregex.lastIndex = 0; | ||||
| 	str = str.replace(/<!--([^\u2603]*?)-->/mg,""); | ||||
| @ -12289,6 +12424,12 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 			} else { | ||||
| 				row = xlml_parsexmltag(Rn[0]); | ||||
| 				if(row.Index) r = +row.Index - 1; | ||||
| 				rowobj = {}; | ||||
| 				if(row.AutoFitHeight == "0") { | ||||
| 					rowobj.hpx = parseInt(row.Height, 10); rowobj.hpt = px2pt(rowobj.hpx); | ||||
| 					rowinfo[r] = rowobj; | ||||
| 				} | ||||
| 				if(row.Hidden == "1") { rowobj.hidden = true; rowinfo[r] = rowobj; } | ||||
| 			} | ||||
| 			break; | ||||
| 		case 'Worksheet': /* TODO: read range from FullRows/FullColumns */ | ||||
| @ -12339,9 +12480,10 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 		case 'Column': | ||||
| 			if(state[state.length-1][0] !== 'Table') break; | ||||
| 			csty = xlml_parsexmltag(Rn[0]); | ||||
| 			csty.wpx = parseInt(csty.Width, 10); | ||||
| 			if(csty.Hidden) { csty.hidden = true; delete csty.Hidden; } | ||||
| 			if(csty.Width) csty.wpx = parseInt(csty.Width, 10); | ||||
| 			if(!seencol && csty.wpx > 10) { | ||||
| 				seencol = true; find_mdw_wpx(csty.wpx); | ||||
| 				seencol = true; MDW = DEF_MDW; //find_mdw_wpx(csty.wpx);
 | ||||
| 				for(var _col = 0; _col < cstys.length; ++_col) if(cstys[_col]) process_col(cstys[_col]); | ||||
| 			} | ||||
| 			if(seencol) process_col(csty); | ||||
| @ -12478,7 +12620,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ { | ||||
| 					case 'Color': break; | ||||
| 					case 'Index': break; | ||||
| 					case 'RGB': break; | ||||
| 					case 'PixelsPerInch': break; | ||||
| 					case 'PixelsPerInch': break; // TODO: set PPI
 | ||||
| 					case 'TargetScreenSize': break; | ||||
| 					case 'ReadOnlyRecommended': break; | ||||
| 					default: seen = false; | ||||
| @ -13030,6 +13172,15 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{ | ||||
| 
 | ||||
| 	return writextag("Cell", m, attr); | ||||
| } | ||||
| function write_ws_xlml_row(R/*:number*/, row)/*:string*/ { | ||||
| 	var o = '<Row ss:Index="' + (R+1) + '"'; | ||||
| 	if(row) { | ||||
| 		if(row.hpt && !row.hpx) row.hpx = pt2px(row.hpt); | ||||
| 		if(row.hpx) o += ' ss:AutoFitHeight="0" ss:Height="' + row.hpx + '"'; | ||||
| 		if(row.hidden) o += ' ss:Hidden="1"'; | ||||
| 	} | ||||
| 	return o + '>'; | ||||
| } | ||||
| /* TODO */ | ||||
| function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ { | ||||
| 	if(!ws['!ref']) return ""; | ||||
| @ -13037,12 +13188,17 @@ function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbo | ||||
| 	var marr = ws['!merges'] || [], mi = 0; | ||||
| 	var o = []; | ||||
| 	if(ws['!cols']) ws['!cols'].forEach(function(n, i) { | ||||
| 		process_col(n); | ||||
| 		var w = !!n.width; | ||||
| 		var p = col_obj_w(i, n); | ||||
| 		o.push(writextag("Column",null, {"ss:Index":i+1, "ss:Width":width2px(p.width)})); | ||||
| 		var k = {"ss:Index":i+1}; | ||||
| 		if(w) k['ss:Width'] = width2px(p.width); | ||||
| 		if(n.hidden) k['ss:Hidden']="1"; | ||||
| 		o.push(writextag("Column",null,k)); | ||||
| 	}); | ||||
| 	var dense = Array.isArray(ws); | ||||
| 	for(var R = range.s.r; R <= range.e.r; ++R) { | ||||
| 		var row = ['<Row ss:Index="' + (R+1) + '">']; | ||||
| 		var row = [write_ws_xlml_row(R, (ws['!rows']||[])[R])]; | ||||
| 		for(var C = range.s.c; C <= range.e.c; ++C) { | ||||
| 			var skip = false; | ||||
| 			for(mi = 0; mi != marr.length; ++mi) { | ||||
| @ -13597,7 +13753,14 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { | ||||
| 						process_col(colinfo[val.e+1]); | ||||
| 					} | ||||
| 				} break; | ||||
| 				case 'Row': break; // TODO
 | ||||
| 				case 'Row': { | ||||
| 					var rowobj = {}; | ||||
| 					if(val.hidden) { rowinfo[val.r] = rowobj; rowobj.hidden = true; } | ||||
| 					if(val.hpt) { | ||||
| 						rowinfo[val.r] = rowobj; | ||||
| 						rowobj.hpt = val.hpt; rowobj.hpx = pt2px(val.hpt); | ||||
| 					} | ||||
| 				} break; | ||||
| 
 | ||||
| 				case 'LeftMargin': | ||||
| 				case 'RightMargin': | ||||
|  | ||||
							
								
								
									
										265
									
								
								xlsx.js
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										265
									
								
								xlsx.js
									
									
									
									
									
								
							| @ -4008,14 +4008,19 @@ function parse_ExtSST(blob, length) { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* 2.4.221 TODO*/ | ||||
| /* 2.4.221 TODO: check BIFF2-4 */ | ||||
| function parse_Row(blob, length) { | ||||
| 	var rw = blob.read_shift(2), col = blob.read_shift(2), Col = blob.read_shift(2), rht = blob.read_shift(2); | ||||
| 	blob.read_shift(4); // reserved(2), unused(2)
 | ||||
| 	var z = ({}); | ||||
| 	z.r = blob.read_shift(2); | ||||
| 	z.c = blob.read_shift(2); | ||||
| 	z.cnt = blob.read_shift(2) - z.c; | ||||
| 	var miyRw = blob.read_shift(2); | ||||
| 	blob.l += 4; // reserved(2), unused(2)
 | ||||
| 	var flags = blob.read_shift(1); // various flags
 | ||||
| 	blob.read_shift(1); // reserved
 | ||||
| 	blob.read_shift(2); //ixfe, other flags
 | ||||
| 	return {r:rw, c:col, cnt:Col-col}; | ||||
| 	blob.l += 3; // reserved(8), ixfe(12), flags(4)
 | ||||
| 	if(flags & 0x20) z.hidden = true; | ||||
| 	if(flags & 0x40) z.hpt = miyRw / 20; | ||||
| 	return z; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -5052,19 +5057,19 @@ var SYLK = (function() { | ||||
| 		var records = str.split(/[\n\r]+/), R = -1, C = -1, ri = 0, rj = 0, arr = []; | ||||
| 		var formats = []; | ||||
| 		var next_cell_format = null; | ||||
| 		var sht = {}, rowinfo = [], colinfo = [], cw = []; | ||||
| 		var Mval = 0, j; | ||||
| 		for (; ri !== records.length; ++ri) { | ||||
| 			Mval = 0; | ||||
| 			var record = records[ri].trim().split(";"); | ||||
| 			var RT = record[0], val; | ||||
| 			if(RT === 'P') for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 				case 'P': | ||||
| 					formats.push(record[rj].substr(1)); | ||||
| 					break; | ||||
| 			} | ||||
| 			else if(RT !== 'C' && RT !== 'F') continue; | ||||
| 			else for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 			switch(RT) { | ||||
| 			case 'P': if(record[1].charAt(0) == 'P') formats.push(records[ri].trim().substr(3).replace(/;;/g, ";")); | ||||
| 				break; | ||||
| 			case 'C': case 'F': for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) { | ||||
| 				case 'Y': | ||||
| 					R = parseInt(record[rj].substr(1))-1; C = 0; | ||||
| 					for(var j = arr.length; j <= R; ++j) arr[j] = []; | ||||
| 					for(j = arr.length; j <= R; ++j) arr[j] = []; | ||||
| 					break; | ||||
| 				case 'X': C = parseInt(record[rj].substr(1))-1; break; | ||||
| 				case 'K': | ||||
| @ -5074,7 +5079,7 @@ var SYLK = (function() { | ||||
| 					else if(val === 'FALSE') val = false; | ||||
| 					else if(+val === +val) { | ||||
| 						val = +val; | ||||
| 						if(next_cell_format !== null && next_cell_format.match(/[ymdhmsYMDHMS]/)) val = numdate(val); | ||||
| 						if(next_cell_format !== null && SSF.is_date(next_cell_format)) val = numdate(val); | ||||
| 					} | ||||
| 					arr[R][C] = val; | ||||
| 					next_cell_format = null; | ||||
| @ -5082,12 +5087,37 @@ var SYLK = (function() { | ||||
| 				case 'P': | ||||
| 					if(RT !== 'F') break; | ||||
| 					next_cell_format = formats[parseInt(record[rj].substr(1))]; | ||||
| 					break; | ||||
| 				case 'M': Mval = parseInt(record[rj].substr(1)) / 20; break; | ||||
| 				case 'W': | ||||
| 					if(RT !== 'F') break; | ||||
| 					cw = record[rj].substr(1).split(" "); | ||||
| 					for(j = parseInt(cw[0], 10); j <= parseInt(cw[1], 10); ++j) { | ||||
| 						Mval = parseInt(cw[2], 10); | ||||
| 						colinfo[j-1] = Mval == 0 ? {hidden:true}: {wch:Mval}; process_col(colinfo[j-1]); | ||||
| 					} break; | ||||
| 				case 'R': | ||||
| 					R = parseInt(record[rj].substr(1))-1; | ||||
| 					rowinfo[R] = {}; | ||||
| 					if(Mval > 0) { rowinfo[R].hpt = Mval; rowinfo[R].hpx = pt2px(Mval); } | ||||
| 					else if(Mval == 0) rowinfo[R].hidden = true; | ||||
| 			} break; | ||||
| 			default: break; | ||||
| 			} | ||||
| 		} | ||||
| 		if(rowinfo.length > 0) sht['!rows'] = rowinfo; | ||||
| 		if(colinfo.length > 0) sht['!cols'] = colinfo; | ||||
| 		arr[arr.length] = sht; | ||||
| 		return arr; | ||||
| 	} | ||||
| 
 | ||||
| 	function sylk_to_sheet(str, opts) { return aoa_to_sheet(sylk_to_aoa(str, opts), opts); } | ||||
| 	function sylk_to_sheet(str, opts) { | ||||
| 		var aoa = sylk_to_aoa(str, opts); | ||||
| 		var ws = aoa.pop(); | ||||
| 		var o = aoa_to_sheet(aoa, opts); | ||||
| 		keys(ws).forEach(function(k) { o[k] = ws[k]; }); | ||||
| 		return o; | ||||
| 	} | ||||
| 
 | ||||
| 	function sylk_to_workbook(str, opts) { return sheet_to_workbook(sylk_to_sheet(str, opts), opts); } | ||||
| 
 | ||||
| @ -5103,11 +5133,40 @@ var SYLK = (function() { | ||||
| 		return o; | ||||
| 	} | ||||
| 
 | ||||
| 	function write_ws_cols_sylk(out, cols) { | ||||
| 		cols.forEach(function(col, i) { | ||||
| 			var rec = "F;W" + (i+1) + " " + (i+1) + " "; | ||||
| 			if(col.hidden) rec += "0"; | ||||
| 			else { | ||||
| 				if(typeof col.width == 'number') col.wpx = width2px(col.width); | ||||
| 				if(typeof col.wpx == 'number') col.wch = px2char(col.wpx); | ||||
| 				if(typeof col.wch == 'number') rec += Math.round(col.wch); | ||||
| 			} | ||||
| 			if(rec.charAt(rec.length - 1) != " ") out.push(rec); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	function write_ws_rows_sylk(out, rows) { | ||||
| 		rows.forEach(function(row, i) { | ||||
| 			var rec = "F;"; | ||||
| 			if(row.hidden) rec += "M0;"; | ||||
| 			else if(row.hpt) rec += "M" + 20 * row.hpt + ";"; | ||||
| 			else if(row.hpx) rec += "M" + 20 * px2pt(row.hpx) + ";"; | ||||
| 			if(rec.length > 2) out.push(rec + "R" + (i+1)); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	function sheet_to_sylk(ws, opts) { | ||||
| 		var preamble = ["ID;PWXL;N;E"], o = []; | ||||
| 		preamble.push("P;PGeneral"); | ||||
| 		var r = decode_range(ws['!ref']), cell; | ||||
| 		var dense = Array.isArray(ws); | ||||
| 		var RS = "\r\n"; | ||||
| 
 | ||||
| 		preamble.push("P;PGeneral"); | ||||
| 		preamble.push("F;P0;DG0G8;M255"); | ||||
| 		if(ws['!cols']) write_ws_cols_sylk(preamble, ws['!cols']); | ||||
| 		if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']); | ||||
| 
 | ||||
| 		for(var R = r.s.r; R <= r.e.r; ++R) { | ||||
| 			for(var C = r.s.c; C <= r.e.c; ++C) { | ||||
| 				var coord = encode_cell({r:R,c:C}); | ||||
| @ -5116,8 +5175,6 @@ var SYLK = (function() { | ||||
| 				o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); | ||||
| 			} | ||||
| 		} | ||||
| 		preamble.push("F;P0;DG0G8;M255"); | ||||
| 		var RS = "\r\n"; | ||||
| 		return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS; | ||||
| 	} | ||||
| 
 | ||||
| @ -6327,13 +6384,17 @@ function process_col(coll) { | ||||
| 		coll.wch = px2char(coll.wpx); | ||||
| 		coll.width = char2width(coll.wch); | ||||
| 		coll.MDW = MDW; | ||||
| 	} else if(typeof coll.wch == 'number') { | ||||
| 		coll.width = char2width(coll.wch); | ||||
| 		coll.wpx = width2px(coll.width); | ||||
| 		coll.MDW = MDW; | ||||
| 	} | ||||
| 	if(coll.customWidth) delete coll.customWidth; | ||||
| } | ||||
| 
 | ||||
| var DEF_DPI = 96, DPI = DEF_DPI; | ||||
| function px2pt(px) { return px * 72 / DPI; } | ||||
| function pt2px(pt) { return pt * DPI / 72; } | ||||
| var DEF_PPI = 96, PPI = DEF_PPI; | ||||
| function px2pt(px) { return px * 96 / PPI; } | ||||
| function pt2px(pt) { return pt * PPI / 96; } | ||||
| 
 | ||||
| /* [MS-EXSPXML3] 2.4.54 ST_enmPattern */ | ||||
| var XLMLPatternTypeMap = { | ||||
| @ -9860,13 +9921,14 @@ function get_sst_id(sst, str) { | ||||
| function col_obj_w(C, col) { | ||||
| 	var p = ({min:C+1,max:C+1}); | ||||
| 	/* wch (chars), wpx (pixels) */ | ||||
| 	var width = -1; | ||||
| 	var wch = -1; | ||||
| 	if(col.MDW) MDW = col.MDW; | ||||
| 	if(col.width != null) p.customWidth = 1; | ||||
| 	else if(col.wpx != null) width = px2char(col.wpx); | ||||
| 	else if(col.wch != null) width = col.wch; | ||||
| 	if(width > -1) { p.width = char2width(width); p.customWidth = 1; } | ||||
| 	else p.width = col.width; | ||||
| 	else if(col.wpx != null) wch = px2char(col.wpx); | ||||
| 	else if(col.wch != null) wch = col.wch; | ||||
| 	if(wch > -1) { p.width = char2width(wch); p.customWidth = 1; } | ||||
| 	else if(col.width != null) p.width = col.width; | ||||
| 	if(col.hidden) p.hidden = true; | ||||
| 	return p; | ||||
| } | ||||
| 
 | ||||
| @ -10086,6 +10148,7 @@ function parse_ws_xml_cols(columns, cols) { | ||||
| 	var seencol = false; | ||||
| 	for(var coli = 0; coli != cols.length; ++coli) { | ||||
| 		var coll = parsexmltag(cols[coli], true); | ||||
| 		if(coll.hidden) coll.hidden = parsexmlbool(coll.hidden); | ||||
| 		var colm=parseInt(coll.min, 10)-1, colM=parseInt(coll.max,10)-1; | ||||
| 		delete coll.min; delete coll.max; coll.width = +coll.width; | ||||
| 		if(!seencol && coll.width) { seencol = true; find_mdw_colw(coll.width); } | ||||
| @ -10112,6 +10175,12 @@ function write_ws_xml_autofilter(data) { | ||||
| 	return writextag("autoFilter", null, {ref:data.ref}); | ||||
| } | ||||
| 
 | ||||
| /* 18.3.1.88 sheetViews CT_SheetViews */ | ||||
| /* 18.3.1.87 sheetView CT_SheetView */ | ||||
| function write_ws_xml_sheetviews(ws, opts, idx, wb) { | ||||
| 	return writextag("sheetViews", writextag("sheetView", null, {workbookViewId:"0"}), {}); | ||||
| } | ||||
| 
 | ||||
| function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) { | ||||
| 	if(cell.v === undefined && cell.f === undefined || cell.t === 'z') return ""; | ||||
| 	var vv = ""; | ||||
| @ -10163,13 +10232,14 @@ var parse_ws_xml_data = (function parse_ws_xml_data_factory() { | ||||
| 	var match_v = matchtag("v"), match_f = matchtag("f"); | ||||
| 
 | ||||
| return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 	var ri = 0, x = "", cells = [], cref = [], idx = 0, i=0, cc=0, d="", p; | ||||
| 	var ri = 0, x = "", cells = [], cref = [], idx=0, i=0, cc=0, d="", p; | ||||
| 	var tag, tagr = 0, tagc = 0; | ||||
| 	var sstr, ftag; | ||||
| 	var fmtid = 0, fillid = 0, do_format = Array.isArray(styles.CellXf), cf; | ||||
| 	var arrayf = []; | ||||
| 	var sharedf = []; | ||||
| 	var dense = Array.isArray(s); | ||||
| 	var rows = [], rowobj = {}, rowrite = false; | ||||
| 	for(var marr = sdata.split(rowregex), mt = 0, marrlen = marr.length; mt != marrlen; ++mt) { | ||||
| 		x = marr[mt].trim(); | ||||
| 		var xlen = x.length; | ||||
| @ -10183,6 +10253,13 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 		if(guess.s.r > tagr - 1) guess.s.r = tagr - 1; | ||||
| 		if(guess.e.r < tagr - 1) guess.e.r = tagr - 1; | ||||
| 
 | ||||
| 		if(opts && opts.cellStyles) { | ||||
| 			rowobj = {}; rowrite = false; | ||||
| 			if(tag.ht) { rowrite = true; rowobj.hpt = parseFloat(tag.ht); rowobj.hpx = pt2px(rowobj.hpt); } | ||||
| 			if(tag.hidden == "1") { rowrite = true; rowobj.hidden = true; } | ||||
| 			if(rowrite) rows[tagr-1] = rowobj; | ||||
| 		} | ||||
| 
 | ||||
| 		/* 18.3.1.4 c CT_Cell */ | ||||
| 		cells = x.substr(ri).split(cellregex); | ||||
| 		for(ri = 0; ri != cells.length; ++ri) { | ||||
| @ -10291,6 +10368,7 @@ return function parse_ws_xml_data(sdata, s, opts, guess, themes, styles) { | ||||
| 			} else s[tag.r] = p; | ||||
| 		} | ||||
| 	} | ||||
| 	if(rows.length > 0) s['!rows'] = rows; | ||||
| }; })(); | ||||
| 
 | ||||
| function write_ws_xml_data(ws, opts, idx, wb, rels) { | ||||
| @ -10341,7 +10419,7 @@ function write_ws_xml(idx, opts, wb, rels) { | ||||
| 
 | ||||
| 	o[o.length] = (writextag('dimension', null, {'ref': ref})); | ||||
| 
 | ||||
| 	/* sheetViews */ | ||||
| 	o[o.length] = write_ws_xml_sheetviews(ws, opts, idx, wb); | ||||
| 
 | ||||
| 	/* 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' })); | ||||
| @ -10391,7 +10469,7 @@ function write_ws_xml(idx, opts, wb, rels) { | ||||
| 	delete ws['!links']; | ||||
| 
 | ||||
| 	/* printOptions */ | ||||
| 	if (ws['!margins'] != null) o[o.length] =  write_ws_xml_margins(ws['!margins']) | ||||
| 	if (ws['!margins'] != null) o[o.length] =  write_ws_xml_margins(ws['!margins']); | ||||
| 	/* pageSetup */ | ||||
| 
 | ||||
| 	var hfidx = o.length; | ||||
| @ -10431,20 +10509,38 @@ function write_ws_xml(idx, opts, wb, rels) { | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.718 BrtRowHdr */ | ||||
| function parse_BrtRowHdr(data, length) { | ||||
| 	var z = ([]); | ||||
| 	var z = ({}); | ||||
| 	var tgt = data.l + length; | ||||
| 	z.r = data.read_shift(4); | ||||
| 	data.l += length-4; | ||||
| 	data.l += 4; // TODO: ixfe
 | ||||
| 	var miyRw = data.read_shift(2); | ||||
| 	data.l += 1; // TODO: top/bot padding
 | ||||
| 	var flags = data.read_shift(1); | ||||
| 	data.l = tgt; | ||||
| 	if(flags & 0x10) z.hidden = true; | ||||
| 	if(flags & 0x20) z.hpt = miyRw / 20; | ||||
| 	return z; | ||||
| } | ||||
| function write_BrtRowHdr(R, range, ws) { | ||||
| 	var o = new_buf(17+8*16); | ||||
| 	var row = (ws['!rows']||[])[R]||{}; | ||||
| 	o.write_shift(4, R); | ||||
| 
 | ||||
| 	/* TODO: flags styles */ | ||||
| 	o.write_shift(4, 0); | ||||
| 	o.write_shift(2, 0x0140); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(1, 0); | ||||
| 	o.write_shift(4, 0); /* TODO: ixfe */ | ||||
| 
 | ||||
| 	var miyRw = 0x0140; | ||||
| 	if(row.hpx) miyRw = px2pt(row.hpx) * 20; | ||||
| 	else if(row.hpt) miyRw = row.hpt * 20; | ||||
| 	o.write_shift(2, miyRw); | ||||
| 
 | ||||
| 	o.write_shift(1, 0); /* top/bot padding */ | ||||
| 
 | ||||
| 	var flags = 0x0; | ||||
| 	if(row.hidden) flags |= 0x10; | ||||
| 	if(row.hpx || row.hpt) flags |= 0x20; | ||||
| 	o.write_shift(1, flags); | ||||
| 
 | ||||
| 	o.write_shift(1, 0); /* phonetic guide */ | ||||
| 
 | ||||
| 	/* [MS-XLSB] 2.5.8 BrtColSpan explains the mechanism */ | ||||
| 	var ncolspan = 0, lcs = o.l; | ||||
| @ -10712,9 +10808,12 @@ function write_BrtColInfo(C, col, o) { | ||||
| 	var p = col_obj_w(C, col); | ||||
| 	o.write_shift(-4, C); | ||||
| 	o.write_shift(-4, C); | ||||
| 	o.write_shift(4, p.width * 256); | ||||
| 	o.write_shift(4, (p.width || 10) * 256); | ||||
| 	o.write_shift(4, 0/*ixfe*/); // style
 | ||||
| 	o.write_shift(1, 2); // bit flag
 | ||||
| 	var flags = 0; | ||||
| 	if(col.hidden) flags |= 0x01; | ||||
| 	if(typeof p.width == 'number') flags |= 0x02; | ||||
| 	o.write_shift(1, flags); // bit flag
 | ||||
| 	o.write_shift(1, 0); // bit flag
 | ||||
| 	return o; | ||||
| } | ||||
| @ -10742,6 +10841,24 @@ function write_BrtMargins(margins, o) { | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.292 BrtBeginWsView */ | ||||
| function write_BrtBeginWsView(ws, o) { | ||||
| 	if(o == null) o = new_buf(30); | ||||
| 	o.write_shift(2, 924); // bit flag
 | ||||
| 	o.write_shift(4, 0); | ||||
| 	o.write_shift(4, 0); // view first row
 | ||||
| 	o.write_shift(4, 0); // view first col
 | ||||
| 	o.write_shift(1, 0); // gridline color ICV
 | ||||
| 	o.write_shift(1, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 100); // zoom scale
 | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(2, 0); | ||||
| 	o.write_shift(4, 0); // workbook view id
 | ||||
| 	return o; | ||||
| } | ||||
| 
 | ||||
| /* [MS-XLSB] 2.4.740 BrtSheetProtection */ | ||||
| function write_BrtSheetProtection(sp, o) { | ||||
| 	if(o == null) o = new_buf(16*4+2); | ||||
| @ -10764,9 +10881,8 @@ function write_BrtSheetProtection(sp, o) { | ||||
| 		["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); | ||||
| 		if(n[1]) o.write_shift(4, sp[n[0]] != null && !sp[n[0]] ? 1 : 0); | ||||
| 		else      o.write_shift(4, sp[n[0]] != null && sp[n[0]] ? 0 : 1); | ||||
| 	}); | ||||
| 	return o; | ||||
| } | ||||
| @ -10813,6 +10929,10 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles) { | ||||
| 				if(opts.sheetRows && opts.sheetRows <= row.r) end=true; | ||||
| 				rr = encode_row(R = row.r); | ||||
| 				opts['!row'] = row.r; | ||||
| 				if(val.hidden || val.hpt) { | ||||
| 					if(val.hpt) val.hpx = pt2px(val.hpt); | ||||
| 					rowinfo[val.r] = val; | ||||
| 				} | ||||
| 				break; | ||||
| 
 | ||||
| 			case 0x0002: /* 'BrtCellRk' */ | ||||
| @ -10910,7 +11030,7 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles) { | ||||
| 			case 0x003C: /* 'BrtColInfo' */ | ||||
| 				if(!opts.cellStyles) break; | ||||
| 				while(val.e >= val.s) { | ||||
| 					colinfo[val.e--] = { width: val.w/256 }; | ||||
| 					colinfo[val.e--] = { width: val.w/256, hidden: !!(val.flags & 0x01) }; | ||||
| 					if(!seencol) { seencol = true; find_mdw_colw(val.w/256); } | ||||
| 					process_col(colinfo[val.e+1]); | ||||
| 				} | ||||
| @ -11126,6 +11246,21 @@ function write_AUTOFILTER(ba, ws) { | ||||
| 	write_record(ba, "BrtEndAFilter"); | ||||
| } | ||||
| 
 | ||||
| function write_WSVIEWS2(ba, ws) { | ||||
| 	write_record(ba, "BrtBeginWsViews"); | ||||
| 	{ /* 1*WSVIEW2 */ | ||||
| 		/* [ACUID] */ | ||||
| 		write_record(ba, "BrtBeginWsView", write_BrtBeginWsView(ws)); | ||||
| 		/* [BrtPane] */ | ||||
| 		/* *4BrtSel */ | ||||
| 		/* *4SXSELECT */ | ||||
| 		/* *FRT */ | ||||
| 		write_record(ba, "BrtEndWsView"); | ||||
| 	} | ||||
| 	/* *FRT */ | ||||
| 	write_record(ba, "BrtEndWsViews"); | ||||
| } | ||||
| 
 | ||||
| function write_SHEETPROTECT(ba, ws) { | ||||
| 	if(!ws['!protect']) return; | ||||
| 	/* [BrtSheetProtectionIso] */ | ||||
| @ -11142,7 +11277,7 @@ function write_ws_bin(idx, opts, wb, rels) { | ||||
| 	write_record(ba, "BrtBeginSheet"); | ||||
| 	write_record(ba, "BrtWsProp", write_BrtWsProp(s)); | ||||
| 	write_record(ba, "BrtWsDim", write_BrtWsDim(r)); | ||||
| 	/* [WSVIEWS2] */ | ||||
| 	write_WSVIEWS2(ba, ws); | ||||
| 	/* [WSFMTINFO] */ | ||||
| 	write_COLINFOS(ba, ws, idx, opts, wb); | ||||
| 	write_CELLTABLE(ba, ws, idx, opts, wb); | ||||
| @ -12160,7 +12295,7 @@ function parse_xlml_xml(d, opts) { | ||||
| 	var comments = [], comment = {}; | ||||
| 	var cstys = [], csty, seencol = false; | ||||
| 	var arrayf = []; | ||||
| 	var rowinfo = []; | ||||
| 	var rowinfo = [], rowobj = {}; | ||||
| 	var Workbook = { Sheets:[] }, wsprops = {}; | ||||
| 	xlmlregex.lastIndex = 0; | ||||
| 	str = str.replace(/<!--([^\u2603]*?)-->/mg,""); | ||||
| @ -12224,6 +12359,12 @@ for(var cma = c; cma <= cc; ++cma) { | ||||
| 			} else { | ||||
| 				row = xlml_parsexmltag(Rn[0]); | ||||
| 				if(row.Index) r = +row.Index - 1; | ||||
| 				rowobj = {}; | ||||
| 				if(row.AutoFitHeight == "0") { | ||||
| 					rowobj.hpx = parseInt(row.Height, 10); rowobj.hpt = px2pt(rowobj.hpx); | ||||
| 					rowinfo[r] = rowobj; | ||||
| 				} | ||||
| 				if(row.Hidden == "1") { rowobj.hidden = true; rowinfo[r] = rowobj; } | ||||
| 			} | ||||
| 			break; | ||||
| 		case 'Worksheet': /* TODO: read range from FullRows/FullColumns */ | ||||
| @ -12274,9 +12415,10 @@ for(var cma = c; cma <= cc; ++cma) { | ||||
| 		case 'Column': | ||||
| 			if(state[state.length-1][0] !== 'Table') break; | ||||
| 			csty = xlml_parsexmltag(Rn[0]); | ||||
| 			csty.wpx = parseInt(csty.Width, 10); | ||||
| 			if(csty.Hidden) { csty.hidden = true; delete csty.Hidden; } | ||||
| 			if(csty.Width) csty.wpx = parseInt(csty.Width, 10); | ||||
| 			if(!seencol && csty.wpx > 10) { | ||||
| 				seencol = true; find_mdw_wpx(csty.wpx); | ||||
| 				seencol = true; MDW = DEF_MDW; //find_mdw_wpx(csty.wpx);
 | ||||
| 				for(var _col = 0; _col < cstys.length; ++_col) if(cstys[_col]) process_col(cstys[_col]); | ||||
| 			} | ||||
| 			if(seencol) process_col(csty); | ||||
| @ -12413,7 +12555,7 @@ for(var cma = c; cma <= cc; ++cma) { | ||||
| 					case 'Color': break; | ||||
| 					case 'Index': break; | ||||
| 					case 'RGB': break; | ||||
| 					case 'PixelsPerInch': break; | ||||
| 					case 'PixelsPerInch': break; // TODO: set PPI
 | ||||
| 					case 'TargetScreenSize': break; | ||||
| 					case 'ReadOnlyRecommended': break; | ||||
| 					default: seen = false; | ||||
| @ -12964,6 +13106,15 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr){ | ||||
| 
 | ||||
| 	return writextag("Cell", m, attr); | ||||
| } | ||||
| function write_ws_xlml_row(R, row) { | ||||
| 	var o = '<Row ss:Index="' + (R+1) + '"'; | ||||
| 	if(row) { | ||||
| 		if(row.hpt && !row.hpx) row.hpx = pt2px(row.hpt); | ||||
| 		if(row.hpx) o += ' ss:AutoFitHeight="0" ss:Height="' + row.hpx + '"'; | ||||
| 		if(row.hidden) o += ' ss:Hidden="1"'; | ||||
| 	} | ||||
| 	return o + '>'; | ||||
| } | ||||
| /* TODO */ | ||||
| function write_ws_xlml_table(ws, opts, idx, wb) { | ||||
| 	if(!ws['!ref']) return ""; | ||||
| @ -12971,12 +13122,17 @@ function write_ws_xlml_table(ws, opts, idx, wb) { | ||||
| 	var marr = ws['!merges'] || [], mi = 0; | ||||
| 	var o = []; | ||||
| 	if(ws['!cols']) ws['!cols'].forEach(function(n, i) { | ||||
| 		process_col(n); | ||||
| 		var w = !!n.width; | ||||
| 		var p = col_obj_w(i, n); | ||||
| 		o.push(writextag("Column",null, {"ss:Index":i+1, "ss:Width":width2px(p.width)})); | ||||
| 		var k = {"ss:Index":i+1}; | ||||
| 		if(w) k['ss:Width'] = width2px(p.width); | ||||
| 		if(n.hidden) k['ss:Hidden']="1"; | ||||
| 		o.push(writextag("Column",null,k)); | ||||
| 	}); | ||||
| 	var dense = Array.isArray(ws); | ||||
| 	for(var R = range.s.r; R <= range.e.r; ++R) { | ||||
| 		var row = ['<Row ss:Index="' + (R+1) + '">']; | ||||
| 		var row = [write_ws_xlml_row(R, (ws['!rows']||[])[R])]; | ||||
| 		for(var C = range.s.c; C <= range.e.c; ++C) { | ||||
| 			var skip = false; | ||||
| 			for(mi = 0; mi != marr.length; ++mi) { | ||||
| @ -13531,7 +13687,14 @@ function parse_workbook(blob, options) { | ||||
| 						process_col(colinfo[val.e+1]); | ||||
| 					} | ||||
| 				} break; | ||||
| 				case 'Row': break; // TODO
 | ||||
| 				case 'Row': { | ||||
| 					var rowobj = {}; | ||||
| 					if(val.hidden) { rowinfo[val.r] = rowobj; rowobj.hidden = true; } | ||||
| 					if(val.hpt) { | ||||
| 						rowinfo[val.r] = rowobj; | ||||
| 						rowobj.hpt = val.hpt; rowobj.hpx = pt2px(val.hpt); | ||||
| 					} | ||||
| 				} break; | ||||
| 
 | ||||
| 				case 'LeftMargin': | ||||
| 				case 'RightMargin': | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user