Skip to content

Commit 5d18f82

Browse files
committed
common XLSB name ranges
1 parent d4beb13 commit 5d18f82

File tree

9 files changed

+438
-43
lines changed

9 files changed

+438
-43
lines changed

bits/62_fxls.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,15 @@ var PtgBinOp = {
688688
PtgSub: "-"
689689
};
690690

691+
// TODO: explore space
692+
function make_3d_range(start, end) {
693+
var s = start.lastIndexOf("!"), e = end.lastIndexOf("!");
694+
if(s == -1 && e == -1) return start + ":" + end;
695+
if(s > 0 && e > 0 && start.slice(0, s).toLowerCase() == end.slice(0, e).toLowerCase()) return start + ":" + end.slice(e+1);
696+
console.error("Cannot hydrate range", start, end);
697+
return start + ":" + end;
698+
}
699+
691700
// List of invalid characters needs to be tested further
692701
function formula_quote_sheet_name(sname/*:string*/, opts)/*:string*/ {
693702
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
@@ -790,7 +799,7 @@ function stringify_formula(formula/*Array<any>*/, range, cell/*:any*/, supbooks,
790799
break;
791800
case 'PtgRange': /* [MS-XLS] 2.5.198.83 */
792801
e1 = stack.pop(); e2 = stack.pop();
793-
stack.push(e2+":"+e1);
802+
stack.push(make_3d_range(e2,e1));
794803
break;
795804

796805
case 'PtgAttrChoose': /* [MS-XLS] 2.5.198.34 */

bits/63_fbin.js

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,21 +193,129 @@ function write_XLSBFormulaRef3D(str, wb) {
193193
var out = new_buf(17);
194194
out.write_shift(4, 9);
195195
out.write_shift(1, 0x1A | ((1)<<5));
196-
out.write_shift(2, 1 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
196+
out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
197197
out.write_shift(4, cell.r);
198198
out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
199199
out.write_shift(4, 0);
200200

201201
return out;
202202
}
203203

204+
/* Writes a PtgRefErr3d */
205+
function write_XLSBFormulaRefErr3D(str, wb) {
206+
var lastbang = str.lastIndexOf("!");
207+
var sname = str.slice(0, lastbang);
208+
str = str.slice(lastbang+1);
209+
if(sname.charAt(0) == "'") sname = sname.slice(1, -1).replace(/''/g, "'");
210+
211+
var out = new_buf(17);
212+
out.write_shift(4, 9);
213+
out.write_shift(1, 0x1C | ((1)<<5));
214+
out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
215+
out.write_shift(4, 0);
216+
out.write_shift(2, 0); // <== ColRelShort
217+
out.write_shift(4, 0);
218+
219+
return out;
220+
}
221+
222+
/* Writes a single sheet range [PtgRef PtgRef PtgRange] */
223+
function write_XLSBFormulaRange(_str) {
224+
var parts = _str.split(":"), str = parts[0];
225+
226+
var out = new_buf(23);
227+
out.write_shift(4, 15);
228+
229+
/* start cell */
230+
str = parts[0]; var cell = decode_cell(str);
231+
out.write_shift(1, 0x04 | ((1)<<5));
232+
out.write_shift(4, cell.r);
233+
out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
234+
out.write_shift(4, 0);
235+
236+
/* end cell */
237+
str = parts[1]; cell = decode_cell(str);
238+
out.write_shift(1, 0x04 | ((1)<<5));
239+
out.write_shift(4, cell.r);
240+
out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
241+
out.write_shift(4, 0);
242+
243+
/* PtgRange */
244+
out.write_shift(1, 0x11);
245+
246+
out.write_shift(4, 0);
247+
248+
return out;
249+
}
250+
251+
/* Writes a range with explicit sheet name [PtgRef3D PtgRef3D PtgRange] */
252+
function write_XLSBFormulaRangeWS(_str, wb) {
253+
var lastbang = _str.lastIndexOf("!");
254+
var sname = _str.slice(0, lastbang);
255+
_str = _str.slice(lastbang+1);
256+
if(sname.charAt(0) == "'") sname = sname.slice(1, -1).replace(/''/g, "'");
257+
var parts = _str.split(":"); str = parts[0];
258+
259+
var out = new_buf(27);
260+
out.write_shift(4, 19);
261+
262+
/* start cell */
263+
var str = parts[0], cell = decode_cell(str);
264+
out.write_shift(1, 0x1A | ((1)<<5));
265+
out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
266+
out.write_shift(4, cell.r);
267+
out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
268+
269+
/* end cell */
270+
str = parts[1]; cell = decode_cell(str);
271+
out.write_shift(1, 0x1A | ((1)<<5));
272+
out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
273+
out.write_shift(4, cell.r);
274+
out.write_shift(2, cell.c | ((str.charAt(0) == "$" ? 0 : 1)<<14) | ((str.match(/\$\d/) ? 0 : 1)<<15)); // <== ColRelShort
275+
276+
/* PtgRange */
277+
out.write_shift(1, 0x11);
278+
279+
out.write_shift(4, 0);
280+
281+
return out;
282+
}
283+
284+
/* Writes a range with explicit sheet name [PtgArea3d] */
285+
function write_XLSBFormulaArea3D(_str, wb) {
286+
var lastbang = _str.lastIndexOf("!");
287+
var sname = _str.slice(0, lastbang);
288+
_str = _str.slice(lastbang+1);
289+
if(sname.charAt(0) == "'") sname = sname.slice(1, -1).replace(/''/g, "'");
290+
var range = decode_range(_str);
291+
292+
var out = new_buf(23);
293+
out.write_shift(4, 15);
294+
295+
out.write_shift(1, 0x1B | ((1)<<5));
296+
out.write_shift(2, 2 + wb.SheetNames.map(function(n) { return n.toLowerCase(); }).indexOf(sname.toLowerCase()));
297+
out.write_shift(4, range.s.r);
298+
out.write_shift(4, range.e.r);
299+
out.write_shift(2, range.s.c);
300+
out.write_shift(2, range.e.c);
301+
302+
out.write_shift(4, 0);
303+
304+
return out;
305+
}
306+
307+
204308
/* General Formula */
205309
function write_XLSBFormula(val/*:string*/, wb) {
206310
if(/^#(DIV\/0!|GETTING_DATA|N\/A|NAME\?|NULL!|NUM!|REF!|VALUE!)$/.test(val)) return write_XLSBFormulaErr(+RBErr[val]);
207311
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef(val);
208-
if(val.match(/^(?:'[^\\\/?*\[\]:]*'|[^'][^\\\/?*\[\]:'`~!@#$%^()\-_=+{}|;,<.>]*)!\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef3D(val, wb);
209-
if(val.match(/^".*"$/)) return write_XLSBFormulaStr(val);
210-
if(val.match(/^\d+$/)) return write_XLSBFormulaNum(parseInt(val, 10));
312+
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRange(val);
313+
if(val.match(/^#REF!\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaArea3D(val, wb);
314+
if(val.match(/^(?:'[^\\\/?*\[\]:]*'|[^'][^\\\/?*\[\]:'`~!@#$%^()\-=+{}|;,<.>]*)!\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef3D(val, wb);
315+
if(val.match(/^(?:'[^\\\/?*\[\]:]*'|[^'][^\\\/?*\[\]:'`~!@#$%^()\-=+{}|;,<.>]*)!\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRangeWS(val, wb);
316+
if(/^(?:'[^\\\/?*\[\]:]*'|[^'][^\\\/?*\[\]:'`~!@#$%^()\-=+{}|;,<.>]*)!#REF!$/.test(val)) return write_XLSBFormulaRefErr3D(val, wb);
317+
if(/^".*"$/.test(val)) return write_XLSBFormulaStr(val);
318+
if(/^[+-]\d+$/.test(val)) return write_XLSBFormulaNum(parseInt(val, 10));
211319
throw "Formula |" + val + "| not supported for XLSB";
212320
}
213321
var write_XLSBNameParsedFormula = write_XLSBFormula;

bits/65_fods.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ function ods_to_csf_formula(f/*:string*/)/*:string*/ {
99
f = f.replace(/COM\.MICROSOFT\./g, "");
1010
/* Part 3 Section 5.8 References */
1111
f = f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, function($$, $1) { return $1.replace(/\./g,""); });
12+
f = f.replace(/\$'([^']|'')+'/g, function($$) { return $$.slice(1); });
13+
f = f.replace(/\$([^\]\. #$]+)/g, function($$, $1) { return ($1).match(/^([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])?(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})?$/) ? $$ : $1; });
1214
/* TODO: something other than this */
1315
f = f.replace(/\[.(#[A-Z]*[?!])\]/g, "$1");
1416
return f.replace(/[;~]/g,",").replace(/\|/g,";");
@@ -21,6 +23,8 @@ function csf_to_ods_formula(f/*:string*/)/*:string*/ {
2123
}
2224

2325
function ods_to_csf_3D(r/*:string*/)/*:[string, string]*/ {
26+
r = r.replace(/\$'([^']|'')+'/g, function($$) { return $$.slice(1); });
27+
r = r.replace(/\$([^\]\. #$]+)/g, function($$, $1) { return ($1).match(/^([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])?(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})?$/) ? $$ : $1; });
2428
var a = r.split(":");
2529
var s = a[0].split(".")[0];
2630
return [s, a[0].split(".")[1] + (a.length > 1 ? (":" + (a[1].split(".")[1] || a[1].split(".")[0])) : "")];

bits/73_wbbin.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,36 +64,40 @@ function parse_BrtFRTArchID$(data, length) {
6464
/* [MS-XLSB] 2.4.687 BrtName */
6565
function parse_BrtName(data, length, opts) {
6666
var end = data.l + length;
67-
data.l += 4; //var flags = data.read_shift(4);
67+
var flags = data.read_shift(4);
6868
data.l += 1; //var chKey = data.read_shift(1);
6969
var itab = data.read_shift(4);
7070
var name = parse_XLNameWideString(data);
7171
var formula = parse_XLSBNameParsedFormula(data, 0, opts);
7272
var comment = parse_XLNullableWideString(data);
73+
if(flags & 0x20) name = "_xlnm." + name;
7374
//if(0 /* fProc */) {
7475
// unusedstring1: XLNullableWideString
7576
// description: XLNullableWideString
7677
// helpTopic: XLNullableWideString
7778
// unusedstring2: XLNullableWideString
7879
//}
7980
data.l = end;
80-
var out = ({Name:name, Ptg:formula}/*:any*/);
81+
var out = ({Name:name, Ptg:formula, Flags: flags}/*:any*/);
8182
if(itab < 0xFFFFFFF) out.Sheet = itab;
8283
if(comment) out.Comment = comment;
8384
return out;
8485
}
8586
function write_BrtName(name, wb) {
8687
var o = new_buf(9);
87-
o.write_shift(4, 0); // flags
88+
var flags = 0;
89+
var dname = name.Name;
90+
if(XLSLblBuiltIn.indexOf(dname) > -1) { flags |= 0x20; dname = dname.slice(6); }
91+
o.write_shift(4, flags); // flags
8892
o.write_shift(1, 0); // chKey
8993
o.write_shift(4, name.Sheet == null ? 0xFFFFFFFF : name.Sheet);
9094

9195
var arr = [
9296
o,
93-
write_XLWideString(name.Name),
94-
write_XLSBNameParsedFormula(name.Ref, wb),
97+
write_XLWideString(dname),
98+
write_XLSBNameParsedFormula(name.Ref, wb)
9599
];
96-
if(name.Comment) arr.push(write_XLNullableWideString(name.Comment))
100+
if(name.Comment) arr.push(write_XLNullableWideString(name.Comment));
97101
else {
98102
var x = new_buf(4);
99103
x.write_shift(4, 0xFFFFFFFF);
@@ -106,7 +110,7 @@ function write_BrtName(name, wb) {
106110
// write_XLNullableWideString(helpTopic)
107111
// write_shift(4, 0xFFFFFFFF);
108112

109-
return bconcat(arr);
113+
return bconcat(arr);
110114
}
111115

112116
/* [MS-XLSB] 2.1.7.61 Workbook */
@@ -275,6 +279,7 @@ function write_BOOKVIEWS(ba, wb/*::, opts*/) {
275279
function write_BRTNAMES(ba, wb) {
276280
if(!wb.Workbook || !wb.Workbook.Names) return;
277281
wb.Workbook.Names.forEach(function(name) { try {
282+
if(name.Flags & 0x0e) return; // TODO: macro name write
278283
write_record(ba, 0x0027 /* BrtName */, write_BrtName(name, wb));
279284
} catch(e) {
280285
console.error("Could not serialize defined name " + JSON.stringify(name));
@@ -283,9 +288,10 @@ function write_BRTNAMES(ba, wb) {
283288

284289
function write_SELF_EXTERNS_xlsb(wb) {
285290
var L = wb.SheetNames.length;
286-
var o = new_buf(12 * L + 16);
287-
o.write_shift(4, L + 1);
291+
var o = new_buf(12 * L + 28);
292+
o.write_shift(4, L + 2);
288293
o.write_shift(4, 0); o.write_shift(4, -2); o.write_shift(4, -2); // workbook-level reference
294+
o.write_shift(4, 0); o.write_shift(4, -1); o.write_shift(4, -1); // #REF!...
289295
for(var i = 0; i < L; ++i) {
290296
o.write_shift(4, 0); o.write_shift(4, i); o.write_shift(4, i);
291297
}

bits/78_writebiff.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ function write_ws_biff8(idx/*:number*/, opts, wb/*:Workbook*/) {
242242
/* ... */
243243

244244
if(b8) ws['!links'] = [];
245+
var comments = [];
245246
for(var R = range.s.r; R <= range.e.r; ++R) {
246247
rr = encode_row(R);
247248
for(var C = range.s.c; C <= range.e.c; ++C) {
@@ -252,10 +253,13 @@ function write_ws_biff8(idx/*:number*/, opts, wb/*:Workbook*/) {
252253
/* write cell */
253254
write_ws_biff8_cell(ba, cell, R, C, opts);
254255
if(b8 && cell.l) ws['!links'].push([ref, cell.l]);
256+
if(b8 && cell.c) comments.push([ref, cell.c]);
255257
}
256258
}
257259
var cname/*:string*/ = _sheet.CodeName || _sheet.name || s;
258260
/* ... */
261+
// if(b8) comments.forEach(function(comment) { write_biff_rec(ba, 0x001c /* Note */, write_NoteSh(comment)); });
262+
/* ... */
259263
if(b8) write_biff_rec(ba, 0x023e /* Window2 */, write_Window2((_WB.Views||[])[0]));
260264
/* ... */
261265
if(b8 && (ws['!merges']||[]).length) write_biff_rec(ba, 0x00e5 /* MergeCells */, write_MergeCells(ws['!merges']));

packages/ssf/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export function get_table(): SSF$Table;
2626
/** Set format table */
2727
export function load_table(tbl: SSF$Table): void;
2828

29+
/** Find the relevant sub-format for a given value*/
30+
export function choose_format(fmt: string, value: any): [number, string];
2931

3032
/** Parsed date */
3133
export interface SSF$Date {

test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1716,7 +1716,7 @@ describe('roundtrip features', function() {
17161716
describe('should preserve cell comments', function() { [
17171717
['xlsx', paths.cstxlsx],
17181718
['xlsb', paths.cstxlsb],
1719-
//['xls', paths.cstxlsx],
1719+
//['xls', paths.cstxls],
17201720
['xlml', paths.cstxml]
17211721
//['ods', paths.cstods]
17221722
].forEach(function(w) {

0 commit comments

Comments
 (0)