Skip to content

Commit 1587688

Browse files
committed
Page Margins
- XLSB read/write page margins - XLSX/XLS/XLML read page margins - separated encrypted XLSX/XLSB document logic from XLS
1 parent 0189bc2 commit 1587688

18 files changed

+968
-82
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,28 @@ Special sheet keys (accessible as `sheet[key]`, each starting with `!`):
563563
When reading a worksheet with the `sheetRows` property set, the ref parameter
564564
will use the restricted range. The original range is set at `ws['!fullref']`
565565

566+
- `sheet['!margins']`: Object representing the page margins. The default values
567+
follow Excel's "normal" preset. Excel also has a "wide" and a "narrow" preset
568+
but they are stored as raw measurements. The main properties are listed below:
569+
570+
| key | description | "normal" | "wide" | "narrow" |
571+
|----------|------------------------|:---------|:-------|:-------- |
572+
| `left` | left margin (inches) | `0.7` | `1.0` | `0.25` |
573+
| `right` | right margin (inches) | `0.7` | `1.0` | `0.25` |
574+
| `top` | top margin (inches) | `0.75` | `1.0` | `0.75` |
575+
| `bottom` | bottom margin (inches) | `0.75` | `1.0` | `0.75` |
576+
| `header` | header margin (inches) | `0.3` | `0.5` | `0.3` |
577+
| `footer` | footer margin (inches) | `0.3` | `0.5` | `0.3` |
578+
579+
```js
580+
/* Set worksheet sheet to "normal" */
581+
sheet["!margins"] = { left:0.7, right:0.7, top:0.75, bottom:0.75, header:0.3, footer:0.3 }
582+
/* Set worksheet sheet to "wide" */
583+
sheet["!margins"] = { left:1.0, right:1.0, top:1.0, bottom:1.0, header:0.5, footer:0.5 }
584+
/* Set worksheet sheet to "narrow" */
585+
sheet["!margins"] = { left:0.25, right:0.25, top:0.75, bottom:0.75, header:0.3, footer:0.3 }
586+
```
587+
566588
#### Worksheet Object
567589

568590
In addition to the base sheet keys, worksheets also add:

bits/23_binutils.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ var __lpstr, ___lpstr;
3636
__lpstr = ___lpstr = function lpstr_(b,i) { var len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : "";};
3737
var __lpwstr, ___lpwstr;
3838
__lpwstr = ___lpwstr = function lpwstr_(b,i) { var len = 2*__readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : "";};
39+
var __lpp4, ___lpp4;
40+
__lpp4 = ___lpp4 = function lpp4_(b,i) { var len = __readUInt32LE(b,i); return len > 0 ? __utf16le(b, i+4,i+4+len) : "";};
41+
var __8lpp4, ___8lpp4;
42+
__8lpp4 = ___8lpp4 = function lpp4_8(b,i) { var len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len) : "";};
3943
var __double, ___double;
4044
__double = ___double = function(b, idx) { return read_double_le(b, idx);};
4145

@@ -45,6 +49,8 @@ if(has_buf/*:: && typeof Buffer != 'undefined'*/) {
4549
__hexlify = function(b,s,l) { return Buffer.isBuffer(b) ? b.toString('hex',s,s+l) : ___hexlify(b,s,l); };
4650
__lpstr = function lpstr_b(b,i) { if(!Buffer.isBuffer(b)) return ___lpstr(b, i); var len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8',i+4,i+4+len-1) : "";};
4751
__lpwstr = function lpwstr_b(b,i) { if(!Buffer.isBuffer(b)) return ___lpwstr(b, i); var len = 2*b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len-1);};
52+
__lpp4 = function lpp4_b(b,i) { if(!Buffer.isBuffer(b)) return ___lpp4(b, i); var len = b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len);};
53+
__8lpp4 = function lpp4_8b(b,i) { if(!Buffer.isBuffer(b)) return ___8lpp4(b, i); var len = b.readUInt32LE(i); return b.toString('utf8',i+4,i+4+len);};
4854
__utf8 = function utf8_b(b, s,e) { return b.toString('utf8',s,e); };
4955
__toBuffer = function(bufs) { return (bufs[0].length > 0 && Buffer.isBuffer(bufs[0][0])) ? Buffer.concat(bufs[0]) : ___toBuffer(bufs);};
5056
bconcat = function(bufs) { return Buffer.isBuffer(bufs[0]) ? Buffer.concat(bufs) : [].concat.apply([], bufs); };
@@ -58,6 +64,8 @@ if(typeof cptable !== 'undefined') {
5864
__utf8 = function(b,s,e) { return cptable.utils.decode(65001, b.slice(s,e)); };
5965
__lpstr = function(b,i) { var len = __readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(current_codepage, b.slice(i+4, i+4+len-1)) : "";};
6066
__lpwstr = function(b,i) { var len = 2*__readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(1200, b.slice(i+4,i+4+len-1)) : "";};
67+
__lpp4 = function(b,i) { var len = __readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(1200, b.slice(i+4,i+4+len)) : "";};
68+
__8lpp4 = function(b,i) { var len = __readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(65001, b.slice(i+4,i+4+len)) : "";};
6169
}
6270

6371
var __readUInt8 = function(b, idx) { return b[idx]; };
@@ -91,6 +99,10 @@ function ReadShift(size/*:number*/, t/*:?string*/) {
9199
case 'lpstr': o = __lpstr(this, this.l); size = 5 + o.length; break;
92100
/* [MS-OLEDS] 2.1.5 LengthPrefixedUnicodeString */
93101
case 'lpwstr': o = __lpwstr(this, this.l); size = 5 + o.length; if(o[o.length-1] == '\u0000') size += 2; break;
102+
/* [MS-OFFCRYPTO] 2.1.2 Length-Prefixed Padded Unicode String (UNICODE-LP-P4) */
103+
case 'lpp4': size = 4 + __readUInt32LE(this, this.l); o = __lpp4(this, this.l); if(size & 0x02) size += 2; break;
104+
/* [MS-OFFCRYPTO] 2.1.3 Length-Prefixed UTF-8 String (UTF-8-LP-P4) */
105+
case '8lpp4': size = 4 + __readUInt32LE(this, this.l); o = __8lpp4(this, this.l); if(size & 0x03) size += 4 - (size & 0x03); break;
94106

95107
case 'cstr': size = 0; o = "";
96108
while((w=__readUInt8(this, this.l + size++))!==0) oo.push(_getchar(w));

bits/39_xlsbiff.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,16 @@ function parse_ColInfo(blob, length, opts) {
655655
return {s:colFirst, e:colLast, w:coldx, ixfe:ixfe, flags:flags};
656656
}
657657

658+
/* 2.4.257 */
659+
function parse_Setup(blob, length, opts) {
660+
var o = {};
661+
blob.l += 16;
662+
o.header = parse_Xnum(blob, 8);
663+
o.footer = parse_Xnum(blob, 8);
664+
blob.l += 2;
665+
return o;
666+
}
667+
658668
/* 2.4.261 */
659669
function parse_ShtProps(blob, length, opts) {
660670
var def = {area:false};
@@ -664,7 +674,6 @@ function parse_ShtProps(blob, length, opts) {
664674
return def;
665675
}
666676

667-
668677
var parse_Style = parsenoop;
669678
var parse_StyleExt = parsenoop;
670679

@@ -747,7 +756,6 @@ var parse_FnGroupName = parsenoop;
747756
var parse_FilterMode = parsenoop;
748757
var parse_AutoFilterInfo = parsenoop;
749758
var parse_AutoFilter = parsenoop;
750-
var parse_Setup = parsenoop;
751759
var parse_ScenMan = parsenoop;
752760
var parse_SCENARIO = parsenoop;
753761
var parse_SxView = parsenoop;

bits/44_offcrypto.js

Lines changed: 128 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,154 @@ function _JS2ANSI(str/*:string*/)/*:Array<number>*/ {
66
}
77

88
/* [MS-OFFCRYPTO] 2.1.4 Version */
9-
function parse_Version(blob, length/*:number*/) {
9+
function parse_CRYPTOVersion(blob, length/*:number*/) {
1010
var o = {};
1111
o.Major = blob.read_shift(2);
1212
o.Minor = blob.read_shift(2);
1313
return o;
1414
}
15-
/* [MS-OFFCRYPTO] 2.3.2 Encryption Header */
16-
function parse_EncryptionHeader(blob, length/*:number*/) {
15+
16+
/* [MS-OFFCRYPTO] 2.1.5 DataSpaceVersionInfo */
17+
function parse_DataSpaceVersionInfo(blob, length) {
1718
var o = {};
18-
o.Flags = blob.read_shift(4);
19+
o.id = blob.read_shift(0, 'lpp4');
20+
o.R = parse_CRYPTOVersion(blob, 4);
21+
o.U = parse_CRYPTOVersion(blob, 4);
22+
o.W = parse_CRYPTOVersion(blob, 4);
23+
return o;
24+
}
25+
26+
/* [MS-OFFCRYPTO] 2.1.6.1 DataSpaceMapEntry Structure */
27+
function parse_DataSpaceMapEntry(blob) {
28+
var len = blob.read_shift(4);
29+
var end = blob.l + len - 4;
30+
var o = {};
31+
var cnt = blob.read_shift(4);
32+
var comps = [];
33+
while(cnt-- > 0) {
34+
/* [MS-OFFCRYPTO] 2.1.6.2 DataSpaceReferenceComponent Structure */
35+
var rc = {};
36+
rc.t = blob.read_shift(4);
37+
rc.v = blob.read_shift(0, 'lpp4');
38+
comps.push(rc);
39+
}
40+
o.name = blob.read_shift(0, 'lpp4');
41+
o.comps = comps;
42+
return o;
43+
}
1944

20-
// Check if SizeExtra is 0x00000000
21-
var tmp = blob.read_shift(4);
22-
if(tmp !== 0) throw 'Unrecognized SizeExtra: ' + tmp;
45+
/* [MS-OFFCRYPTO] 2.1.6 DataSpaceMap */
46+
function parse_DataSpaceMap(blob, length) {
47+
var o = [];
48+
blob.l += 4; // must be 0x8
49+
var cnt = blob.read_shift(4);
50+
while(cnt-- > 0) o.push(parse_DataSpaceMapEntry(blob));
51+
return o;
52+
}
2353

54+
/* [MS-OFFCRYPTO] 2.1.7 DataSpaceDefinition */
55+
function parse_DataSpaceDefinition(blob, length) {
56+
var o = [];
57+
blob.l += 4; // must be 0x8
58+
var cnt = blob.read_shift(4);
59+
while(cnt-- > 0) o.push(blob.read_shift(0, 'lpp4'));
60+
return o;
61+
}
62+
63+
/* [MS-OFFCRYPTO] 2.1.8 DataSpaceDefinition */
64+
function parse_TransformInfoHeader(blob, length) {
65+
var o = {};
66+
var len = blob.read_shift(4);
67+
var tgt = blob.l + len - 4;
68+
blob.l += 4; // must be 0x1
69+
o.id = blob.read_shift(0, 'lpp4');
70+
// tgt == len
71+
o.name = blob.read_shift(0, 'lpp4');
72+
o.R = parse_CRYPTOVersion(blob, 4);
73+
o.U = parse_CRYPTOVersion(blob, 4);
74+
o.W = parse_CRYPTOVersion(blob, 4);
75+
return o;
76+
}
77+
78+
function parse_Primary(blob, length) {
79+
/* [MS-OFFCRYPTO] 2.2.6 IRMDSTransformInfo */
80+
var hdr = parse_TransformInfoHeader(blob);
81+
/* [MS-OFFCRYPTO] 2.1.9 EncryptionTransformInfo */
82+
hdr.ename = blob.read_shift(0, '8lpp4');
83+
hdr.blksz = blob.read_shift(4);
84+
hdr.cmode = blob.read_shift(4);
85+
if(blob.read_shift(4) != 0x04) throw new Error("Bad !Primary record");
86+
return hdr;
87+
}
88+
89+
/* [MS-OFFCRYPTO] 2.3.2 Encryption Header */
90+
function parse_EncryptionHeader(blob, length/*:number*/) {
91+
var tgt = blob.l + length;
92+
var o = {};
93+
o.Flags = (blob.read_shift(4) & 0x3F);
94+
blob.l += 4;
2495
o.AlgID = blob.read_shift(4);
96+
var valid = false;
2597
switch(o.AlgID) {
26-
case 0: case 0x6801: case 0x660E: case 0x660F: case 0x6610: break;
98+
case 0x660E: case 0x660F: case 0x6610: valid = (o.Flags == 0x24); break;
99+
case 0x6801: valid = (o.Flags == 0x04); break;
100+
case 0: valid = (o.Flags == 0x10 || o.Flags == 0x04 || o.Flags == 0x24); break;
27101
default: throw 'Unrecognized encryption algorithm: ' + o.AlgID;
28102
}
29-
parsenoop(blob, length-12);
103+
if(!valid) throw new Error("Encryption Flags/AlgID mismatch");
104+
o.AlgIDHash = blob.read_shift(4);
105+
o.KeySize = blob.read_shift(4);
106+
o.ProviderType = blob.read_shift(4);
107+
blob.l += 8;
108+
o.CSPName = blob.read_shift((tgt-blob.l)>>1, 'utf16le').slice(0,-1);
109+
blob.l = tgt;
30110
return o;
31111
}
32112

33113
/* [MS-OFFCRYPTO] 2.3.3 Encryption Verifier */
34114
function parse_EncryptionVerifier(blob, length/*:number*/) {
35-
return parsenoop(blob, length);
115+
var o = {};
116+
blob.l += 4; // SaltSize must be 0x10
117+
o.Salt = blob.slice(blob.l, blob.l+16); blob.l += 16;
118+
o.Verifier = blob.slice(blob.l, blob.l+16); blob.l += 16;
119+
var sz = blob.read_shift(4);
120+
o.VerifierHash = blob.slice(blob.l, blob.l + sz); blob.l += sz;
121+
return o;
36122
}
123+
124+
/* [MS-OFFCRYPTO] 2.3.4.* EncryptionInfo Stream */
125+
function parse_EncryptionInfo(blob, length) {
126+
var vers = parse_CRYPTOVersion(blob);
127+
switch(vers.Minor) {
128+
case 0x02: return parse_EncInfoStd(blob, vers);
129+
case 0x03: return parse_EncInfoExt(blob, vers);
130+
case 0x04: return parse_EncInfoAgl(blob, vers);
131+
}
132+
throw new Error("ECMA-376 Encryped file unrecognized Version: " + vers.Minor);
133+
}
134+
135+
/* [MS-OFFCRYPTO] 2.3.4.5 EncryptionInfo Stream (Standard Encryption) */
136+
function parse_EncInfoStd(blob, vers) {
137+
var flags = blob.read_shift(4);
138+
if((flags & 0x3F) != 0x24) throw new Error("EncryptionInfo mismatch");
139+
var sz = blob.read_shift(4);
140+
var tgt = blob.l + sz;
141+
var hdr = parse_EncryptionHeader(blob, sz);
142+
var verifier = parse_EncryptionVerifier(blob, blob.length - blob.l);
143+
return { t:"Std", h:hdr, v:verifier };
144+
}
145+
/* [MS-OFFCRYPTO] 2.3.4.6 EncryptionInfo Stream (Extensible Encryption) */
146+
function parse_EncInfoExt(blob, vers) { throw new Error("File is password-protected: ECMA-376 Extensible"); }
147+
/* [MS-OFFCRYPTO] 2.3.4.10 EncryptionInfo Stream (Agile Encryption) */
148+
function parse_EncInfoAgl(blob, vers) { throw new Error("File is password-protected: ECMA-376 Agile"); }
149+
150+
151+
152+
37153
/* [MS-OFFCRYPTO] 2.3.5.1 RC4 CryptoAPI Encryption Header */
38154
function parse_RC4CryptoHeader(blob, length/*:number*/) {
39155
var o = {};
40-
var vers = o.EncryptionVersionInfo = parse_Version(blob, 4); length -= 4;
156+
var vers = o.EncryptionVersionInfo = parse_CRYPTOVersion(blob, 4); length -= 4;
41157
if(vers.Minor != 2) throw 'unrecognized minor version code: ' + vers.Minor;
42158
if(vers.Major > 4 || vers.Major < 2) throw 'unrecognized major version code: ' + vers.Major;
43159
o.Flags = blob.read_shift(4); length -= 4;
@@ -49,7 +165,7 @@ function parse_RC4CryptoHeader(blob, length/*:number*/) {
49165
/* [MS-OFFCRYPTO] 2.3.6.1 RC4 Encryption Header */
50166
function parse_RC4Header(blob, length/*:number*/) {
51167
var o = {};
52-
var vers = o.EncryptionVersionInfo = parse_Version(blob, 4); length -= 4;
168+
var vers = o.EncryptionVersionInfo = parse_CRYPTOVersion(blob, 4); length -= 4;
53169
if(vers.Major != 1 || vers.Minor != 1) throw 'unrecognized version code ' + vers.Major + ' : ' + vers.Minor;
54170
o.Salt = blob.read_shift(16);
55171
o.EncryptedVerifier = blob.read_shift(16);

bits/66_wscommon.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ function col_obj_w(C/*:number*/, col) {
2424
return p;
2525
}
2626

27+
function default_margins(margins, mode) {
28+
if(!margins) return;
29+
var defs = [0.7, 0.7, 0.75, 0.75, 0.3, 0.3];
30+
if(mode == 'xlml') defs = [1, 1, 1, 1, 0.5, 0.5];
31+
if(margins.left == null) margins.left = defs[0];
32+
if(margins.right == null) margins.right = defs[1];
33+
if(margins.top == null) margins.top = defs[2];
34+
if(margins.bottom == null) margins.bottom = defs[3];
35+
if(margins.header == null) margins.header = defs[4];
36+
if(margins.footer == null) margins.footer = defs[5];
37+
}
38+
2739
function get_cell_style(styles, cell, opts) {
2840
var z = opts.revssf[cell.z != null ? cell.z : "General"];
2941
for(var i = 0, len = styles.length; i != len; ++i) if(styles[i].numFmtId === z) return i;

bits/67_wsxml.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ var hlinkregex = /<(?:\w:)?hyperlink [^>]*>/mg;
88
var dimregex = /"(\w*:\w*)"/;
99
var colregex = /<(?:\w:)?col[^>]*[\/]?>/g;
1010
var afregex = /<(?:\w:)?autoFilter[^>]*([\/]|>([^\u2603]*)<\/(?:\w:)?autoFilter)>/g;
11+
var marginregex= /<(?:\w:)?pageMargins[^>]*\/>/g;
1112
/* 18.3 Worksheets */
1213
function parse_ws_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksheet*/ {
1314
if(!data) return data;
@@ -57,6 +58,10 @@ function parse_ws_xml(data/*:?string*/, opts, rels, wb, themes, styles)/*:Worksh
5758
var hlink = data2.match(hlinkregex);
5859
if(hlink) parse_ws_xml_hlinks(s, hlink, rels);
5960

61+
/* 18.3.1.62 pageMargins CT_PageMargins */
62+
var margins = data2.match(marginregex);
63+
if(margins) s['!margins'] = parse_ws_xml_margins(parsexmltag(margins[0]));
64+
6065
if(!s["!ref"] && refguess.e.c >= refguess.s.c && refguess.e.r >= refguess.s.r) s["!ref"] = encode_range(refguess);
6166
if(opts.sheetRows > 0 && s["!ref"]) {
6267
var tmpref = safe_decode_range(s["!ref"]);
@@ -131,6 +136,14 @@ function parse_ws_xml_hlinks(s, data/*:Array<string>*/, rels) {
131136
}
132137
}
133138

139+
function parse_ws_xml_margins(margin) {
140+
var o = {};
141+
["left", "right", "top", "bottom", "header", "footer"].forEach(function(k) {
142+
if(margin[k]) o[k] = parseFloat(margin[k]);
143+
});
144+
return o;
145+
}
146+
134147
function parse_ws_xml_cols(columns, cols) {
135148
var seencol = false;
136149
for(var coli = 0; coli != cols.length; ++coli) {

bits/68_wsbin.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,29 @@ function write_BrtColInfo(C/*:number*/, col, o) {
289289
return o;
290290
}
291291

292+
/* [MS-XLSB] 2.4.672 BrtMargins */
293+
function parse_BrtMargins(data, length, opts) {
294+
return {
295+
left: parse_Xnum(data, 8),
296+
right: parse_Xnum(data, 8),
297+
top: parse_Xnum(data, 8),
298+
bottom: parse_Xnum(data, 8),
299+
header: parse_Xnum(data, 8),
300+
footer: parse_Xnum(data, 8)
301+
};
302+
}
303+
function write_BrtMargins(margins, o) {
304+
if(o == null) o = new_buf(6*8);
305+
default_margins(margins);
306+
write_Xnum(margins.left, o);
307+
write_Xnum(margins.right, o);
308+
write_Xnum(margins.top, o);
309+
write_Xnum(margins.bottom, o);
310+
write_Xnum(margins.header, o);
311+
write_Xnum(margins.footer, o);
312+
return o;
313+
}
314+
292315
/* [MS-XLSB] 2.4.740 BrtSheetProtection */
293316
function write_BrtSheetProtection(sp, o) {
294317
if(o == null) o = new_buf(16*4+2);
@@ -467,6 +490,10 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ {
467490
s['!autofilter'] = { ref:encode_range(val) };
468491
break;
469492

493+
case 0x01DC: /* 'BrtMargins' */
494+
s['!margins'] = val;
495+
break;
496+
470497
case 0x00AF: /* 'BrtAFilterDateGroupItem' */
471498
case 0x0284: /* 'BrtActiveX' */
472499
case 0x0271: /* 'BrtBigName' */
@@ -498,7 +525,6 @@ function parse_ws_bin(data, _opts, rels, wb, themes, styles)/*:Worksheet*/ {
498525
case 0x0227: /* 'BrtLegacyDrawing' */
499526
case 0x0228: /* 'BrtLegacyDrawingHF' */
500527
case 0x0295: /* 'BrtListPart' */
501-
case 0x01DC: /* 'BrtMargins' */
502528
case 0x027F: /* 'BrtOleObject' */
503529
case 0x01DE: /* 'BrtPageSetup' */
504530
case 0x0097: /* 'BrtPane' */
@@ -704,7 +730,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) {
704730
/* [DVALS] */
705731
write_HLINKS(ba, ws, rels);
706732
/* [BrtPrintOptions] */
707-
/* [BrtMargins] */
733+
if(ws['!margins']) write_record(ba, "BrtMargins", write_BrtMargins(ws['!margins']));
708734
/* [BrtPageSetup] */
709735
/* [HEADERFOOTER] */
710736
/* [RWBRK] */

0 commit comments

Comments
 (0)