Skip to content

Commit

Permalink
row and column size and visibility
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
SheetJSDev committed Apr 28, 2017
1 parent c6f96c3 commit dcee744
Show file tree
Hide file tree
Showing 20 changed files with 999 additions and 235 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"curly": 0,
"comma-style": [ 2, "last" ],
"no-trailing-spaces": 2,
"semi": [ 2, "always" ],
"comma-dangle": [ 2, "never" ]
}
}
98 changes: 74 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):

| 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 |
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 | 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:

Expand Down Expand Up @@ -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
Expand All @@ -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
};
```

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
17 changes: 11 additions & 6 deletions bits/39_xlsbiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}


Expand Down
78 changes: 65 additions & 13 deletions bits/40_harb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -228,20 +228,45 @@ 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;
break;
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); }

Expand All @@ -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});
Expand All @@ -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;
}

Expand Down
10 changes: 7 additions & 3 deletions bits/45_styutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
11 changes: 6 additions & 5 deletions bits/66_wscommon.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading

0 comments on commit dcee744

Please sign in to comment.