diff --git a/lib/xlsx/xform/table/custom-filter-xform.js b/lib/xlsx/xform/table/custom-filter-xform.js
new file mode 100644
index 000000000..d0e9d272f
--- /dev/null
+++ b/lib/xlsx/xform/table/custom-filter-xform.js
@@ -0,0 +1,33 @@
+const BaseXform = require('../base-xform');
+
+class CustomFilterXform extends BaseXform {
+ get tag() {
+ return 'customFilter';
+ }
+
+ render(xmlStream, model) {
+ xmlStream.leafNode(this.tag, {
+ val: model.val,
+ operator: model.operator,
+ });
+ }
+
+ parseOpen(node) {
+ if (node.name === this.tag) {
+ this.model = {
+ val: node.attributes.val,
+ operator: node.attributes.operator,
+ };
+ return true;
+ }
+ return false;
+ }
+
+ parseText() {}
+
+ parseClose() {
+ return false;
+ }
+}
+
+module.exports = CustomFilterXform;
diff --git a/lib/xlsx/xform/table/filter-column-xform.js b/lib/xlsx/xform/table/filter-column-xform.js
index 20fd24628..c900996d4 100644
--- a/lib/xlsx/xform/table/filter-column-xform.js
+++ b/lib/xlsx/xform/table/filter-column-xform.js
@@ -1,6 +1,29 @@
const BaseXform = require('../base-xform');
+const ListXform = require('../list-xform');
+
+const CustomFilterXform = require('./custom-filter-xform');
+const FilterXform = require('./filter-xform');
class FilterColumnXform extends BaseXform {
+ constructor() {
+ super();
+
+ this.map = {
+ customFilters: new ListXform({
+ tag: 'customFilters',
+ count: false,
+ empty: true,
+ childXform: new CustomFilterXform(),
+ }),
+ filters: new ListXform({
+ tag: 'filters',
+ count: false,
+ empty: true,
+ childXform: new FilterXform(),
+ }),
+ };
+ }
+
get tag() {
return 'filterColumn';
}
@@ -10,6 +33,17 @@ class FilterColumnXform extends BaseXform {
}
render(xmlStream, model) {
+ if (model.customFilters) {
+ xmlStream.openNode(this.tag, {
+ colId: model.colId,
+ hiddenButton: model.filterButton ? '0' : '1',
+ });
+
+ this.map.customFilters.render(xmlStream, model.customFilters);
+
+ xmlStream.closeNode();
+ return true;
+ }
xmlStream.leafNode(this.tag, {
colId: model.colId,
hiddenButton: model.filterButton ? '0' : '1',
@@ -18,20 +52,44 @@ class FilterColumnXform extends BaseXform {
}
parseOpen(node) {
- if (node.name === this.tag) {
- const {attributes} = node;
- this.model = {
- filterButton: attributes.hiddenButton === '0',
- };
+ if (this.parser) {
+ this.parser.parseOpen(node);
return true;
}
- return false;
+ const {attributes} = node;
+ switch (node.name) {
+ case this.tag:
+ this.model = {
+ filterButton: attributes.hiddenButton === '0',
+ };
+ return true;
+ default:
+ this.parser = this.map[node.name];
+ if (this.parser) {
+ this.parseOpen(node);
+ return true;
+ }
+ throw new Error(`Unexpected xml node in parseOpen: ${JSON.stringify(node)}`);
+ }
}
parseText() {}
- parseClose() {
- return false;
+ parseClose(name) {
+ if (this.parser) {
+ if (!this.parser.parseClose(name)) {
+ this.parser = undefined;
+ }
+ return true;
+ }
+ switch (name) {
+ case this.tag:
+ this.model.customFilters = this.map.customFilters.model;
+ return false;
+ default:
+ // could be some unrecognised tags
+ return true;
+ }
}
}
diff --git a/lib/xlsx/xform/table/filter-xform.js b/lib/xlsx/xform/table/filter-xform.js
new file mode 100644
index 000000000..91a620a02
--- /dev/null
+++ b/lib/xlsx/xform/table/filter-xform.js
@@ -0,0 +1,31 @@
+const BaseXform = require('../base-xform');
+
+class FilterXform extends BaseXform {
+ get tag() {
+ return 'filter';
+ }
+
+ render(xmlStream, model) {
+ xmlStream.leafNode(this.tag, {
+ val: model.val,
+ });
+ }
+
+ parseOpen(node) {
+ if (node.name === this.tag) {
+ this.model = {
+ val: node.attributes.val,
+ };
+ return true;
+ }
+ return false;
+ }
+
+ parseText() {}
+
+ parseClose() {
+ return false;
+ }
+}
+
+module.exports = FilterXform;
diff --git a/spec/integration/data/test-issue-1669.xlsx b/spec/integration/data/test-issue-1669.xlsx
new file mode 100644
index 000000000..cc9dab8f1
Binary files /dev/null and b/spec/integration/data/test-issue-1669.xlsx differ
diff --git a/spec/integration/issues/issue-1669-optional-custom-autofilter-on-table.spec.js b/spec/integration/issues/issue-1669-optional-custom-autofilter-on-table.spec.js
new file mode 100644
index 000000000..3a030c090
--- /dev/null
+++ b/spec/integration/issues/issue-1669-optional-custom-autofilter-on-table.spec.js
@@ -0,0 +1,8 @@
+const ExcelJS = verquire('exceljs');
+
+describe('github issues', () => {
+ it('issue 1669 - optional autofilter and custom autofilter on tables', () => {
+ const wb = new ExcelJS.Workbook();
+ return wb.xlsx.readFile('./spec/integration/data/test-issue-1669.xlsx');
+ }).timeout(6000);
+});
diff --git a/spec/unit/xlsx/xform/table/custom-filter-xform.spec.js b/spec/unit/xlsx/xform/table/custom-filter-xform.spec.js
new file mode 100644
index 000000000..971ac53b7
--- /dev/null
+++ b/spec/unit/xlsx/xform/table/custom-filter-xform.spec.js
@@ -0,0 +1,30 @@
+const testXformHelper = require('../test-xform-helper');
+
+const CustomFilterXform = verquire('xlsx/xform/table/custom-filter-xform');
+
+const expectations = [
+ {
+ title: 'custom filter',
+ create() {
+ return new CustomFilterXform();
+ },
+ preparedModel: {val: '*brandywine*'},
+ xml: '',
+ parsedModel: {val: '*brandywine*'},
+ tests: ['render', 'renderIn', 'parse'],
+ },
+ {
+ title: 'custom filter with operator',
+ create() {
+ return new CustomFilterXform();
+ },
+ preparedModel: {operator: 'notEqual', val: '4'},
+ xml: '',
+ parsedModel: {operator: 'notEqual', val: '4'},
+ tests: ['render', 'renderIn', 'parse'],
+ },
+];
+
+describe('CustomFilterXform', () => {
+ testXformHelper(expectations);
+});
diff --git a/spec/unit/xlsx/xform/table/filter-column-xform.spec.js b/spec/unit/xlsx/xform/table/filter-column-xform.spec.js
index 2d9900c17..c15f802fe 100644
--- a/spec/unit/xlsx/xform/table/filter-column-xform.spec.js
+++ b/spec/unit/xlsx/xform/table/filter-column-xform.spec.js
@@ -31,6 +31,25 @@ const expectations = [
tests: ['prepare', 'render', 'renderIn', 'parse'],
options: {index: 1},
},
+ {
+ title: 'with custom filter',
+ create() {
+ return new FilterColumnXform();
+ },
+ initialModel: {filterButton: false, customFilters: [{val: '*brandywine*'}]},
+ preparedModel: {
+ colId: '0',
+ filterButton: false,
+ customFilters: [{val: '*brandywine*'}],
+ },
+ xml:
+ '',
+ get parsedModel() {
+ return this.initialModel;
+ },
+ tests: ['prepare', 'render', 'renderIn', 'parse'],
+ options: {index: 0},
+ },
];
describe('FilterColumnXform', () => {