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",
|
||||