unified autofilter defined name sync on export

This commit is contained in:
SheetJS 2022-05-30 04:40:51 -04:00
parent 5d18f82664
commit 83ddb4c120
32 changed files with 510 additions and 508 deletions

@ -44,6 +44,17 @@ function encode_range(cs/*:CellAddrSpec|Range*/,ce/*:?CellAddrSpec*/)/*:string*/
/*:: if(typeof ce !== 'string') throw "unreachable"; */
return cs == ce ? cs : cs + ":" + ce;
}
function fix_range(a1/*:string*/)/*:string*/ {
var s = decode_range(a1);
return "$" + encode_col(s.s.c) + "$" + encode_row(s.s.r) + ":$" + encode_col(s.e.c) + "$" + encode_row(s.e.r);
}
// List of invalid characters needs to be tested further
function formula_quote_sheet_name(sname/*:string*/, opts)/*:string*/ {
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname.replace(/'/g, "''") + "'";
return sname;
}
function safe_decode_range(range/*:string*/)/*:Range*/ {
var o = {s:{c:0,r:0},e:{c:0,r:0}};

@ -304,3 +304,20 @@ var RBErr = {
"#WTF?": 0xFF
};
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];

@ -669,22 +669,6 @@ function parse_ExternName(blob, length, opts) {
}
/* [MS-XLS] 2.4.150 TODO */
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
function parse_Lbl(blob, length, opts) {
var target = blob.l + length;
var flags = blob.read_shift(2);

@ -29,8 +29,8 @@ var a1_to_rc = /*#__PURE__*/(function(){
return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5) {
var c = decode_col($3) - ($2 ? 0 : base.c);
var r = decode_row($5) - ($4 ? 0 : base.r);
var R = (r == 0 ? "" : !$4 ? "[" + r + "]" : (r+1));
var C = (c == 0 ? "" : !$2 ? "[" + c + "]" : (c+1));
var R = $4 == "$" ? (r+1) : (r == 0 ? "" : "[" + r + "]");
var C = $2 == "$" ? (c+1) : (c == 0 ? "" : "[" + c + "]");
return $1 + "R" + R + "C" + C;
});
};

@ -697,12 +697,6 @@ function make_3d_range(start, end) {
return start + ":" + end;
}
// List of invalid characters needs to be tested further
function formula_quote_sheet_name(sname/*:string*/, opts)/*:string*/ {
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname + "'";
return sname;
}
function get_ixti_raw(supbooks, ixti/*:number*/, opts)/*:string*/ {
if(!supbooks) return "SH33TJSERR0";
if(opts.biff > 8 && (!supbooks.XTI || !supbooks.XTI[ixti])) return supbooks.SheetNames[ixti];

@ -228,7 +228,7 @@ function write_ws_xml_autofilter(data, ws, wb, idx)/*:string*/ {
var name = names[i];
if(name.Name != '_xlnm._FilterDatabase') continue;
if(name.Sheet != idx) continue;
name.Ref = "'" + wb.SheetNames[idx] + "'!" + ref; break;
name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
}
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref });
return writextag("autoFilter", null, {ref:ref});

@ -938,9 +938,9 @@ function write_AUTOFILTER(ba, ws, wb, idx) {
var name = names[i];
if(name.Name != '_xlnm._FilterDatabase') continue;
if(name.Sheet != idx) continue;
name.Ref = "'" + wb.SheetNames[idx] + "'!" + ref; break;
name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
}
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref });
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref) });
write_record(ba, 0x00A1 /* BrtBeginAFilter */, write_UncheckedRfX(safe_decode_range(ref)));
/* *FILTERCOLUMN */

@ -140,5 +140,16 @@ function check_wb(wb) {
var Sheets = (wb.Workbook && wb.Workbook.Sheets) || [];
check_wb_names(wb.SheetNames, Sheets, !!wb.vbaraw);
for(var i = 0; i < wb.SheetNames.length; ++i) check_ws(wb.Sheets[wb.SheetNames[i]], wb.SheetNames[i], i);
wb.SheetNames.forEach(function(n, i) {
var ws = wb.Sheets[n];
if(!ws || !ws["!autofilter"]) return;
var DN;
if(!wb.Workbook) wb.Workbook = {};
if(!wb.Workbook.Names) wb.Workbook.Names = [];
wb.Workbook.Names.forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == i) DN = dn; });
var nn = formula_quote_sheet_name(n) + "!" + fix_range(ws["!autofilter"].ref);
if(DN) DN.Ref = nn;
else wb.Workbook.Names.push({Name: "_xlnm._FilterDatabase", Sheet: i, Ref: nn});
});
/* TODO: validate workbook */
}

@ -159,6 +159,10 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a
if(cell.StyleID !== undefined) cell.ixfe = cell.StyleID;
}
function xlml_prefix_dname(dname) {
return XLSLblBuiltIn.indexOf("_xlnm." + dname) > -1 ? "_xlnm." + dname : dname;
}
function xlml_clean_comment(comment/*:any*/) {
comment.t = comment.v || "";
comment.t = comment.t.replace(/\r\n/g,"\n").replace(/\r/g,"\n");
@ -366,7 +370,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ {
if(!Workbook.Names) Workbook.Names = [];
var _NamedRange = parsexmltag(Rn[0]);
var _DefinedName/*:DefinedName*/ = ({
Name: _NamedRange.Name,
Name: xlml_prefix_dname(_NamedRange.Name),
Ref: rc_to_a1(_NamedRange.RefersTo.slice(1), {r:0, c:0})
}/*:any*/);
if(Workbook.Sheets.length>0) _DefinedName.Sheet=Workbook.Sheets.length-1;
@ -956,7 +960,7 @@ function write_sty_xlml(wb, opts)/*:string*/ {
});
return writextag("Styles", styles.join(""));
}
function write_name_xlml(n) { return writextag("NamedRange", null, {"ss:Name": n.Name, "ss:RefersTo":"=" + a1_to_rc(n.Ref, {r:0,c:0})}); }
function write_name_xlml(n) { return writextag("NamedRange", null, {"ss:Name": n.Name.slice(0,6) == "_xlnm." ? n.Name.slice(6) : n.Name, "ss:RefersTo":"=" + a1_to_rc(n.Ref, {r:0,c:0})}); }
function write_names_xlml(wb/*::, opts*/)/*:string*/ {
if(!((wb||{}).Workbook||{}).Names) return "";
/*:: if(!wb || !wb.Workbook || !wb.Workbook.Names) throw new Error("unreachable"); */
@ -1209,6 +1213,8 @@ function write_ws_xlml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ {
/* WorksheetOptions */
o.push(write_ws_xlml_wsopts(ws, opts, idx, wb));
if(ws["!autofilter"]) o.push('<AutoFilter x:Range="' + a1_to_rc(fix_range(ws["!autofilter"].ref), {r:0,c:0}) + '" xmlns="urn:schemas-microsoft-com:office:excel"></AutoFilter>');
return o.join("");
}
function write_xlml(wb, opts)/*:string*/ {

@ -44,7 +44,7 @@ can be installed with Bash on Windows or with `cygwin`.
- [`nw.js application`](nwjs/)
- [`Chrome / Chromium extensions`](chrome/)
- [`Google Sheets API`](https://docs.sheetjs.com/docs/getting-started/demos/gsheet)
- [`Adobe ExtendScript`](extendscript/)
- [`Adobe Apps`](https://docs.sheetjs.com/docs/getting-started/demos/extendscript)
- [`Headless Browsers`](headless/)
- [`canvas-datagrid`](datagrid/)
- [`x-spreadsheet`](xspreadsheet/)

@ -1 +0,0 @@
xlsx.extendscript.js

@ -1,16 +0,0 @@
APPS= aftereffects estoolkit illustrator indesign photoshop
TARGETS=$(patsubst %,%.jsx,$(APPS))
.PHONY: all
all: deps $(TARGETS)
.PHONY: deps
deps:
cp ../../dist/xlsx.extendscript.js .
%.base:
echo "#target $*" > $@
.PHONY: $(TARGETS)
$(TARGETS):%.jsx:%.base test.jsx
cat $^ > $@

@ -1,89 +1,7 @@
# Adobe ExtendScript
ExtendScript adds some features to a limited form of ECMAScript version 3. With
the included shim, the library can run within Photoshop and other Adobe apps!
The main file is `test.jsx`. Target-specific files prepend target directives.
Copy the `test.jsx` file as well as the `xlsx.extendscript.js` library script
to wherever you want the scripts to reside.
## ExtendScript Quirks
There are numerous quirks in ExtendScript code parsing, especially related to
Boolean and bit operations. Most JS tooling will generate code that is not
compatible with ExtendScript. It is highly recommended to `#include` the `dist`
file directly and avoid trying to minify or pack as part of a larger project.
## File I/O
Using the `"binary"` encoding, file operations will work with binary strings
that play nice with the `"binary"` type of this library. The `readFile` and
`writeFile` library functions wrap the File logic:
```js
/* Read file from disk */
var workbook = XLSX.readFile(filename);
/* Write file to disk */
XLSX.writeFile(workbook, filename);
```
<details><summary><b>Implementation Details</b> (click to show)</summary>
The `readFile` and `writeFile` functions use `"binary"` encoding under the hood:
```js
/* Read file from disk without using readFile */
var infile = File(filename);
infile.open("r");
infile.encoding = "binary";
var data = infile.read();
var workbook = XLSX.read(data, {type:"binary"});
infile.close();
/* Write file to disk without using writeFile */
var outFile = File(filename);
outFile.open("w");
outFile.encoding = "binary";
outFile.write(workbook);
outFile.close();
```
</details>
## Demo
The demo shows:
- loading the library in ExtendScript using `#include`:
```js
#include "xlsx.extendscript.js"
```
- opening a file with `XLSX.readFile`:
```js
var workbook = XLSX.readFile("sheetjs.xlsx");
```
- converting a worksheet to an array of arrays:
```js
var first_sheet_name = workbook.SheetNames[0];
var first_worksheet = workbook.Sheets[first_sheet_name];
var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1});
alert(data);
```
- writing a new workbook file:
```js
XLSX.writeFile(workbook, "sheetjs.slk");
```
[The new demo](https://docs.sheetjs.com/docs/getting-started/demos/extendscript)
has a more focused Photoshop example as well as notes about other extensibility
frameworks shipping with newer versions of Creative Cloud apps.
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)

@ -1 +0,0 @@
#target aftereffects

@ -1,45 +0,0 @@
#target aftereffects
var thisFile = new File($.fileName);
var basePath = thisFile.path;
#include "xlsx.extendscript.js";
var filename = "/sheetjs.xlsx";
/* Read file from disk */
var workbook = XLSX.readFile(basePath + filename, {cellDates:true});
/* Display first worksheet */
var first_sheet_name = workbook.SheetNames[0], first_worksheet = workbook.Sheets[first_sheet_name];
var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1});
alert(data);
var outfmts = [
["xlsb", "testw.xlsb"],
["biff8", "testw.xls"],
["xlml", "testw.xml"],
["fods", "testw.fods"],
["csv", "testw.csv"],
["txt", "testw.txt"],
["slk", "testw.slk"],
["eth", "testw.eth"],
["htm", "testw.htm"],
["dif", "testw.dif"],
["ods", "testw.ods"],
["xlsx", "testw.xlsx"]
];
for(var i = 0; i < outfmts.length; ++i) {
alert(outfmts[i][0]);
var fname = basePath + "/" + outfmts[i][1];
/* Write file to disk */
XLSX.writeFile(workbook, fname);
/* Read new file */
var wb = XLSX.readFile(fname, {cellDates:true});
/* Display first worksheet */
var f_sheet_name = wb.SheetNames[0], f_worksheet = wb.Sheets[f_sheet_name];
var data = XLSX.utils.sheet_to_json(f_worksheet, {header:1, cellDates:true});
alert(data);
}

@ -1 +0,0 @@
#target estoolkit

@ -1,45 +0,0 @@
#target estoolkit
var thisFile = new File($.fileName);
var basePath = thisFile.path;
#include "xlsx.extendscript.js";
var filename = "/sheetjs.xlsx";
/* Read file from disk */
var workbook = XLSX.readFile(basePath + filename, {cellDates:true});
/* Display first worksheet */
var first_sheet_name = workbook.SheetNames[0], first_worksheet = workbook.Sheets[first_sheet_name];
var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1});
alert(data);
var outfmts = [
["xlsb", "testw.xlsb"],
["biff8", "testw.xls"],
["xlml", "testw.xml"],
["fods", "testw.fods"],
["csv", "testw.csv"],
["txt", "testw.txt"],
["slk", "testw.slk"],
["eth", "testw.eth"],
["htm", "testw.htm"],
["dif", "testw.dif"],
["ods", "testw.ods"],
["xlsx", "testw.xlsx"]
];
for(var i = 0; i < outfmts.length; ++i) {
alert(outfmts[i][0]);
var fname = basePath + "/" + outfmts[i][1];
/* Write file to disk */
XLSX.writeFile(workbook, fname);
/* Read new file */
var wb = XLSX.readFile(fname, {cellDates:true});
/* Display first worksheet */
var f_sheet_name = wb.SheetNames[0], f_worksheet = wb.Sheets[f_sheet_name];
var data = XLSX.utils.sheet_to_json(f_worksheet, {header:1, cellDates:true});
alert(data);
}

@ -1 +0,0 @@
#target illustrator

@ -1,45 +0,0 @@
#target illustrator
var thisFile = new File($.fileName);
var basePath = thisFile.path;
#include "xlsx.extendscript.js";
var filename = "/sheetjs.xlsx";
/* Read file from disk */
var workbook = XLSX.readFile(basePath + filename, {cellDates:true});
/* Display first worksheet */
var first_sheet_name = workbook.SheetNames[0], first_worksheet = workbook.Sheets[first_sheet_name];
var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1});
alert(data);
var outfmts = [
["xlsb", "testw.xlsb"],
["biff8", "testw.xls"],
["xlml", "testw.xml"],
["fods", "testw.fods"],
["csv", "testw.csv"],
["txt", "testw.txt"],
["slk", "testw.slk"],
["eth", "testw.eth"],
["htm", "testw.htm"],
["dif", "testw.dif"],
["ods", "testw.ods"],
["xlsx", "testw.xlsx"]
];
for(var i = 0; i < outfmts.length; ++i) {
alert(outfmts[i][0]);
var fname = basePath + "/" + outfmts[i][1];
/* Write file to disk */
XLSX.writeFile(workbook, fname);
/* Read new file */
var wb = XLSX.readFile(fname, {cellDates:true});
/* Display first worksheet */
var f_sheet_name = wb.SheetNames[0], f_worksheet = wb.Sheets[f_sheet_name];
var data = XLSX.utils.sheet_to_json(f_worksheet, {header:1, cellDates:true});
alert(data);
}

@ -1 +0,0 @@
#target indesign

@ -1,45 +0,0 @@
#target indesign
var thisFile = new File($.fileName);
var basePath = thisFile.path;
#include "xlsx.extendscript.js";
var filename = "/sheetjs.xlsx";
/* Read file from disk */
var workbook = XLSX.readFile(basePath + filename, {cellDates:true});
/* Display first worksheet */
var first_sheet_name = workbook.SheetNames[0], first_worksheet = workbook.Sheets[first_sheet_name];
var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1});
alert(data);
var outfmts = [
["xlsb", "testw.xlsb"],
["biff8", "testw.xls"],
["xlml", "testw.xml"],
["fods", "testw.fods"],
["csv", "testw.csv"],
["txt", "testw.txt"],
["slk", "testw.slk"],
["eth", "testw.eth"],
["htm", "testw.htm"],
["dif", "testw.dif"],
["ods", "testw.ods"],
["xlsx", "testw.xlsx"]
];
for(var i = 0; i < outfmts.length; ++i) {
alert(outfmts[i][0]);
var fname = basePath + "/" + outfmts[i][1];
/* Write file to disk */
XLSX.writeFile(workbook, fname);
/* Read new file */
var wb = XLSX.readFile(fname, {cellDates:true});
/* Display first worksheet */
var f_sheet_name = wb.SheetNames[0], f_worksheet = wb.Sheets[f_sheet_name];
var data = XLSX.utils.sheet_to_json(f_worksheet, {header:1, cellDates:true});
alert(data);
}

@ -1 +0,0 @@
#target photoshop

@ -1,45 +0,0 @@
#target photoshop
var thisFile = new File($.fileName);
var basePath = thisFile.path;
#include "xlsx.extendscript.js";
var filename = "/sheetjs.xlsx";
/* Read file from disk */
var workbook = XLSX.readFile(basePath + filename, {cellDates:true});
/* Display first worksheet */
var first_sheet_name = workbook.SheetNames[0], first_worksheet = workbook.Sheets[first_sheet_name];
var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1});
alert(data);
var outfmts = [
["xlsb", "testw.xlsb"],
["biff8", "testw.xls"],
["xlml", "testw.xml"],
["fods", "testw.fods"],
["csv", "testw.csv"],
["txt", "testw.txt"],
["slk", "testw.slk"],
["eth", "testw.eth"],
["htm", "testw.htm"],
["dif", "testw.dif"],
["ods", "testw.ods"],
["xlsx", "testw.xlsx"]
];
for(var i = 0; i < outfmts.length; ++i) {
alert(outfmts[i][0]);
var fname = basePath + "/" + outfmts[i][1];
/* Write file to disk */
XLSX.writeFile(workbook, fname);
/* Read new file */
var wb = XLSX.readFile(fname, {cellDates:true});
/* Display first worksheet */
var f_sheet_name = wb.SheetNames[0], f_worksheet = wb.Sheets[f_sheet_name];
var data = XLSX.utils.sheet_to_json(f_worksheet, {header:1, cellDates:true});
alert(data);
}

Binary file not shown.

@ -1,44 +0,0 @@
var thisFile = new File($.fileName);
var basePath = thisFile.path;
#include "xlsx.extendscript.js";
var filename = "/sheetjs.xlsx";
/* Read file from disk */
var workbook = XLSX.readFile(basePath + filename, {cellDates:true});
/* Display first worksheet */
var first_sheet_name = workbook.SheetNames[0], first_worksheet = workbook.Sheets[first_sheet_name];
var data = XLSX.utils.sheet_to_json(first_worksheet, {header:1});
alert(data);
var outfmts = [
["xlsb", "testw.xlsb"],
["biff8", "testw.xls"],
["xlml", "testw.xml"],
["fods", "testw.fods"],
["csv", "testw.csv"],
["txt", "testw.txt"],
["slk", "testw.slk"],
["eth", "testw.eth"],
["htm", "testw.htm"],
["dif", "testw.dif"],
["ods", "testw.ods"],
["xlsx", "testw.xlsx"]
];
for(var i = 0; i < outfmts.length; ++i) {
alert(outfmts[i][0]);
var fname = basePath + "/" + outfmts[i][1];
/* Write file to disk */
XLSX.writeFile(workbook, fname);
/* Read new file */
var wb = XLSX.readFile(fname, {cellDates:true});
/* Display first worksheet */
var f_sheet_name = wb.SheetNames[0], f_worksheet = wb.Sheets[f_sheet_name];
var data = XLSX.utils.sheet_to_json(f_worksheet, {header:1, cellDates:true});
alert(data);
}

38
test.js

@ -1483,6 +1483,27 @@ describe('write features', function() {
assert.equal(X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet2"}), "5,6\n7,8");
assert.throws(function() { X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet3"}); });
});
it('should create/update autofilter defined name on write', function() {[
"xlsx", "xlsb", /* "xls", */ "xlml" /*, "ods" */
].forEach(function(fmt) {
var wb = X.utils.book_new();
var ws = X.utils.aoa_to_sheet([["A","B","C"],[1,2,3]]);
ws["!autofilter"] = { ref: ws["!ref"] };
X.utils.book_append_sheet(wb, ws, "Sheet1");
var wb2 = X.read(X.write(wb, {bookType:fmt, type:TYPE}), {type:TYPE});
var Name = void 0;
((wb2.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert(!!Name, "Could not find _xlnm._FilterDatabases name WR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$2");
X.utils.sheet_add_aoa(wb2.Sheets.Sheet1, [[4,5,6]], { origin: -1 });
wb2.Sheets.Sheet1["!autofilter"].ref = wb2.Sheets.Sheet1["!ref"];
var wb3 = X.read(X.write(wb2, {bookType:fmt, type:TYPE}), {type:TYPE});
Name = void 0;
((wb3.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert(!!Name, "Could not find _xlnm._FilterDatabases name WRWR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$3");
assert.equal(wb2.Workbook.Names.length, wb3.Workbook.Names.length);
}); });
});
function seq(end/*:number*/, start/*:?number*/)/*:Array<number>*/ {
@ -1742,6 +1763,23 @@ describe('roundtrip features', function() {
Object.keys(row).forEach(function(k) { assert.equal(row[k], o[i][k]); });
});
});
it('should preserve autofilter settings', function() {[
['xlsx', paths.afxlsx],
['xlsb', paths.afxlsb],
// TODO:
//['xls', paths.afxls],
['xlml', paths.afxml]
//['ods', paths.afods]
].forEach(function(w) {
var wb = X.read(fs.readFileSync(w[1]), {type:TYPE});
var wb2 = X.read(X.write(wb, {bookType:w[0], type: TYPE}), {type:TYPE});
assert(!wb2.Sheets[wb2.SheetNames[0]]['!autofilter']);
for(var i = 1; i < wb2.SheetNames.length; ++i) {
assert(wb2.Sheets[wb2.SheetNames[i]]['!autofilter']);
assert.equal(wb2.Sheets[wb2.SheetNames[i]]['!autofilter'].ref,"A1:E22");
}
}); });
});
//function password_file(x){return x.match(/^password.*\.xls$/); }

40
test.mjs generated

@ -1473,6 +1473,27 @@ describe('write features', function() {
assert.equal(X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet2"}), "5,6\n7,8");
assert.throws(function() { X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet3"}); });
});
it('should create/update autofilter defined name on write', function() {[
"xlsx", "xlsb", /* "xls", */ "xlml" /*, "ods" */
].forEach(function(fmt) {
var wb = X.utils.book_new();
var ws = X.utils.aoa_to_sheet([["A","B","C"],[1,2,3]]);
ws["!autofilter"] = { ref: ws["!ref"] };
X.utils.book_append_sheet(wb, ws, "Sheet1");
var wb2 = X.read(X.write(wb, {bookType:fmt, type:TYPE}), {type:TYPE});
var Name = void 0;
((wb2.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert.ok(!!Name, "Could not find _xlnm._FilterDatabases name WR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$2");
X.utils.sheet_add_aoa(wb2.Sheets.Sheet1, [[4,5,6]], { origin: -1 });
wb2.Sheets.Sheet1["!autofilter"].ref = wb2.Sheets.Sheet1["!ref"];
var wb3 = X.read(X.write(wb2, {bookType:fmt, type:TYPE}), {type:TYPE});
Name = void 0;
((wb3.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert.ok(!!Name, "Could not find _xlnm._FilterDatabases name WRWR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$3");
assert.equal(wb2.Workbook.Names.length, wb3.Workbook.Names.length);
}); });
});
function seq(end/*:number*/, start/*:?number*/)/*:Array<number>*/ {
@ -1703,7 +1724,7 @@ describe('roundtrip features', function() {
describe('should preserve cell comments', function() { [
['xlsx', paths.cstxlsx],
['xlsb', paths.cstxlsb],
//['xls', paths.cstxlsx],
//['xls', paths.cstxls],
['xlml', paths.cstxml]
//['ods', paths.cstods]
].forEach(function(w) {
@ -1729,6 +1750,23 @@ describe('roundtrip features', function() {
Object.keys(row).forEach(function(k) { assert.equal(row[k], o[i][k]); });
});
});
it('should preserve autofilter settings', function() {[
['xlsx', paths.afxlsx],
['xlsb', paths.afxlsb],
// TODO:
//['xls', paths.afxls],
['xlml', paths.afxml]
//['ods', paths.afods]
].forEach(function(w) {
var wb = X.read(fs.readFileSync(w[1]), {type:TYPE});
var wb2 = X.read(X.write(wb, {bookType:w[0], type: TYPE}), {type:TYPE});
assert.ok(!wb2.Sheets[wb2.SheetNames[0]]['!autofilter']);
for(var i = 1; i < wb2.SheetNames.length; ++i) {
assert.ok(wb2.Sheets[wb2.SheetNames[i]]['!autofilter']);
assert.equal(wb2.Sheets[wb2.SheetNames[i]]['!autofilter'].ref,"A1:E22");
}
}); });
});
//function password_file(x){return x.match(/^password.*\.xls$/); }

38
test.ts

@ -1433,6 +1433,27 @@ Deno.test('write features', async function(t) {
assert.equal(X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet2"}), "5,6\n7,8");
assert.throws(function() { X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet3"}); });
});
await t.step('should create/update autofilter defined name on write', async function() {([
"xlsx", "xlsb", /* "xls", */ "xlml" /*, "ods" */
] as Array<X.BookType>).forEach(function(fmt) {
var wb = X.utils.book_new();
var ws = X.utils.aoa_to_sheet([["A","B","C"],[1,2,3]]);
ws["!autofilter"] = { ref: (ws["!ref"] as string) };
X.utils.book_append_sheet(wb, ws, "Sheet1");
var wb2 = X.read(X.write(wb, {bookType:fmt, type:TYPE}), {type:TYPE});
var Name: X.DefinedName = (null as any);
((wb2.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert.assert(!!Name, "Could not find _xlnm._FilterDatabases name WR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$2");
X.utils.sheet_add_aoa(wb2.Sheets.Sheet1, [[4,5,6]], { origin: -1 });
(wb2.Sheets.Sheet1["!autofilter"] as any).ref = wb2.Sheets.Sheet1["!ref"];
var wb3 = X.read(X.write(wb2, {bookType:fmt, type:TYPE}), {type:TYPE});
Name = (null as any);
((wb3.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert.assert(!!Name, "Could not find _xlnm._FilterDatabases name WRWR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$3");
assert.equal(((wb2.Workbook as any).Names as any).length, ((wb3.Workbook as any).Names as any).length);
}); });
});
function seq(end: number, start?: number): Array<number> {
@ -1689,6 +1710,23 @@ Deno.test('roundtrip features', async function(t) {
Object.keys(row).forEach(function(k) { assert.equal(row[k], o[i][k]); });
});
});
await t.step('should preserve autofilter settings', async function() {[
['xlsx', paths.afxlsx],
['xlsb', paths.afxlsb],
// TODO:
//['xls', paths.afxls],
['xlml', paths.afxml]
//['ods', paths.afods]
].forEach(function(w) {
var wb = X.read(fs.readFileSync(w[1]), {type:TYPE});
var wb2 = X.read(X.write(wb, {bookType:w[0], type: TYPE}), {type:TYPE});
assert.assert(!wb2.Sheets[wb2.SheetNames[0]]['!autofilter']);
for(var i = 1; i < wb2.SheetNames.length; ++i) {
assert.assert(wb2.Sheets[wb2.SheetNames[i]]['!autofilter']);
assert.equal((wb2.Sheets[wb2.SheetNames[i]]['!autofilter'] as any).ref,"A1:E22");
}
}); });
});
//function password_file(x){return x.match(/^password.*\.xls$/); }

40
tests/core.js generated

@ -1483,6 +1483,27 @@ describe('write features', function() {
assert.equal(X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet2"}), "5,6\n7,8");
assert.throws(function() { X.write(wb, {type:"string", bookType:"csv", sheet:"Sheet3"}); });
});
it('should create/update autofilter defined name on write', function() {[
"xlsx", "xlsb", /* "xls", */ "xlml" /*, "ods" */
].forEach(function(fmt) {
var wb = X.utils.book_new();
var ws = X.utils.aoa_to_sheet([["A","B","C"],[1,2,3]]);
ws["!autofilter"] = { ref: ws["!ref"] };
X.utils.book_append_sheet(wb, ws, "Sheet1");
var wb2 = X.read(X.write(wb, {bookType:fmt, type:TYPE}), {type:TYPE});
var Name = void 0;
((wb2.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert(!!Name, "Could not find _xlnm._FilterDatabases name WR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$2");
X.utils.sheet_add_aoa(wb2.Sheets.Sheet1, [[4,5,6]], { origin: -1 });
wb2.Sheets.Sheet1["!autofilter"].ref = wb2.Sheets.Sheet1["!ref"];
var wb3 = X.read(X.write(wb2, {bookType:fmt, type:TYPE}), {type:TYPE});
Name = void 0;
((wb3.Workbook||{}).Names || []).forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == 0) Name = dn; });
assert(!!Name, "Could not find _xlnm._FilterDatabases name WRWR");
assert.equal(Name.Ref, "Sheet1!$A$1:$C$3");
assert.equal(wb2.Workbook.Names.length, wb3.Workbook.Names.length);
}); });
});
function seq(end/*:number*/, start/*:?number*/)/*:Array<number>*/ {
@ -1716,7 +1737,7 @@ describe('roundtrip features', function() {
describe('should preserve cell comments', function() { [
['xlsx', paths.cstxlsx],
['xlsb', paths.cstxlsb],
//['xls', paths.cstxlsx],
//['xls', paths.cstxls],
['xlml', paths.cstxml]
//['ods', paths.cstods]
].forEach(function(w) {
@ -1742,6 +1763,23 @@ describe('roundtrip features', function() {
Object.keys(row).forEach(function(k) { assert.equal(row[k], o[i][k]); });
});
});
it('should preserve autofilter settings', function() {[
['xlsx', paths.afxlsx],
['xlsb', paths.afxlsb],
// TODO:
//['xls', paths.afxls],
['xlml', paths.afxml]
//['ods', paths.afods]
].forEach(function(w) {
var wb = X.read(fs.readFileSync(w[1]), {type:TYPE});
var wb2 = X.read(X.write(wb, {bookType:w[0], type: TYPE}), {type:TYPE});
assert(!wb2.Sheets[wb2.SheetNames[0]]['!autofilter']);
for(var i = 1; i < wb2.SheetNames.length; ++i) {
assert(wb2.Sheets[wb2.SheetNames[i]]['!autofilter']);
assert.equal(wb2.Sheets[wb2.SheetNames[i]]['!autofilter'].ref,"A1:E22");
}
}); });
});
//function password_file(x){return x.match(/^password.*\.xls$/); }

@ -4259,6 +4259,17 @@ function encode_range(cs/*:CellAddrSpec|Range*/,ce/*:?CellAddrSpec*/)/*:string*/
/*:: if(typeof ce !== 'string') throw "unreachable"; */
return cs == ce ? cs : cs + ":" + ce;
}
function fix_range(a1/*:string*/)/*:string*/ {
var s = decode_range(a1);
return "$" + encode_col(s.s.c) + "$" + encode_row(s.s.r) + ":$" + encode_col(s.e.c) + "$" + encode_row(s.e.r);
}
// List of invalid characters needs to be tested further
function formula_quote_sheet_name(sname/*:string*/, opts)/*:string*/ {
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname.replace(/'/g, "''") + "'";
return sname;
}
function safe_decode_range(range/*:string*/)/*:Range*/ {
var o = {s:{c:0,r:0},e:{c:0,r:0}};
@ -4999,6 +5010,23 @@ var RBErr = {
"#WTF?": 0xFF
};
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
/* Parts enumerated in OPC spec, MS-XLSB and MS-XLSX */
/* 12.3 Part Summary <SpreadsheetML> */
/* 14.2 Part Summary <DrawingML> */
@ -7143,22 +7171,6 @@ function parse_ExternName(blob, length, opts) {
}
/* [MS-XLS] 2.4.150 TODO */
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
function parse_Lbl(blob, length, opts) {
var target = blob.l + length;
var flags = blob.read_shift(2);
@ -12417,8 +12429,8 @@ var a1_to_rc = /*#__PURE__*/(function(){
return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5) {
var c = decode_col($3) - ($2 ? 0 : base.c);
var r = decode_row($5) - ($4 ? 0 : base.r);
var R = (r == 0 ? "" : !$4 ? "[" + r + "]" : (r+1));
var C = (c == 0 ? "" : !$2 ? "[" + c + "]" : (c+1));
var R = $4 == "$" ? (r+1) : (r == 0 ? "" : "[" + r + "]");
var C = $2 == "$" ? (c+1) : (c == 0 ? "" : "[" + c + "]");
return $1 + "R" + R + "C" + C;
});
};
@ -13145,12 +13157,6 @@ function make_3d_range(start, end) {
return start + ":" + end;
}
// List of invalid characters needs to be tested further
function formula_quote_sheet_name(sname/*:string*/, opts)/*:string*/ {
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname + "'";
return sname;
}
function get_ixti_raw(supbooks, ixti/*:number*/, opts)/*:string*/ {
if(!supbooks) return "SH33TJSERR0";
if(opts.biff > 8 && (!supbooks.XTI || !supbooks.XTI[ixti])) return supbooks.SheetNames[ixti];
@ -15346,7 +15352,7 @@ function write_ws_xml_autofilter(data, ws, wb, idx)/*:string*/ {
var name = names[i];
if(name.Name != '_xlnm._FilterDatabase') continue;
if(name.Sheet != idx) continue;
name.Ref = "'" + wb.SheetNames[idx] + "'!" + ref; break;
name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
}
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref });
return writextag("autoFilter", null, {ref:ref});
@ -16711,9 +16717,9 @@ function write_AUTOFILTER(ba, ws, wb, idx) {
var name = names[i];
if(name.Name != '_xlnm._FilterDatabase') continue;
if(name.Sheet != idx) continue;
name.Ref = "'" + wb.SheetNames[idx] + "'!" + ref; break;
name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
}
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref });
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref) });
write_record(ba, 0x00A1 /* BrtBeginAFilter */, write_UncheckedRfX(safe_decode_range(ref)));
/* *FILTERCOLUMN */
@ -17092,6 +17098,17 @@ function check_wb(wb) {
var Sheets = (wb.Workbook && wb.Workbook.Sheets) || [];
check_wb_names(wb.SheetNames, Sheets, !!wb.vbaraw);
for(var i = 0; i < wb.SheetNames.length; ++i) check_ws(wb.Sheets[wb.SheetNames[i]], wb.SheetNames[i], i);
wb.SheetNames.forEach(function(n, i) {
var ws = wb.Sheets[n];
if(!ws || !ws["!autofilter"]) return;
var DN;
if(!wb.Workbook) wb.Workbook = {};
if(!wb.Workbook.Names) wb.Workbook.Names = [];
wb.Workbook.Names.forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == i) DN = dn; });
var nn = formula_quote_sheet_name(n) + "!" + fix_range(ws["!autofilter"].ref);
if(DN) DN.Ref = nn;
else wb.Workbook.Names.push({Name: "_xlnm._FilterDatabase", Sheet: i, Ref: nn});
});
/* TODO: validate workbook */
}
/* 18.2 Workbook */
@ -17911,6 +17928,10 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a
if(cell.StyleID !== undefined) cell.ixfe = cell.StyleID;
}
function xlml_prefix_dname(dname) {
return XLSLblBuiltIn.indexOf("_xlnm." + dname) > -1 ? "_xlnm." + dname : dname;
}
function xlml_clean_comment(comment/*:any*/) {
comment.t = comment.v || "";
comment.t = comment.t.replace(/\r\n/g,"\n").replace(/\r/g,"\n");
@ -18118,7 +18139,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ {
if(!Workbook.Names) Workbook.Names = [];
var _NamedRange = parsexmltag(Rn[0]);
var _DefinedName/*:DefinedName*/ = ({
Name: _NamedRange.Name,
Name: xlml_prefix_dname(_NamedRange.Name),
Ref: rc_to_a1(_NamedRange.RefersTo.slice(1), {r:0, c:0})
}/*:any*/);
if(Workbook.Sheets.length>0) _DefinedName.Sheet=Workbook.Sheets.length-1;
@ -18708,7 +18729,7 @@ function write_sty_xlml(wb, opts)/*:string*/ {
});
return writextag("Styles", styles.join(""));
}
function write_name_xlml(n) { return writextag("NamedRange", null, {"ss:Name": n.Name, "ss:RefersTo":"=" + a1_to_rc(n.Ref, {r:0,c:0})}); }
function write_name_xlml(n) { return writextag("NamedRange", null, {"ss:Name": n.Name.slice(0,6) == "_xlnm." ? n.Name.slice(6) : n.Name, "ss:RefersTo":"=" + a1_to_rc(n.Ref, {r:0,c:0})}); }
function write_names_xlml(wb/*::, opts*/)/*:string*/ {
if(!((wb||{}).Workbook||{}).Names) return "";
/*:: if(!wb || !wb.Workbook || !wb.Workbook.Names) throw new Error("unreachable"); */
@ -18961,6 +18982,8 @@ function write_ws_xlml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ {
/* WorksheetOptions */
o.push(write_ws_xlml_wsopts(ws, opts, idx, wb));
if(ws["!autofilter"]) o.push('<AutoFilter x:Range="' + a1_to_rc(fix_range(ws["!autofilter"].ref), {r:0,c:0}) + '" xmlns="urn:schemas-microsoft-com:office:excel"></AutoFilter>');
return o.join("");
}
function write_xlml(wb, opts)/*:string*/ {

81
xlsx.js generated

@ -4174,6 +4174,17 @@ if(typeof cs !== 'string') cs = encode_cell((cs));
if(typeof ce !== 'string') ce = encode_cell((ce));
return cs == ce ? cs : cs + ":" + ce;
}
function fix_range(a1) {
var s = decode_range(a1);
return "$" + encode_col(s.s.c) + "$" + encode_row(s.s.r) + ":$" + encode_col(s.e.c) + "$" + encode_row(s.e.r);
}
// List of invalid characters needs to be tested further
function formula_quote_sheet_name(sname, opts) {
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname.replace(/'/g, "''") + "'";
return sname;
}
function safe_decode_range(range) {
var o = {s:{c:0,r:0},e:{c:0,r:0}};
@ -4914,6 +4925,23 @@ var RBErr = {
"#WTF?": 0xFF
};
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
/* Parts enumerated in OPC spec, MS-XLSB and MS-XLSX */
/* 12.3 Part Summary <SpreadsheetML> */
/* 14.2 Part Summary <DrawingML> */
@ -7054,22 +7082,6 @@ function parse_ExternName(blob, length, opts) {
}
/* [MS-XLS] 2.4.150 TODO */
var XLSLblBuiltIn = [
"_xlnm.Consolidate_Area",
"_xlnm.Auto_Open",
"_xlnm.Auto_Close",
"_xlnm.Extract",
"_xlnm.Database",
"_xlnm.Criteria",
"_xlnm.Print_Area",
"_xlnm.Print_Titles",
"_xlnm.Recorder",
"_xlnm.Data_Form",
"_xlnm.Auto_Activate",
"_xlnm.Auto_Deactivate",
"_xlnm.Sheet_Title",
"_xlnm._FilterDatabase"
];
function parse_Lbl(blob, length, opts) {
var target = blob.l + length;
var flags = blob.read_shift(2);
@ -12324,8 +12336,8 @@ var a1_to_rc = (function(){
return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5) {
var c = decode_col($3) - ($2 ? 0 : base.c);
var r = decode_row($5) - ($4 ? 0 : base.r);
var R = (r == 0 ? "" : !$4 ? "[" + r + "]" : (r+1));
var C = (c == 0 ? "" : !$2 ? "[" + c + "]" : (c+1));
var R = $4 == "$" ? (r+1) : (r == 0 ? "" : "[" + r + "]");
var C = $2 == "$" ? (c+1) : (c == 0 ? "" : "[" + c + "]");
return $1 + "R" + R + "C" + C;
});
};
@ -13051,12 +13063,6 @@ function make_3d_range(start, end) {
return start + ":" + end;
}
// List of invalid characters needs to be tested further
function formula_quote_sheet_name(sname, opts) {
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname + "'";
return sname;
}
function get_ixti_raw(supbooks, ixti, opts) {
if(!supbooks) return "SH33TJSERR0";
if(opts.biff > 8 && (!supbooks.XTI || !supbooks.XTI[ixti])) return supbooks.SheetNames[ixti];
@ -15252,7 +15258,7 @@ function write_ws_xml_autofilter(data, ws, wb, idx) {
var name = names[i];
if(name.Name != '_xlnm._FilterDatabase') continue;
if(name.Sheet != idx) continue;
name.Ref = "'" + wb.SheetNames[idx] + "'!" + ref; break;
name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
}
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref });
return writextag("autoFilter", null, {ref:ref});
@ -16616,9 +16622,9 @@ function write_AUTOFILTER(ba, ws, wb, idx) {
var name = names[i];
if(name.Name != '_xlnm._FilterDatabase') continue;
if(name.Sheet != idx) continue;
name.Ref = "'" + wb.SheetNames[idx] + "'!" + ref; break;
name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
}
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref });
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref) });
write_record(ba, 0x00A1 /* BrtBeginAFilter */, write_UncheckedRfX(safe_decode_range(ref)));
/* *FILTERCOLUMN */
@ -16997,6 +17003,17 @@ function check_wb(wb) {
var Sheets = (wb.Workbook && wb.Workbook.Sheets) || [];
check_wb_names(wb.SheetNames, Sheets, !!wb.vbaraw);
for(var i = 0; i < wb.SheetNames.length; ++i) check_ws(wb.Sheets[wb.SheetNames[i]], wb.SheetNames[i], i);
wb.SheetNames.forEach(function(n, i) {
var ws = wb.Sheets[n];
if(!ws || !ws["!autofilter"]) return;
var DN;
if(!wb.Workbook) wb.Workbook = {};
if(!wb.Workbook.Names) wb.Workbook.Names = [];