From f1509526349cb0f014086db44ca6a6e17ee28184 Mon Sep 17 00:00:00 2001 From: Andrii Krupskyi Date: Tue, 7 Sep 2021 11:04:30 +0300 Subject: [PATCH 01/41] fix: styles rendering in case when "numFmt" is present in conditional formatting rules (resolves #1814) (#1815) * fix: styles rendering in case when "numFmt" is present in conditional formatting rules (resolves #1814) * test: add integration test to cover #1814 * fix: lock jasmine version to prevent pipeline got stuck issue Co-authored-by: Andrii Krupskyi Co-authored-by: Siemienik Pawel --- gruntfile.js | 23 +++++++++----- lib/xlsx/xform/style/dxf-xform.js | 5 +-- lib/xlsx/xform/style/styles-xform.js | 16 +++++----- .../workbook-xlsx-writer.spec.js | 25 +++++++++++++++ spec/utils/data/conditional-formatting.json | 31 ++++++++++++++++++- 5 files changed, 81 insertions(+), 19 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index cc1574170..384b04500 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -27,14 +27,17 @@ module.exports = function(grunt) { browserify: { options: { transform: [ - ['babelify', { - // enable babel transpile for node_modules - global: true, - presets: ['@babel/preset-env'], - // core-js should not be transpiled - // See https://github.com/zloirock/core-js/issues/514 - ignore: [/node_modules[\\/]core-js/], - }], + [ + 'babelify', + { + // enable babel transpile for node_modules + global: true, + presets: ['@babel/preset-env'], + // core-js should not be transpiled + // See https://github.com/zloirock/core-js/issues/514 + ignore: [/node_modules[\\/]core-js/], + }, + ], ], browserifyOptions: { // enable source map for browserify @@ -119,6 +122,10 @@ module.exports = function(grunt) { }, jasmine: { + options: { + version: '3.8.0', + noSandbox: true, + }, dev: { src: ['./dist/exceljs.js'], options: { diff --git a/lib/xlsx/xform/style/dxf-xform.js b/lib/xlsx/xform/style/dxf-xform.js index ea22d365f..5cb2d78b9 100644 --- a/lib/xlsx/xform/style/dxf-xform.js +++ b/lib/xlsx/xform/style/dxf-xform.js @@ -39,8 +39,9 @@ class DxfXform extends BaseXform { if (model.font) { this.map.font.render(xmlStream, model.font); } - if (model.numFmt) { - this.map.numFmt.render(xmlStream, model.numFmt); + if (model.numFmt && model.numFmtId) { + const numFmtModel = {id: model.numFmtId, formatCode: model.numFmt}; + this.map.numFmt.render(xmlStream, numFmtModel); } if (model.fill) { this.map.fill.render(xmlStream, model.fill); diff --git a/lib/xlsx/xform/style/styles-xform.js b/lib/xlsx/xform/style/styles-xform.js index e1071c424..bf749e7bc 100644 --- a/lib/xlsx/xform/style/styles-xform.js +++ b/lib/xlsx/xform/style/styles-xform.js @@ -135,9 +135,7 @@ class StylesXform extends BaseXform { }); xmlStream.closeNode(); - this.map.cellStyleXfs.render(xmlStream, [ - {numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, xfId: 0}, - ]); + this.map.cellStyleXfs.render(xmlStream, [{numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, xfId: 0}]); xmlStream.openNode('cellXfs', {count: model.styles.length}); model.styles.forEach(styleXml => { @@ -150,9 +148,7 @@ class StylesXform extends BaseXform { this.map.fonts.render(xmlStream, model.fonts); this.map.fills.render(xmlStream, model.fills); this.map.borders.render(xmlStream, model.borders); - this.map.cellStyleXfs.render(xmlStream, [ - {numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, xfId: 0}, - ]); + this.map.cellStyleXfs.render(xmlStream, [{numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, xfId: 0}]); this.map.cellXfs.render(xmlStream, model.styles); } @@ -313,8 +309,7 @@ class StylesXform extends BaseXform { // ------------------------------------------------------- // number format if (style.numFmtId) { - const numFmt = - this.index.numFmt[style.numFmtId] || NumFmtXform.getDefaultFmtCode(style.numFmtId); + const numFmt = this.index.numFmt[style.numFmtId] || NumFmtXform.getDefaultFmtCode(style.numFmtId); if (numFmt) { model.numFmt = numFmt; } @@ -349,6 +344,11 @@ class StylesXform extends BaseXform { } addDxfStyle(style) { + if (style.numFmt) { + // register numFmtId to use it during dxf-xform rendering + style.numFmtId = this._addNumFmtStr(style.numFmt); + } + this.model.dxfs.push(style); return this.model.dxfs.length - 1; } diff --git a/spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js b/spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js index 6feb93aca..b74d8f496 100644 --- a/spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js +++ b/spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js @@ -588,5 +588,30 @@ describe('WorkbookWriter', () => { testUtils.checkTestBook(wb2, 'xlsx', ['conditionalFormatting']); }); }); + + it('with conditional formatting that contains numFmt (#1814)', async () => { + const sheet = 'conditionalFormatting'; + const options = {filename: TEST_XLSX_FILE_NAME, useStyles: true}; + + // generate file with conditional formatting that contains styles with numFmt + const wb1 = new ExcelJS.stream.xlsx.WorkbookWriter(options); + const ws1 = wb1.addWorksheet(sheet); + const cf1 = testUtils.conditionalFormatting.abbreviation; + ws1.addConditionalFormatting(cf1); + await wb1.commit(); + + // read generated file and extract saved conditional formatting rule + const wb2 = new ExcelJS.Workbook(); + await wb2.xlsx.readFile(TEST_XLSX_FILE_NAME); + const ws2 = wb2.getWorksheet(sheet); + const [cf2] = ws2.conditionalFormattings; + + // verify that rules from generated file contain styles with valid numFmt + cf2.rules.forEach(rule => { + expect(rule.style.numFmt).to.exist(); + expect(rule.style.numFmt.id).to.be.a('number'); + expect(rule.style.numFmt.formatCode).to.be.a('string'); + }); + }); }); }); diff --git a/spec/utils/data/conditional-formatting.json b/spec/utils/data/conditional-formatting.json index 7a58a3ee0..d80b53998 100644 --- a/spec/utils/data/conditional-formatting.json +++ b/spec/utils/data/conditional-formatting.json @@ -61,6 +61,35 @@ } ] }, + "abbreviation": { + "ref": "A:A", + "rules": [ + { + "type": "cellIs", + "operator": "between", + "formulae": [1000, 1000000], + "style": { "numFmt": "#,##0.000,\\K;-#,##0.000,\\K" } + }, + { + "type": "cellIs", + "operator": "between", + "formulae": [-1000, -1000000], + "style": { "numFmt": "#,##0.000,\\K;-#,##0.000,\\K" } + }, + { + "type": "cellIs", + "operator": "greaterThan", + "formulae": [1000000], + "style": { "numFmt": "#,##0.000,,\\M;-#,##0.000,,\\M" } + }, + { + "type": "cellIs", + "operator": "lessThan", + "formulae": [-1000000], + "style": { "numFmt": "#,##0.000,,\\M;-#,##0.000,,\\M" } + } + ] + }, "types": [ "expression", "cellIs", @@ -71,4 +100,4 @@ "containsText", "timePeriod" ] -} \ No newline at end of file +} From 33d833f76b7a183c73f308697498ea96452a2a4d Mon Sep 17 00:00:00 2001 From: Dmitry <4129726+drdmitry@users.noreply.github.com> Date: Tue, 7 Sep 2021 10:30:49 +0200 Subject: [PATCH 02/41] inlineStr cell type support #1575 (#1576) * inlineStr cell type support #1575 * Added integration test for #1575 Co-authored-by: Siemienik Pawel Co-authored-by: Andreas Lubbe --- lib/stream/xlsx/worksheet-reader.js | 7 +++++++ spec/integration/data/test-issue-1575.xlsx | Bin 0 -> 4776 bytes spec/integration/pr/test-pr-1576.spec.js | 23 +++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 spec/integration/data/test-issue-1575.xlsx create mode 100644 spec/integration/pr/test-pr-1576.spec.js diff --git a/lib/stream/xlsx/worksheet-reader.js b/lib/stream/xlsx/worksheet-reader.js index ade13f426..8cecd5c17 100644 --- a/lib/stream/xlsx/worksheet-reader.js +++ b/lib/stream/xlsx/worksheet-reader.js @@ -214,6 +214,12 @@ class WorksheetReader extends EventEmitter { current = c.v = {text: ''}; } break; + case 'is': + case 't': + if (c) { + current = c.v = {text: ''}; + } + break; case 'mergeCell': break; default: @@ -307,6 +313,7 @@ class WorksheetReader extends EventEmitter { break; } + case 'inlineStr': case 'str': cell.value = utils.xmlDecode(c.v.text); break; diff --git a/spec/integration/data/test-issue-1575.xlsx b/spec/integration/data/test-issue-1575.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8a8caa24aa96be0b811238ba4e3e3417100b0aa0 GIT binary patch literal 4776 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 a2s-sQ6kx1RK}qyZLwL0i=<@Yp%>M%wEuv)r literal 0 HcmV?d00001 diff --git a/spec/integration/pr/test-pr-1576.spec.js b/spec/integration/pr/test-pr-1576.spec.js new file mode 100644 index 000000000..bd575be4c --- /dev/null +++ b/spec/integration/pr/test-pr-1576.spec.js @@ -0,0 +1,23 @@ +const ExcelJS = verquire('exceljs'); + +describe('github issues', () => { + describe('pull request 1576 - inlineStr cell type support', () => { + it('Reading test-issue-1575.xlsx', () => { + const wb = new ExcelJS.Workbook(); + return wb.xlsx + .readFile('./spec/integration/data/test-issue-1575.xlsx') + .then(() => { + const ws = wb.getWorksheet('Sheet1'); + expect(ws.getCell('A1').value).to.equal('A'); + expect(ws.getCell('B1').value).to.equal('B'); + expect(ws.getCell('C1').value).to.equal('C'); + expect(ws.getCell('A2').value).to.equal('1.0'); + expect(ws.getCell('B2').value).to.equal('2.0'); + expect(ws.getCell('C2').value).to.equal('3.0'); + expect(ws.getCell('A3').value).to.equal('4.0'); + expect(ws.getCell('B3').value).to.equal('5.0'); + expect(ws.getCell('C3').value).to.equal('6.0'); + }); + }); + }); +}); From fb838ea7a03f5de493691eb0e32836534aee59df Mon Sep 17 00:00:00 2001 From: bno1 Date: Fri, 8 Oct 2021 04:01:42 +0300 Subject: [PATCH 03/41] Fix parsing of boolean attributes (#1849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexandru Șorodoc --- lib/utils/utils.js | 4 ++++ lib/xlsx/xform/sheet/col-xform.js | 12 ++++-------- lib/xlsx/xform/sheet/data-validations-xform.js | 12 ++---------- lib/xlsx/xform/sheet/row-xform.js | 12 ++++-------- lib/xlsx/xform/style/alignment-xform.js | 4 ++-- lib/xlsx/xform/style/border-xform.js | 5 +++-- 6 files changed, 19 insertions(+), 30 deletions(-) diff --git a/lib/utils/utils.js b/lib/utils/utils.js index a85af676d..84cd212c2 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -163,6 +163,10 @@ const utils = { toIsoDateString(dt) { return dt.toIsoString().subsstr(0, 10); }, + + parseBoolean(value) { + return value === true || value === 'true' || value === 1 || value === '1'; + }, }; module.exports = utils; diff --git a/lib/xlsx/xform/sheet/col-xform.js b/lib/xlsx/xform/sheet/col-xform.js index 93003cbdf..0d47b42c5 100644 --- a/lib/xlsx/xform/sheet/col-xform.js +++ b/lib/xlsx/xform/sheet/col-xform.js @@ -1,3 +1,4 @@ +const utils = require('../../../utils/utils'); const BaseXform = require('../base-xform'); class ColXform extends BaseXform { @@ -51,21 +52,16 @@ class ColXform extends BaseXform { if (node.attributes.style) { model.styleId = parseInt(node.attributes.style, 10); } - if ( - node.attributes.hidden === true || - node.attributes.hidden === 'true' || - node.attributes.hidden === 1 || - node.attributes.hidden === '1' - ) { + if (utils.parseBoolean(node.attributes.hidden)) { model.hidden = true; } - if (node.attributes.bestFit) { + if (utils.parseBoolean(node.attributes.bestFit)) { model.bestFit = true; } if (node.attributes.outlineLevel) { model.outlineLevel = parseInt(node.attributes.outlineLevel, 10); } - if (node.attributes.collapsed) { + if (utils.parseBoolean(node.attributes.collapsed)) { model.collapsed = true; } return true; diff --git a/lib/xlsx/xform/sheet/data-validations-xform.js b/lib/xlsx/xform/sheet/data-validations-xform.js index 5f20f33e3..58d6f098a 100644 --- a/lib/xlsx/xform/sheet/data-validations-xform.js +++ b/lib/xlsx/xform/sheet/data-validations-xform.js @@ -12,19 +12,11 @@ function assign(definedName, attributes, name, defaultValue) { definedName[name] = defaultValue; } } -function parseBool(value) { - switch (value) { - case '1': - case 'true': - return true; - default: - return false; - } -} + function assignBool(definedName, attributes, name, defaultValue) { const value = attributes[name]; if (value !== undefined) { - definedName[name] = parseBool(value); + definedName[name] = utils.parseBoolean(value); } else if (defaultValue !== undefined) { definedName[name] = defaultValue; } diff --git a/lib/xlsx/xform/sheet/row-xform.js b/lib/xlsx/xform/sheet/row-xform.js index 4ccb73fee..890a0fc8a 100644 --- a/lib/xlsx/xform/sheet/row-xform.js +++ b/lib/xlsx/xform/sheet/row-xform.js @@ -1,4 +1,5 @@ const BaseXform = require('../base-xform'); +const utils = require('../../../utils/utils'); const CellXform = require('./cell-xform'); @@ -79,15 +80,10 @@ class RowXform extends BaseXform { if (node.attributes.s) { model.styleId = parseInt(node.attributes.s, 10); } - if ( - node.attributes.hidden === true || - node.attributes.hidden === 'true' || - node.attributes.hidden === 1 || - node.attributes.hidden === '1' - ) { + if (utils.parseBoolean(node.attributes.hidden)) { model.hidden = true; } - if (node.attributes.bestFit) { + if (utils.parseBoolean(node.attributes.bestFit)) { model.bestFit = true; } if (node.attributes.ht) { @@ -96,7 +92,7 @@ class RowXform extends BaseXform { if (node.attributes.outlineLevel) { model.outlineLevel = parseInt(node.attributes.outlineLevel, 10); } - if (node.attributes.collapsed) { + if (utils.parseBoolean(node.attributes.collapsed)) { model.collapsed = true; } return true; diff --git a/lib/xlsx/xform/style/alignment-xform.js b/lib/xlsx/xform/style/alignment-xform.js index 6cd7eb11a..75391be98 100644 --- a/lib/xlsx/xform/style/alignment-xform.js +++ b/lib/xlsx/xform/style/alignment-xform.js @@ -145,8 +145,8 @@ class AlignmentXform extends BaseXform { 'vertical', node.attributes.vertical === 'center' ? 'middle' : node.attributes.vertical ); - add(node.attributes.wrapText, 'wrapText', !!node.attributes.wrapText); - add(node.attributes.shrinkToFit, 'shrinkToFit', !!node.attributes.shrinkToFit); + add(node.attributes.wrapText, 'wrapText', utils.parseBoolean(node.attributes.wrapText)); + add(node.attributes.shrinkToFit, 'shrinkToFit', utils.parseBoolean(node.attributes.shrinkToFit)); add(node.attributes.indent, 'indent', parseInt(node.attributes.indent, 10)); add( node.attributes.textRotation, diff --git a/lib/xlsx/xform/style/border-xform.js b/lib/xlsx/xform/style/border-xform.js index 3066c8188..212484bd6 100644 --- a/lib/xlsx/xform/style/border-xform.js +++ b/lib/xlsx/xform/style/border-xform.js @@ -1,5 +1,6 @@ /* eslint-disable max-classes-per-file */ const BaseXform = require('../base-xform'); +const utils = require('../../../utils/utils'); const ColorXform = require('./color-xform'); @@ -156,8 +157,8 @@ class BorderXform extends BaseXform { switch (node.name) { case 'border': this.reset(); - this.diagonalUp = !!node.attributes.diagonalUp; - this.diagonalDown = !!node.attributes.diagonalDown; + this.diagonalUp = utils.parseBoolean(node.attributes.diagonalUp); + this.diagonalDown = utils.parseBoolean(node.attributes.diagonalDown); return true; default: this.parser = this.map[node.name]; From b19b4c0a4201a71acb93648824916ab879cf080b Mon Sep 17 00:00:00 2001 From: Todd Hambley Date: Sat, 9 Oct 2021 05:38:50 -0400 Subject: [PATCH 04/41] add optional custom auto-filter to table (#1670) * add optional custom auto-filter to table * filters may be customFilters or just filters Co-authored-by: Todd Hambley Co-authored-by: Siemienik Pawel --- lib/xlsx/xform/table/custom-filter-xform.js | 33 ++++++++ lib/xlsx/xform/table/filter-column-xform.js | 74 ++++++++++++++++-- lib/xlsx/xform/table/filter-xform.js | 31 ++++++++ spec/integration/data/test-issue-1669.xlsx | Bin 0 -> 11345 bytes ...ptional-custom-autofilter-on-table.spec.js | 8 ++ .../xform/table/custom-filter-xform.spec.js | 30 +++++++ .../xform/table/filter-column-xform.spec.js | 19 +++++ 7 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 lib/xlsx/xform/table/custom-filter-xform.js create mode 100644 lib/xlsx/xform/table/filter-xform.js create mode 100644 spec/integration/data/test-issue-1669.xlsx create mode 100644 spec/integration/issues/issue-1669-optional-custom-autofilter-on-table.spec.js create mode 100644 spec/unit/xlsx/xform/table/custom-filter-xform.spec.js 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 0000000000000000000000000000000000000000..cc9dab8f15c8504047cd7d39916bfe5b9c92c445 GIT binary patch literal 11345 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 F{{x*wmF55d literal 0 HcmV?d00001 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', () => { From da5e743126753e5ade98ac33869b9884c546f248 Mon Sep 17 00:00:00 2001 From: ikzhr Date: Sat, 9 Oct 2021 19:01:52 +0900 Subject: [PATCH 05/41] Deep copy inherited style (#1850) * Deep copy style when a row is inserted with inherited style (#1813) * Add tests for row's style * Move setIfExists * Fix equal function --- lib/doc/worksheet.js | 5 ++-- lib/utils/copy-style.js | 43 ++++++++++++++++++++++++++++++ spec/integration/worksheet.spec.js | 32 ++++++++++++++++++++++ spec/unit/utils/copy-style.spec.js | 39 +++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 lib/utils/copy-style.js create mode 100644 spec/unit/utils/copy-style.spec.js diff --git a/lib/doc/worksheet.js b/lib/doc/worksheet.js index ac2f4613b..158391d31 100644 --- a/lib/doc/worksheet.js +++ b/lib/doc/worksheet.js @@ -9,6 +9,7 @@ const Image = require('./image'); const Table = require('./table'); const DataValidations = require('./data-validations'); const Encryptor = require('../utils/encryptor'); +const {copyStyle} = require('../utils/copy-style'); // Worksheet requirements // Operate as sheet inside workbook or standalone @@ -406,10 +407,10 @@ class Worksheet { _copyStyle(src, dest, styleEmpty = false) { const rSrc = this.getRow(src); const rDst = this.getRow(dest); - rDst.style = Object.freeze({...rSrc.style}); + rDst.style = copyStyle(rSrc.style); // eslint-disable-next-line no-loop-func rSrc.eachCell({includeEmpty: styleEmpty}, (cell, colNumber) => { - rDst.getCell(colNumber).style = Object.freeze({...cell.style}); + rDst.getCell(colNumber).style = copyStyle(cell.style); }); rDst.height = rSrc.height; } diff --git a/lib/utils/copy-style.js b/lib/utils/copy-style.js new file mode 100644 index 000000000..532bf5701 --- /dev/null +++ b/lib/utils/copy-style.js @@ -0,0 +1,43 @@ +const oneDepthCopy = (obj, nestKeys) => ({ + ...obj, + ...nestKeys.reduce((memo, key) => { + if (obj[key]) memo[key] = {...obj[key]}; + return memo; + }, {}), +}); + +const setIfExists = (src, dst, key, nestKeys = []) => { + if (src[key]) dst[key] = oneDepthCopy(src[key], nestKeys); +}; + +const isEmptyObj = obj => Object.keys(obj).length === 0; + +const copyStyle = style => { + if (!style) return style; + if (isEmptyObj(style)) return {}; + + const copied = {...style}; + + setIfExists(style, copied, 'font', ['color']); + setIfExists(style, copied, 'alignment'); + setIfExists(style, copied, 'protection'); + if (style.border) { + setIfExists(style, copied, 'border'); + setIfExists(style.border, copied.border, 'top', ['color']); + setIfExists(style.border, copied.border, 'left', ['color']); + setIfExists(style.border, copied.border, 'bottom', ['color']); + setIfExists(style.border, copied.border, 'right', ['color']); + setIfExists(style.border, copied.border, 'diagonal', ['color']); + } + + if (style.fill) { + setIfExists(style, copied, 'fill', ['fgColor', 'bgColor', 'center']); + if (style.fill.stops) { + copied.fill.stops = style.fill.stops.map(s => oneDepthCopy(s, ['color'])); + } + } + + return copied; +}; + +exports.copyStyle = copyStyle; diff --git a/spec/integration/worksheet.spec.js b/spec/integration/worksheet.spec.js index 705746bed..81868677c 100644 --- a/spec/integration/worksheet.spec.js +++ b/spec/integration/worksheet.spec.js @@ -598,6 +598,38 @@ describe('Worksheet', () => { } }); + it('should style of the inserted row with inherited style be mutable', () => { + const wb = new ExcelJS.Workbook(); + const ws = wb.addWorksheet('blort'); + + const dateValue1 = new Date(1970, 1, 1); + const dateValue2 = new Date(1965, 1, 7); + + ws.addRow([1, 'John Doe', dateValue1]); + ws.getRow(1).font = testutils.styles.fonts.comicSansUdB16; + + ws.insertRow(2, [3, 'Jane Doe', dateValue2], 'i'); + ws.insertRow(2, [2, 'Jane Doe', dateValue2], 'o'); + + ws.getRow(2).font = testutils.styles.fonts.broadwayRedOutline20; + ws.getRow(3).font = testutils.styles.fonts.broadwayRedOutline20; + ws.getCell('A2').font = testutils.styles.fonts.arialBlackUI14; + ws.getCell('A3').font = testutils.styles.fonts.arialBlackUI14; + + expect(ws.getRow(2).font).not.deep.equal( + testutils.styles.fonts.comicSansUdB16 + ); + expect(ws.getRow(3).font).not.deep.equal( + testutils.styles.fonts.comicSansUdB16 + ); + expect(ws.getCell('A2').font).not.deep.equal( + testutils.styles.fonts.comicSansUdB16 + ); + expect(ws.getCell('A3').font).not.deep.equal( + testutils.styles.fonts.comicSansUdB16 + ); + }); + it('iterates over rows', () => { const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet('blort'); diff --git a/spec/unit/utils/copy-style.spec.js b/spec/unit/utils/copy-style.spec.js new file mode 100644 index 000000000..d54d74860 --- /dev/null +++ b/spec/unit/utils/copy-style.spec.js @@ -0,0 +1,39 @@ +const testUtils = require('../../utils/index'); + +const {copyStyle} = verquire('utils/copy-style'); + +const style1 = { + numFmt: testUtils.styles.numFmts.numFmt1, + font: testUtils.styles.fonts.broadwayRedOutline20, + alignment: testUtils.styles.namedAlignments.topLeft, + border: testUtils.styles.borders.thickRainbow, + fill: testUtils.styles.fills.redGreenDarkTrellis, +}; +const style2 = { + fill: testUtils.styles.fills.rgbPathGrad, +}; + +describe('copyStyle', () => { + it('should copy a style deeply', () => { + const copied = copyStyle(style1); + expect(copied).to.deep.equal(style1); + expect(copied.font).to.not.equal(style1.font); + expect(copied.alignment).to.not.equal(style1.alignment); + expect(copied.border).to.not.equal(style1.border); + expect(copied.fill).to.not.equal(style1.fill); + + expect(copyStyle({})).to.deep.equal({}); + }); + + it('should copy fill.stops deeply', () => { + const copied = copyStyle(style2); + expect(copied.fill.stops).to.deep.equal(style2.fill.stops); + expect(copied.fill.stops).to.not.equal(style2.fill.stops); + expect(copied.fill.stops[0]).to.not.equal(style2.fill.stops[0]); + }); + + it('should return the argument if a falsy value passed', () => { + expect(copyStyle(null)).to.equal(null); + expect(copyStyle(undefined)).to.equal(undefined); + }); +}); From 9bf832cb3c777ec26b591d43b113ae208822fb3a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 14 Oct 2021 22:04:12 +0200 Subject: [PATCH 06/41] Upgrade actions/cache and actions/setup-node (#1846) * https://github.com/actions/cache/releases * https://github.com/actions/setup-node/releases --- .github/workflows/exceljs.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/exceljs.yml b/.github/workflows/exceljs.yml index 876b57eae..0f263370b 100644 --- a/.github/workflows/exceljs.yml +++ b/.github/workflows/exceljs.yml @@ -26,13 +26,13 @@ jobs: if: runner.os == 'Windows' - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - name: Create the npm cache directory run: mkdir npm-cache && npm config set cache ./npm-cache --global - name: Cache node modules - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ./npm-cache key: v1-${{ runner.os }}-node-${{ matrix.node-version }}-npm-${{ hashFiles('**/package.json') }} @@ -49,13 +49,13 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: 12.x - name: Create the npm cache directory run: mkdir npm-cache && npm config set cache ./npm-cache --global - name: Cache node modules - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ./npm-cache key: v1-npm-${{ hashFiles('**/package.json') }} @@ -72,13 +72,13 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: 12.x - name: Create the npm cache directory run: mkdir npm-cache && npm config set cache ./npm-cache --global - name: Cache node modules - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ./npm-cache key: v1-npm-${{ hashFiles('**/package.json') }} From 695023fe6510e1f17d42ff7460fbe5b705e16560 Mon Sep 17 00:00:00 2001 From: bno1 Date: Sun, 17 Oct 2021 09:44:08 +0300 Subject: [PATCH 07/41] Check object keys in isEqual (#1831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Check object keys in isEqual * Add check for null in isEqual * Add isEqual tests Co-authored-by: Alexandru Șorodoc --- lib/utils/under-dash.js | 19 +++++++ spec/unit/utils/under-dash.spec.js | 79 ++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 spec/unit/utils/under-dash.spec.js diff --git a/lib/utils/under-dash.js b/lib/utils/under-dash.js index 65c931d22..b6f8a1312 100644 --- a/lib/utils/under-dash.js +++ b/lib/utils/under-dash.js @@ -55,6 +55,7 @@ const _ = { const bType = typeof b; const aArray = Array.isArray(a); const bArray = Array.isArray(b); + let keys; if (aType !== bType) { return false; @@ -73,6 +74,24 @@ const _ = { } return false; } + + if (a === null || b === null) { + return a === b; + } + + // Compare object keys and values + keys = Object.keys(a); + + if (Object.keys(b).length !== keys.length) { + return false; + } + + for (const key of keys) { + if (!b.hasOwnProperty(key)) { + return false; + } + } + return _.every(a, (aValue, key) => { const bValue = b[key]; return _.isEqual(aValue, bValue); diff --git a/spec/unit/utils/under-dash.spec.js b/spec/unit/utils/under-dash.spec.js new file mode 100644 index 000000000..32f47bf12 --- /dev/null +++ b/spec/unit/utils/under-dash.spec.js @@ -0,0 +1,79 @@ +const _ = verquire('utils/under-dash'); +const util = require('util'); + +describe('under-dash', () => { + describe('isEqual', () => { + const values = [ + 0, + 1, + true, + false, + 'string', + 'foobar', + 'other string', + [], + ['array'], + ['array', 'foobar'], + ['array2'], + ['array2', 'foobar'], + {}, + {object: 1}, + {object: 2}, + {object: 1, foobar: 'quux'}, + {object: 2, foobar: 'quux'}, + null, + undefined, + () => {}, + () => {}, + Symbol('foo'), + Symbol('foo'), + Symbol('bar'), + ]; + + function showVal(o) { + return util.inspect(o, {compact: true}); + } + + it('works on simple values', () => { + for (let i = 0; i < values.length; i++) { + for (let j = 0; j < values.length; j++) { + const a = values[i]; + const b = values[j]; + + const assertion = `${showVal(a)} ${i === j ? '==' : '!='} ${showVal( + b + )}`; + expect(_.isEqual(a, b)).to.equal(i === j, `expected ${assertion}`); + } + } + }); + + it('works on complex arrays', () => { + for (let i = 0; i < values.length; i++) { + for (let j = 0; j < values.length; j++) { + const a = [values[i]]; + const b = [values[j]]; + + const assertion = `${showVal(a)} ${i === j ? '==' : '!='} ${showVal( + b + )}`; + expect(_.isEqual(a, b)).to.equal(i === j, `expected ${assertion}`); + } + } + }); + + it('works on complex objects', () => { + for (let i = 0; i < values.length; i++) { + for (let j = 0; j < values.length; j++) { + const a = {key: values[i]}; + const b = {key: values[j]}; + + const assertion = `${showVal(a)} ${i === j ? '==' : '!='} ${showVal( + b + )}`; + expect(_.isEqual(a, b)).to.equal(i === j, `expected ${assertion}`); + } + } + }); + }); +}); From 1d5e5ac02d836f02f2d8b6282b3e1e327f0403b6 Mon Sep 17 00:00:00 2001 From: Siemienik Pawel Date: Mon, 1 Nov 2021 12:08:53 +0100 Subject: [PATCH 08/41] Add v17 to testing workflow (#1856) --- .github/workflows/exceljs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exceljs.yml b/.github/workflows/exceljs.yml index 0f263370b..338ca06a0 100644 --- a/.github/workflows/exceljs.yml +++ b/.github/workflows/exceljs.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: # https://github.com/actions/setup-node/issues/27 - node-version: [8.17.0, 10.x, 12.x, 14.x, 16.x] + node-version: [8.17.0, 10.x, 12.x, 14.x, 16.x, 17.x] os: [ubuntu-latest, macOS-latest, windows-latest] runs-on: ${{ matrix.os }} From 0f6d7657e71f1de3c1fc2a2a75776b7766f0576a Mon Sep 17 00:00:00 2001 From: Valerio Sevilla Date: Mon, 8 Nov 2021 00:22:06 +0100 Subject: [PATCH 09/41] Upgrade jszip to its latest version to date. This version does not have any vulnerability found by Snyk so far (#1895) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 807ad66b8..ce21d2630 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", - "jszip": "^3.5.0", + "jszip": "^3.7.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", From dc45ddfdae944277db19deb92f19b4d540b92b25 Mon Sep 17 00:00:00 2001 From: Xavi <3817677+xjrcode@users.noreply.github.com> Date: Thu, 18 Nov 2021 03:29:32 +0100 Subject: [PATCH 10/41] Update README.md (#1677) * Update README.md Added note about internationalized formula semantic * Update README.md Co-authored-by: Xavi <3817677+xavi-dev@users.noreply.github.com> --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5b8d3a250..d95f99393 100644 --- a/README.md +++ b/README.md @@ -2631,10 +2631,13 @@ An Excel formula for calculating values on the fly. Note that ExcelJS cannot process the formula to generate a result, it must be supplied. +Note that function semantic names must be in English and the separator must be a comma. + E.g. ```javascript worksheet.getCell('A3').value = { formula: 'A1+A2', result: 7 }; +worksheet.getCell('A3').value = { formula: 'SUM(A1,A2)', result: 7 }; ``` Cells also support convenience getters to access the formula and result: From d786c4db66f0e06587a72d1fac0bead394542957 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Wed, 17 Nov 2021 19:31:33 -0700 Subject: [PATCH 11/41] Set prototype of RegExp correctly. (#1700) Co-authored-by: Siemienik Pawel --- README.md | 2 +- README_zh.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d95f99393..59232e2bc 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ try { } return new RegExp(pattern, flags); }; - global.RegExp.prototype = RegExp; + global.RegExp.prototype = RegExp.prototype; } ``` diff --git a/README_zh.md b/README_zh.md index dc05bf49a..491c0c2f5 100644 --- a/README_zh.md +++ b/README_zh.md @@ -196,7 +196,7 @@ try { } return new RegExp(pattern, flags); }; - global.RegExp.prototype = RegExp; + global.RegExp.prototype = RegExp.prototype; } ``` From 4d71b664b7f9faefcf3cc4e1e8a33240f6c712c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Bj=C3=B8rlig?= Date: Sun, 21 Nov 2021 11:24:49 +0100 Subject: [PATCH 12/41] added timeouts (#1733) --- .github/workflows/asset-size.yml | 1 + .github/workflows/exceljs.yml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/asset-size.yml b/.github/workflows/asset-size.yml index 2ac84661d..f9c813146 100644 --- a/.github/workflows/asset-size.yml +++ b/.github/workflows/asset-size.yml @@ -4,6 +4,7 @@ on: [pull_request] jobs: compare: + timeout-minutes: 15 runs-on: ubuntu-latest steps: diff --git a/.github/workflows/exceljs.yml b/.github/workflows/exceljs.yml index 338ca06a0..2cddfe3a8 100644 --- a/.github/workflows/exceljs.yml +++ b/.github/workflows/exceljs.yml @@ -8,6 +8,7 @@ on: jobs: test: + timeout-minutes: 10 name: Node v${{ matrix.node-version }} on ${{ matrix.os }} strategy: fail-fast: false @@ -44,6 +45,7 @@ jobs: CI: true benchmark: + timeout-minutes: 15 name: Measure performance impact of changes runs-on: ubuntu-latest @@ -67,6 +69,7 @@ jobs: CI: true typescript: + timeout-minutes: 15 name: Ensure typescript compatibility runs-on: ubuntu-latest From 860b862d122c2645f8b34f0f885a64b104f7a538 Mon Sep 17 00:00:00 2001 From: skypesky <2678630761@qq.com> Date: Sun, 21 Nov 2021 18:38:18 +0800 Subject: [PATCH 13/41] fix issue 1676 (#1701) * fix issue 1676 Broken internal link on Google sheets * Update hyperlink-xform.js * fix(issue 1676): add unit test --- lib/xlsx/xform/sheet/hyperlink-xform.js | 29 +++++++++++++++---- .../xlsx/xform/sheet/hyperlink-xform.spec.js | 12 ++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/xlsx/xform/sheet/hyperlink-xform.js b/lib/xlsx/xform/sheet/hyperlink-xform.js index 3fb560a6d..88f4ee2ec 100644 --- a/lib/xlsx/xform/sheet/hyperlink-xform.js +++ b/lib/xlsx/xform/sheet/hyperlink-xform.js @@ -6,11 +6,20 @@ class HyperlinkXform extends BaseXform { } render(xmlStream, model) { - xmlStream.leafNode('hyperlink', { - ref: model.address, - 'r:id': model.rId, - tooltip: model.tooltip, - }); + if (this.isInternalLink(model)) { + xmlStream.leafNode('hyperlink', { + ref: model.address, + 'r:id': model.rId, + tooltip: model.tooltip, + location: model.target, + }); + } else { + xmlStream.leafNode('hyperlink', { + ref: model.address, + 'r:id': model.rId, + tooltip: model.tooltip, + }); + } } parseOpen(node) { @@ -20,6 +29,11 @@ class HyperlinkXform extends BaseXform { rId: node.attributes['r:id'], tooltip: node.attributes.tooltip, }; + + // This is an internal link + if (node.attributes.location) { + this.model.target = node.attributes.location; + } return true; } return false; @@ -30,6 +44,11 @@ class HyperlinkXform extends BaseXform { parseClose() { return false; } + + isInternalLink(model) { + // @example: Sheet2!D3, return true + return model.target && /^[^!]+![a-zA-Z]+[\d]+$/.test(model.target); + } } module.exports = HyperlinkXform; diff --git a/spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js b/spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js index 4292dfe9d..9e25bbc61 100644 --- a/spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js +++ b/spec/unit/xlsx/xform/sheet/hyperlink-xform.spec.js @@ -15,6 +15,18 @@ const expectations = [ xml: '', tests: ['render', 'renderIn', 'parse'], }, + { + title: 'Internal Link', + create() { + return new HyperlinkXform(); + }, + preparedModel: {address: 'B6', rId: 'rId1', target: 'sheet1!B2'}, + get parsedModel() { + return this.preparedModel; + }, + xml: '', + tests: ['render', 'renderIn', 'parse'], + }, ]; describe('HyperlinkXform', () => { From c228180cbea17fe7b7a6e0cabe58fcc94604f368 Mon Sep 17 00:00:00 2001 From: Siemienik Pawel Date: Thu, 13 Apr 2023 15:34:23 +0200 Subject: [PATCH 14/41] ExcelJS/ExcelJS#2237 : Update CI Tests, Drop support for Node v8 (#2242) FEAT https://github.com/exceljs/exceljs/issues/2237 In this PR: * Added node versions 18 and 19 to the tests suite * Drop node v8 tests --- .github/workflows/{exceljs.yml => tests.yml} | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename .github/workflows/{exceljs.yml => tests.yml} (95%) diff --git a/.github/workflows/exceljs.yml b/.github/workflows/tests.yml similarity index 95% rename from .github/workflows/exceljs.yml rename to .github/workflows/tests.yml index 2cddfe3a8..29a713612 100644 --- a/.github/workflows/exceljs.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: ExcelJS +name: Tests on: push: @@ -13,8 +13,7 @@ jobs: strategy: fail-fast: false matrix: - # https://github.com/actions/setup-node/issues/27 - node-version: [8.17.0, 10.x, 12.x, 14.x, 16.x, 17.x] + node-version: [10.x, 12.x, 14.x, 16.x, 17.x, 18.x, 19.x] os: [ubuntu-latest, macOS-latest, windows-latest] runs-on: ${{ matrix.os }} From a081694d283a9b78d78b6dba3115a0e86b7e4d51 Mon Sep 17 00:00:00 2001 From: Henry Chan <112690257+hfhchan-plb@users.noreply.github.com> Date: Thu, 13 Apr 2023 21:53:54 +0800 Subject: [PATCH 15/41] Fix types for getWorksheet() (#2223) ## Summary Fix types for getWorksheet(). It can return undefined ## Test plan N/A ## Related to source code (for typings update) [lib/doc/workbook.js#L143](https://github.com/exceljs/exceljs/blob/master/lib/doc/workbook.js#L143) --------- Co-authored-by: Siemienik Pawel --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index c5174c0d2..5b0fb90de 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1743,7 +1743,7 @@ export class Workbook { /** * fetch sheet by name or id */ - getWorksheet(indexOrName: number | string): Worksheet; + getWorksheet(indexOrName?: number | string): Worksheet | undefined; /** * Iterate over all sheets. From 202d2e41b6b4a3ef8a13d9d6cb20fa8e0e973652 Mon Sep 17 00:00:00 2001 From: Siemienik Pawel Date: Thu, 13 Apr 2023 20:21:43 +0200 Subject: [PATCH 16/41] Remove legacy badges --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 59232e2bc..9ad3b85d4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # ExcelJS [![Build status](https://github.com/exceljs/exceljs/workflows/ExcelJS/badge.svg)](https://github.com/exceljs/exceljs/actions?query=workflow%3AExcelJS) -[![Code Quality: Javascript](https://img.shields.io/lgtm/grade/javascript/g/exceljs/exceljs.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/exceljs/exceljs/context:javascript) -[![Total Alerts](https://img.shields.io/lgtm/alerts/g/exceljs/exceljs.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/exceljs/exceljs/alerts) Read, manipulate and write spreadsheet data and styles to XLSX and JSON. From 478e2a0e89887e6ffe14b42a6e8e5ecaf9ff5444 Mon Sep 17 00:00:00 2001 From: Takumi Kaji Date: Fri, 14 Apr 2023 22:24:04 +0900 Subject: [PATCH 17/41] Add characters cannot be used for worksheet name (#2126) Co-authored-by: Siemienik Pawel --- lib/doc/workbook.js | 4 ++-- spec/integration/worksheet.spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/doc/workbook.js b/lib/doc/workbook.js index a18383317..c05dd7836 100644 --- a/lib/doc/workbook.js +++ b/lib/doc/workbook.js @@ -60,9 +60,9 @@ class Workbook { // Illegal character in worksheet name: asterisk (*), question mark (?), // colon (:), forward slash (/ \), or bracket ([]) - if (/[*?:/\\[\]]/.test(name)) { + if (/[*?:/\\[\]*?:¥/\[]]/.test(name)) { throw new Error( - `Worksheet name ${name} cannot include any of the following characters: * ? : \\ / [ ]` + `Worksheet name ${name} cannot include any of the following characters: * ? : \\ / [ ] * ? : ¥ / \ [ ]` ); } diff --git a/spec/integration/worksheet.spec.js b/spec/integration/worksheet.spec.js index 81868677c..e0210c8cc 100644 --- a/spec/integration/worksheet.spec.js +++ b/spec/integration/worksheet.spec.js @@ -702,11 +702,11 @@ describe('Worksheet', () => { it('throws an error', () => { const workbook = new ExcelJS.Workbook(); - const invalidCharacters = ['*', '?', ':', '/', '\\', '[', ']']; + const invalidCharacters = ['*', '?', ':', '/', '\\', '[', ']', '*', '?', ':', '¥', '/', '\', '[', ']']; for (const invalidCharacter of invalidCharacters) { expect(() => workbook.addWorksheet(invalidCharacter)).to.throw( - `Worksheet name ${invalidCharacter} cannot include any of the following characters: * ? : \\ / [ ]` + `Worksheet name ${invalidCharacter} cannot include any of the following characters: * ? : \\ / [ ] * ? : ¥ / \ [ ]` ); } }); From 4acab1d40a762dec7da3f198747ee23f0555062e Mon Sep 17 00:00:00 2001 From: Paulius Grabauskas Date: Fri, 14 Apr 2023 16:42:23 +0300 Subject: [PATCH 18/41] Fix issue #1753 Reject promise when workbook reader is writing to temporary file stream and error occurs (#1756) Co-authored-by: Paulius Grabauskas Co-authored-by: Siemienik Pawel --- lib/stream/xlsx/workbook-reader.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/stream/xlsx/workbook-reader.js b/lib/stream/xlsx/workbook-reader.js index 0a2a3c65c..0d3afd62a 100644 --- a/lib/stream/xlsx/workbook-reader.js +++ b/lib/stream/xlsx/workbook-reader.js @@ -119,6 +119,7 @@ class WorkbookReader extends EventEmitter { waitingWorkSheets.push({sheetNo, path, tempFileCleanupCallback}); const tempStream = fs.createWriteStream(path); + tempStream.on('error', reject); entry.pipe(tempStream); return tempStream.on('finish', () => { return resolve(); @@ -298,11 +299,8 @@ class WorkbookReader extends EventEmitter { options: this.options, }); - const matchingRel = (this.workbookRels || []).find( - rel => rel.Target === `worksheets/sheet${sheetNo}.xml` - ); - const matchingSheet = - matchingRel && (this.model.sheets || []).find(sheet => sheet.rId === matchingRel.Id); + const matchingRel = (this.workbookRels || []).find(rel => rel.Target === `worksheets/sheet${sheetNo}.xml`); + const matchingSheet = matchingRel && (this.model.sheets || []).find(sheet => sheet.rId === matchingRel.Id); if (matchingSheet) { worksheetReader.id = matchingSheet.id; worksheetReader.name = matchingSheet.name; From fd727053997fb8d5782ee6a5331d933e308d99be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wulf=20S=C3=B6lter?= Date: Sat, 15 Apr 2023 01:49:58 +1200 Subject: [PATCH 19/41] README.md to have correct link for Streaming XLSX (#2186) Co-authored-by: Siemienik Pawel --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ad3b85d4..9886f8bd7 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ To be clear, all contributions added to this library will be included in the lib
  • Streaming I/O
  • From 81c5e09d643105fc847a935f4e4193dfab39454a Mon Sep 17 00:00:00 2001 From: DemoJx <37802689+DemoJj@users.noreply.github.com> Date: Fri, 14 Apr 2023 22:12:58 +0800 Subject: [PATCH 20/41] Added a polyfill of promise.finally to support lower version firefox. (#1982) Co-authored-by: demojx Co-authored-by: Siemienik Pawel --- lib/exceljs.browser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/exceljs.browser.js b/lib/exceljs.browser.js index 80f76c082..d7558203a 100644 --- a/lib/exceljs.browser.js +++ b/lib/exceljs.browser.js @@ -1,5 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies,node/no-unpublished-require */ require('core-js/modules/es.promise'); +require('core-js/modules/es.promise.finally'); require('core-js/modules/es.object.assign'); require('core-js/modules/es.object.keys'); require('core-js/modules/es.object.values'); From 5f306b6ed1ddb363fccbc8d3c398000342074ca5 Mon Sep 17 00:00:00 2001 From: ZyqGitHub1 Date: Fri, 14 Apr 2023 22:21:10 +0800 Subject: [PATCH 21/41] Fix: read this.worksheet before assign it (#1934) Co-authored-by: Siemienik Pawel --- lib/doc/anchor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/doc/anchor.js b/lib/doc/anchor.js index 9aa5f70e2..dd3509a74 100644 --- a/lib/doc/anchor.js +++ b/lib/doc/anchor.js @@ -4,6 +4,8 @@ const colCache = require('../utils/col-cache'); class Anchor { constructor(worksheet, address, offset = 0) { + this.worksheet = worksheet; + if (!address) { this.nativeCol = 0; this.nativeColOff = 0; @@ -29,8 +31,6 @@ class Anchor { this.nativeRow = 0; this.nativeRowOff = 0; } - - this.worksheet = worksheet; } static asInstance(model) { From 5b4fa3b0664ebb267779dbd212ad5f3807bf758d Mon Sep 17 00:00:00 2001 From: jarrod-cocoon <122930640+jarrod-cocoon@users.noreply.github.com> Date: Tue, 18 Apr 2023 05:44:59 -0700 Subject: [PATCH 22/41] chore: upgrade jszip to ^3.10.1 (#2211) No issues found with our usage in https://stuk.github.io/jszip/CHANGES.html, most notable change was 3.9.0 API change, but we are unaffected, due to not using constructor arguments. This patches the zipslip vulnerability in <= 3.7.1 and patched in >= 3.8.0. Co-authored-by: Siemienik Pawel --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce21d2630..d48e94057 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", - "jszip": "^3.7.1", + "jszip": "^3.10.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", From 4460df81307b925d1a3aa32337c06e06cd737604 Mon Sep 17 00:00:00 2001 From: Hugo <51097011+HugoP27@users.noreply.github.com> Date: Tue, 18 Apr 2023 15:24:33 +0200 Subject: [PATCH 23/41] fixed spelling error in README.md file (#2208) becouse changed to because --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9886f8bd7..00c27c491 100644 --- a/README.md +++ b/README.md @@ -371,7 +371,7 @@ workbook.worksheets[0]; //the first one; ``` It's important to know that `workbook.getWorksheet(1) != Workbook.worksheets[0]` and `workbook.getWorksheet(1) != Workbook.worksheets[1]`, -becouse `workbook.worksheets[0].id` may have any value. +because `workbook.worksheets[0].id` may have any value. ## Worksheet State[⬆](#contents) From 77e549a8176d62cd0ada398fc8436ffb5589db5d Mon Sep 17 00:00:00 2001 From: zurmokeeper <3382272560@qq.com> Date: Tue, 25 Apr 2023 02:43:32 +0800 Subject: [PATCH 24/41] fix: Fix xlsx.writeFile() not catching error when error occurs (#2244) * fix: Fix xlsx.writeFile() not catching error when error occurs * temp submit * Additional test cases and formatted writeFile code --------- Co-authored-by: linxl <1658370535@qq.com> Co-authored-by: Siemienik Pawel --- lib/xlsx/xlsx.js | 2 ++ spec/integration/pr/test-pr-2244.spec.js | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 spec/integration/pr/test-pr-2244.spec.js diff --git a/lib/xlsx/xlsx.js b/lib/xlsx/xlsx.js index 4f6bc02c5..268d1ddb3 100644 --- a/lib/xlsx/xlsx.js +++ b/lib/xlsx/xlsx.js @@ -675,6 +675,8 @@ class XLSX { this.write(stream, options).then(() => { stream.end(); + }).catch(err=>{ + reject(err); }); }); } diff --git a/spec/integration/pr/test-pr-2244.spec.js b/spec/integration/pr/test-pr-2244.spec.js new file mode 100644 index 000000000..667446c4c --- /dev/null +++ b/spec/integration/pr/test-pr-2244.spec.js @@ -0,0 +1,23 @@ +const ExcelJS = verquire('exceljs'); + +describe('pull request 2244', () => { + it('pull request 2244- Fix xlsx.writeFile() not catching error when error occurs', async () => { + async function test() { + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('sheet'); + const imageId1 = workbook.addImage({ + filename: 'path/to/image.jpg', // Non-existent file + extension: 'jpeg', + }); + worksheet.addImage(imageId1, 'B2:D6'); + await workbook.xlsx.writeFile('test.xlsx'); + } + let error; + try { + await test(); + } catch (err) { + error = err; + } + expect(error).to.be.an('error'); + }); +}); From 887983a92affab5e9967ed68d70e5670c08c147d Mon Sep 17 00:00:00 2001 From: Siemienik Pawel Date: Fri, 5 May 2023 14:05:58 +0200 Subject: [PATCH 25/41] Improve worksheets' naming validation logic. (#2257) ## Summary This PR aims to resolve #2243 issue. Additionally, I transferred the responsibility for name validation from `Workbook` to `Worksheet`, which allowed me to tighten the rules for changing the name using: `ws.name` ## Test plan I completed the missing tests and covers all new assertions. --- lib/doc/workbook.js | 30 +----------- lib/doc/worksheet.js | 51 +++++++++++++++++-- spec/integration/worksheet.spec.js | 78 +++++++++++++++++++++++++----- 3 files changed, 114 insertions(+), 45 deletions(-) diff --git a/lib/doc/workbook.js b/lib/doc/workbook.js index c05dd7836..8e7f46ecd 100644 --- a/lib/doc/workbook.js +++ b/lib/doc/workbook.js @@ -53,30 +53,6 @@ class Workbook { addWorksheet(name, options) { const id = this.nextId; - if (name && name.length > 31) { - // eslint-disable-next-line no-console - console.warn(`Worksheet name ${name} exceeds 31 chars. This will be truncated`); - } - - // Illegal character in worksheet name: asterisk (*), question mark (?), - // colon (:), forward slash (/ \), or bracket ([]) - if (/[*?:/\\[\]*?:¥/\[]]/.test(name)) { - throw new Error( - `Worksheet name ${name} cannot include any of the following characters: * ? : \\ / [ ] * ? : ¥ / \ [ ]` - ); - } - - if (/(^')|('$)/.test(name)) { - throw new Error( - `The first or last character of worksheet name cannot be a single quotation mark: ${name}` - ); - } - - name = (name || `sheet${id}`).substring(0, 31); - if (this._worksheets.find(ws => ws && ws.name.toLowerCase() === name.toLowerCase())) { - throw new Error(`Worksheet name already exists: ${name}`); - } - // if options is a color, call it tabColor (and signal deprecated message) if (options) { if (typeof options === 'string') { @@ -102,10 +78,7 @@ class Workbook { } } - const lastOrderNo = this._worksheets.reduce( - (acc, ws) => ((ws && ws.orderNo) > acc ? ws.orderNo : acc), - 0 - ); + const lastOrderNo = this._worksheets.reduce((acc, ws) => ((ws && ws.orderNo) > acc ? ws.orderNo : acc), 0); const worksheetOptions = Object.assign({}, options, { id, name, @@ -235,7 +208,6 @@ class Workbook { state, workbook: this, })); - worksheet.model = worksheetModel; }); diff --git a/lib/doc/worksheet.js b/lib/doc/worksheet.js index 158391d31..80fa123e7 100644 --- a/lib/doc/worksheet.js +++ b/lib/doc/worksheet.js @@ -20,13 +20,14 @@ const {copyStyle} = require('../utils/copy-style'); class Worksheet { constructor(options) { options = options || {}; + this._workbook = options.workbook; // in a workbook, each sheet will have a number this.id = options.id; this.orderNo = options.orderNo; // and a name - this.name = options.name || `Sheet${this.id}`; + this.name = options.name; // add a state this.state = options.state || 'visible'; @@ -47,8 +48,6 @@ class Worksheet { // record of all row and column pageBreaks this.rowBreaks = []; - this._workbook = options.workbook; - // for tabColor, default row height, outline levels, etc this.properties = Object.assign( {}, @@ -128,6 +127,52 @@ class Worksheet { this.conditionalFormattings = []; } + get name() { + return this._name; + } + + set name(name) { + if (name === undefined) { + name = `sheet${this.id}`; + } + + if (this._name === name) return; + + if (typeof name !== 'string') { + throw new Error('The name has to be a string.'); + } + + if (name === '') { + throw new Error('The name can\'t be empty.'); + } + + if (name === 'History') { + throw new Error('The name "History" is protected. Please use a different name.'); + } + + // Illegal character in worksheet name: asterisk (*), question mark (?), + // colon (:), forward slash (/ \), or bracket ([]) + if (/[*?:/\\[\]]/.test(name)) { + throw new Error(`Worksheet name ${name} cannot include any of the following characters: * ? : \\ / [ ]`); + } + + if (/(^')|('$)/.test(name)) { + throw new Error(`The first or last character of worksheet name cannot be a single quotation mark: ${name}`); + } + + if (name && name.length > 31) { + // eslint-disable-next-line no-console + console.warn(`Worksheet name ${name} exceeds 31 chars. This will be truncated`); + name = name.substring(0, 31); + } + + if (this._workbook._worksheets.find(ws => ws && ws.name.toLowerCase() === name.toLowerCase())) { + throw new Error(`Worksheet name already exists: ${name}`); + } + + this._name = name; + } + get workbook() { return this._workbook; } diff --git a/spec/integration/worksheet.spec.js b/spec/integration/worksheet.spec.js index e0210c8cc..a70dfcef8 100644 --- a/spec/integration/worksheet.spec.js +++ b/spec/integration/worksheet.spec.js @@ -677,22 +677,56 @@ describe('Worksheet', () => { expect(ws.getRows(1, 0)).to.equal(undefined); }); - context('when worksheet name is less than or equal 31', () => { it('save the original name', () => { const wb = new ExcelJS.Workbook(); - let ws = wb.addWorksheet('ThisIsAWorksheetName'); + let ws = wb.addWorksheet(); + ws.name = 'ThisIsAWorksheetName'; expect(ws.name).to.equal('ThisIsAWorksheetName'); - ws = wb.addWorksheet('ThisIsAWorksheetNameWith31Chars'); + ws = wb.addWorksheet(); + ws.name = 'ThisIsAWorksheetNameWith31Chars'; expect(ws.name).to.equal('ThisIsAWorksheetNameWith31Chars'); }); }); + context('name is be not empty string', () => { + it('when empty should thrown an error', () => { + const wb = new ExcelJS.Workbook(); + + expect(() => { + const ws = wb.addWorksheet(); + ws.name = ''; + }).to.throw('The name can\'t be empty.'); + }); + it('when isn\'t string should thrown an error', () => { + const wb = new ExcelJS.Workbook(); + + expect(() => { + const ws = wb.addWorksheet(); + ws.name = 0; + }).to.throw('The name has to be a string.'); + }); + }); + + context('when worksheet name is `History`', () => { + it('thrown an error', () => { + const wb = new ExcelJS.Workbook(); + + expect(() => { + const ws = wb.addWorksheet(); + ws.name = 'History'; + }).to.throw( + 'The name "History" is protected. Please use a different name.' + ); + }); + }); + context('when worksheet name is longer than 31', () => { it('keep first 31 characters', () => { const wb = new ExcelJS.Workbook(); - const ws = wb.addWorksheet('ThisIsAWorksheetNameThatIsLongerThan31'); + const ws = wb.addWorksheet(); + ws.name = 'ThisIsAWorksheetNameThatIsLongerThan31'; expect(ws.name).to.equal('ThisIsAWorksheetNameThatIsLonge'); }); @@ -702,11 +736,14 @@ describe('Worksheet', () => { it('throws an error', () => { const workbook = new ExcelJS.Workbook(); - const invalidCharacters = ['*', '?', ':', '/', '\\', '[', ']', '*', '?', ':', '¥', '/', '\', '[', ']']; + const invalidCharacters = ['*', '?', ':', '/', '\\', '[', ']']; for (const invalidCharacter of invalidCharacters) { - expect(() => workbook.addWorksheet(invalidCharacter)).to.throw( - `Worksheet name ${invalidCharacter} cannot include any of the following characters: * ? : \\ / [ ] * ? : ¥ / \ [ ]` + expect(() => { + const ws = workbook.addWorksheet(); + ws.name = invalidCharacter; + }).to.throw( + `Worksheet name ${invalidCharacter} cannot include any of the following characters: * ? : \\ / [ ]` ); } }); @@ -717,7 +754,10 @@ describe('Worksheet', () => { const invalidNames = ['\'sheetName', 'sheetName\'']; for (const invalidName of invalidNames) { - expect(() => workbook.addWorksheet(invalidName)).to.throw( + expect(() => { + const ws = workbook.addWorksheet(); + ws.name = invalidName; + }).to.throw( `The first or last character of worksheet name cannot be a single quotation mark: ${invalidName}` ); } @@ -732,9 +772,13 @@ describe('Worksheet', () => { const invalideName = 'THISISAWORKSHEETNAMEINUPPERCASE'; const expectedError = `Worksheet name already exists: ${invalideName}`; - wb.addWorksheet(validName); + const ws = wb.addWorksheet(); + ws.name = validName; - expect(() => wb.addWorksheet(invalideName)).to.throw(expectedError); + expect(() => { + const newWs = wb.addWorksheet(); + newWs.name = invalideName; + }).to.throw(expectedError); }); it('throws an error', () => { @@ -744,10 +788,18 @@ describe('Worksheet', () => { const invalideName = 'ThisIsAWorksheetNameThatIsLongerThan31'; const expectedError = `Worksheet name already exists: ${validName}`; - wb.addWorksheet(validName); + const ws = wb.addWorksheet(); + ws.name = validName; + + expect(() => { + const newWs = wb.addWorksheet(); + newWs.name = validName; + }).to.throw(expectedError); - expect(() => wb.addWorksheet(validName)).to.throw(expectedError); - expect(() => wb.addWorksheet(invalideName)).to.throw(expectedError); + expect(() => { + const newWs = wb.addWorksheet(); + newWs.name = invalideName; + }).to.throw(expectedError); }); }); }); From 0c4b238c51646c48725e91d73a58a87fe56b7cfc Mon Sep 17 00:00:00 2001 From: babu-ch <42715882+babu-ch@users.noreply.github.com> Date: Fri, 5 May 2023 21:58:33 +0900 Subject: [PATCH 26/41] spliceRows remove last row (#2140) fixes #2125 --- lib/doc/worksheet.js | 3 +++ .../issues/issue-2125-spliceRows-last-row.spec.js | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 spec/integration/issues/issue-2125-spliceRows-last-row.spec.js diff --git a/lib/doc/worksheet.js b/lib/doc/worksheet.js index 80fa123e7..13e414140 100644 --- a/lib/doc/worksheet.js +++ b/lib/doc/worksheet.js @@ -490,6 +490,9 @@ class Worksheet { let rSrc; if (nExpand < 0) { // remove rows + if (start === nEnd) { + this._rows[nEnd - 1] = undefined; + } for (i = nKeep; i <= nEnd; i++) { rSrc = this._rows[i - 1]; if (rSrc) { diff --git a/spec/integration/issues/issue-2125-spliceRows-last-row.spec.js b/spec/integration/issues/issue-2125-spliceRows-last-row.spec.js new file mode 100644 index 000000000..6b8737de7 --- /dev/null +++ b/spec/integration/issues/issue-2125-spliceRows-last-row.spec.js @@ -0,0 +1,13 @@ +const ExcelJS = verquire('exceljs'); + +describe('github issues', () => { + it('issue 2125 - spliceRows remove last row', () => { + const wb = new ExcelJS.Workbook(); + const ws = wb.addWorksheet(); + ws.addRows([['1st'], ['2nd'], ['3rd']]); + + ws.spliceRows(ws.rowCount, 1); + + expect(ws.getRow(ws.rowCount).getCell(1).value).to.equal('2nd'); + }); +}); From ec92cb3b898bdf7f806ff9d7b8370c955ee8ba20 Mon Sep 17 00:00:00 2001 From: zhengcp <45730337+cpaiyueyue@users.noreply.github.com> Date: Fri, 5 May 2023 21:03:33 +0800 Subject: [PATCH 27/41] fix: fix the loss of column attributes due to incorrect column order (#2222) When I try to read the excel file and display it on the page, I found that the hidden columns of some files were not parsed correctly. ## Summary I found that if the order of the column data parsed here is not positive, the subsequent attributes will be overwritten ![image](https://user-images.githubusercontent.com/45730337/222389920-684a7904-9b9f-483e-9d97-3d1c1aa3cc36.png) ![image](https://user-images.githubusercontent.com/45730337/222389824-6f1c06b6-876d-4178-98e6-b8603bfd3da6.png) ## Test plan Before modification ![image](https://user-images.githubusercontent.com/45730337/222390826-8515397d-a498-4bca-91b2-62cac42cc0ff.png) After modification ![image](https://user-images.githubusercontent.com/45730337/222391194-bb1fe799-0b29-4396-9b10-0fa44767410f.png) Co-authored-by: zhengcp --- .gitignore | 3 ++- lib/doc/column.js | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b2e50bdae..8026a7080 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,5 @@ _SpecRunner.html .DS_Store # Ignore xlsx files generated during testing -*.xlsx \ No newline at end of file +*.xlsx +enpm-lock.yaml \ No newline at end of file diff --git a/lib/doc/column.js b/lib/doc/column.js index f7636172c..c7fc282fe 100644 --- a/lib/doc/column.js +++ b/lib/doc/column.js @@ -297,6 +297,13 @@ class Column { const columns = []; let count = 1; let index = 0; + /** + * sort cols by min + * If it is not sorted, the subsequent column configuration will be overwritten + * */ + cols = cols.sort(function(pre, next) { + return pre.min - next.min; + }); while (index < cols.length) { const col = cols[index++]; while (count < col.min) { From eec76a1d4e01e634d2fd9b3d2030cd36a0f2add4 Mon Sep 17 00:00:00 2001 From: Siemienik Pawel Date: Wed, 23 Aug 2023 18:02:55 +0200 Subject: [PATCH 28/41] Update README.md --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 00c27c491..0eac0502f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ +# Siemienik's Announcement + +> Hello to all the contributors and supporters of this project, +> +> I'm writing this with a heavy heart to let you know that the my development of this project has come to a halt. Since 2019, this has been primarily a volunteer effort on my part. While the journey has been incredibly rewarding, the lack of resources and other commitments has affected the pace and progress. +> +> However, I still firmly believe in the potential of this project. The possibilities for further development could include: +> +> 1. **Parallel Development:** Working alongside another project that uses `exceljs` and simultaneously contributing to the evolution of `exceljs`. +> 2. **Community Sponsorship:** Exploring platforms like Open Collective or similar avenues where community members can sponsor and support the ongoing development and maintenance. +> +> Please share your thoughs. I'm open to ideas and feedback from all of you. If you have any suggestions or if you're interested in supporting this project further, please do get in touch. I'd be happy to collaborate and find ways to move forward. +> +> For communication regarding this announcement or potential collaboration, please reach out to me at: [exceljs+announcement@siemienik.com](mailto:exceljs+announcement@siemienik.com) +> +> Thank you for your understanding and continued support. Let's keep the dream alive! +> +> Warm regards, +> +> Paweł Siemienik + + # ExcelJS [![Build status](https://github.com/exceljs/exceljs/workflows/ExcelJS/badge.svg)](https://github.com/exceljs/exceljs/actions?query=workflow%3AExcelJS) From 8a84db249e4c60c6131b04dd4ecc40c7a104df60 Mon Sep 17 00:00:00 2001 From: Raouf Albeni Date: Wed, 23 Aug 2023 19:25:07 +0300 Subject: [PATCH 29/41] Fix: Sheet Properties Types (#2327) --- index.d.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/index.d.ts b/index.d.ts index 5b0fb90de..fe3bef75b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1401,6 +1401,13 @@ export interface WorksheetProperties { */ outlineLevelRow: number; + /** + * The outline properties which controls how it will summarize rows and columns + */ + outlineProperties: { + summaryBelow: boolean, + summaryRight: boolean, + }; /** * Default row height (default: 15) */ From 06d014cfe85f0c62233f7d86cf24a50cfecc9c0c Mon Sep 17 00:00:00 2001 From: Siemienik Pawel Date: Thu, 24 Aug 2023 20:33:39 +0200 Subject: [PATCH 30/41] revert --- README.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/README.md b/README.md index 0eac0502f..00c27c491 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,3 @@ -# Siemienik's Announcement - -> Hello to all the contributors and supporters of this project, -> -> I'm writing this with a heavy heart to let you know that the my development of this project has come to a halt. Since 2019, this has been primarily a volunteer effort on my part. While the journey has been incredibly rewarding, the lack of resources and other commitments has affected the pace and progress. -> -> However, I still firmly believe in the potential of this project. The possibilities for further development could include: -> -> 1. **Parallel Development:** Working alongside another project that uses `exceljs` and simultaneously contributing to the evolution of `exceljs`. -> 2. **Community Sponsorship:** Exploring platforms like Open Collective or similar avenues where community members can sponsor and support the ongoing development and maintenance. -> -> Please share your thoughs. I'm open to ideas and feedback from all of you. If you have any suggestions or if you're interested in supporting this project further, please do get in touch. I'd be happy to collaborate and find ways to move forward. -> -> For communication regarding this announcement or potential collaboration, please reach out to me at: [exceljs+announcement@siemienik.com](mailto:exceljs+announcement@siemienik.com) -> -> Thank you for your understanding and continued support. Let's keep the dream alive! -> -> Warm regards, -> -> Paweł Siemienik - - # ExcelJS [![Build status](https://github.com/exceljs/exceljs/workflows/ExcelJS/badge.svg)](https://github.com/exceljs/exceljs/actions?query=workflow%3AExcelJS) From 7c1c3384a5e6b96fa1e7418d0ad10970ce879136 Mon Sep 17 00:00:00 2001 From: Siemienik Pawel Date: Tue, 19 Sep 2023 22:23:50 +0200 Subject: [PATCH 31/41] Use node 18 LTS for tsc, and benchmark. Add node 20. to test matrix. Hash action's version (#2354) --- .github/workflows/tests.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 29a713612..7ae75016b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [10.x, 12.x, 14.x, 16.x, 17.x, 18.x, 19.x] + node-version: [10.x, 12.x, 14.x, 16.x, 17.x, 18.x, 19.x, 20.x] os: [ubuntu-latest, macOS-latest, windows-latest] runs-on: ${{ matrix.os }} @@ -24,15 +24,15 @@ jobs: git config --global core.autocrlf false git config --global core.symlinks true if: runner.os == 'Windows' - - uses: actions/checkout@v2 + - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 #latest v2. TODO upgrade - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 #latest v2. TODO upgrade with: node-version: ${{ matrix.node-version }} - name: Create the npm cache directory run: mkdir npm-cache && npm config set cache ./npm-cache --global - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@8492260343ad570701412c2f464a5877dc76bace #latest v2 TODO upgrade with: path: ./npm-cache key: v1-${{ runner.os }}-node-${{ matrix.node-version }}-npm-${{ hashFiles('**/package.json') }} @@ -49,14 +49,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 #latest v2. TODO upgrade + - uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 #latest v2. TODO upgrade with: - node-version: 12.x + node-version: 18 - name: Create the npm cache directory run: mkdir npm-cache && npm config set cache ./npm-cache --global - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@8492260343ad570701412c2f464a5877dc76bace #latest v2 TODO upgrade with: path: ./npm-cache key: v1-npm-${{ hashFiles('**/package.json') }} @@ -73,14 +73,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 #latest v2. TODO upgrade + - uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 #latest v2. TODO upgrade with: - node-version: 12.x + node-version: 18 - name: Create the npm cache directory run: mkdir npm-cache && npm config set cache ./npm-cache --global - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@8492260343ad570701412c2f464a5877dc76bace #latest v2 TODO upgrade with: path: ./npm-cache key: v1-npm-${{ hashFiles('**/package.json') }} From 63795a3ee405cf84424257271219c8a111cf2562 Mon Sep 17 00:00:00 2001 From: Siemienik Pawel Date: Tue, 19 Sep 2023 22:37:05 +0200 Subject: [PATCH 32/41] Fix build badge --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00c27c491..f5bfeaa50 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ExcelJS -[![Build status](https://github.com/exceljs/exceljs/workflows/ExcelJS/badge.svg)](https://github.com/exceljs/exceljs/actions?query=workflow%3AExcelJS) +[![Build Status](https://github.com/exceljs/exceljs/actions/workflows/tests.yml/badge.svg?branch=master&event=push)](https://github.com/exceljs/exceljs/actions/workflows/tests.yml) Read, manipulate and write spreadsheet data and styles to XLSX and JSON. @@ -84,6 +84,10 @@ Versions are updated on release and any change will most likely result in merge To be clear, all contributions added to this library will be included in the library's MIT licence. +### Let's chat together: + +[![SiemaTeam](https://discordapp.com/api/guilds/976854442009825321/widget.png?style=banner2)](https://discord.gg/siema) + # Contents