From 0edd02d862f938f03c8c3b79acbd93e296ba944c Mon Sep 17 00:00:00 2001 From: Stefan Nedelchev Date: Sat, 15 Jul 2023 14:09:14 +0300 Subject: [PATCH 1/4] feat: added col breaks feature --- lib/doc/column.js | 21 ++++++++-- lib/doc/worksheet.js | 2 + lib/stream/xlsx/worksheet-writer.js | 8 ++++ lib/xlsx/xform/sheet/col-breaks-xform.js | 39 +++++++++++++++++++ lib/xlsx/xform/sheet/worksheet-xform.js | 3 ++ package.json | 2 +- .../integration/worksheet-xlsx-writer.spec.js | 22 +++++++++++ spec/unit/doc/worksheet.pagebreak.spec.js | 22 +++++++++++ .../unit/xlsx/xform/sheet/data/sheet.1.0.json | 1 + .../unit/xlsx/xform/sheet/data/sheet.1.1.json | 1 + .../unit/xlsx/xform/sheet/data/sheet.7.0.json | 1 + .../unit/xlsx/xform/sheet/data/sheet.7.1.json | 1 + 12 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 lib/xlsx/xform/sheet/col-breaks-xform.js diff --git a/lib/doc/column.js b/lib/doc/column.js index c7fc282..533d7e6 100644 --- a/lib/doc/column.js +++ b/lib/doc/column.js @@ -122,9 +122,7 @@ class Column { } get collapsed() { - return !!( - this._outlineLevel && this._outlineLevel >= this._worksheet.properties.outlineLevelCol - ); + return !!(this._outlineLevel && this._outlineLevel >= this._worksheet.properties.outlineLevelCol); } toString() { @@ -201,6 +199,21 @@ class Column { }); } + // Page Breaks + addPageBreak(t, b) { + const ws = this._worksheet; + const top = Math.max(0, t - 1) || 0; + const bottom = Math.max(0, b - 1) || 65535; + const pb = { + id: this._number, + max: bottom, + man: 1, + }; + if (top) pb.min = top; + + ws.colBreaks.push(pb); + } + // ========================================================================= // styles _applyStyle(name, value) { @@ -301,7 +314,7 @@ class Column { * sort cols by min * If it is not sorted, the subsequent column configuration will be overwritten * */ - cols = cols.sort(function(pre, next) { + cols = cols.sort(function(pre, next) { return pre.min - next.min; }); while (index < cols.length) { diff --git a/lib/doc/worksheet.js b/lib/doc/worksheet.js index ea17e13..a5a8892 100644 --- a/lib/doc/worksheet.js +++ b/lib/doc/worksheet.js @@ -48,6 +48,7 @@ class Worksheet { // record of all row and column pageBreaks this.rowBreaks = []; + this.colBreaks = []; // for tabColor, default row height, outline levels, etc this.properties = Object.assign( @@ -850,6 +851,7 @@ class Worksheet { pageSetup: this.pageSetup, headerFooter: this.headerFooter, rowBreaks: this.rowBreaks, + colBreaks: this.colBreaks, views: this.views, autoFilter: this.autoFilter, media: this._media.map(medium => medium.model), diff --git a/lib/stream/xlsx/worksheet-writer.js b/lib/stream/xlsx/worksheet-writer.js index 96705b7..47e09f1 100644 --- a/lib/stream/xlsx/worksheet-writer.js +++ b/lib/stream/xlsx/worksheet-writer.js @@ -34,6 +34,7 @@ const PictureXform = require('../../xlsx/xform/sheet/picture-xform'); const ConditionalFormattingsXform = require('../../xlsx/xform/sheet/cf/conditional-formattings-xform'); const HeaderFooterXform = require('../../xlsx/xform/sheet/header-footer-xform'); const RowBreaksXform = require('../../xlsx/xform/sheet/row-breaks-xform'); +const ColBreaksXform = require('../../xlsx/xform/sheet/col-breaks-xform'); // since prepare and render are functional, we can use singletons const xform = { @@ -52,6 +53,7 @@ const xform = { conditionalFormattings: new ConditionalFormattingsXform(), headerFooter: new HeaderFooterXform(), rowBreaks: new RowBreaksXform(), + colBreaks: new ColBreaksXform(), }; // ============================================================================================ @@ -107,6 +109,7 @@ class WorksheetWriter { // keep a record of all row and column pageBreaks this.rowBreaks = []; + this.colBreaks = []; // for default row height, outline levels, etc this.properties = Object.assign( @@ -246,6 +249,7 @@ class WorksheetWriter { this._writeBackground(); this._writeHeaderFooter(); this._writeRowBreaks(); + this._writeColBreaks(); // Legacy Data tag for comments this._writeLegacyData(); @@ -653,6 +657,10 @@ class WorksheetWriter { this.stream.write(xform.rowBreaks.toXml(this.rowBreaks)); } + _writeColBreaks() { + this.stream.write(xform.colBreaks.toXml(this.colBreaks)); + } + _writeDataValidations() { this.stream.write(xform.dataValidations.toXml(this.dataValidations.model)); } diff --git a/lib/xlsx/xform/sheet/col-breaks-xform.js b/lib/xlsx/xform/sheet/col-breaks-xform.js new file mode 100644 index 0000000..b333fc4 --- /dev/null +++ b/lib/xlsx/xform/sheet/col-breaks-xform.js @@ -0,0 +1,39 @@ +'use strict'; + +const PageBreaksXform = require('./page-breaks-xform'); + +const ListXform = require('../list-xform'); + +class ColBreaksXform extends ListXform { + constructor() { + const options = { + tag: 'colBreaks', + count: true, + childXform: new PageBreaksXform(), + }; + super(options); + } + + // get tag() { return 'colBreaks'; } + + render(xmlStream, model) { + if (model && model.length) { + xmlStream.openNode(this.tag, this.$); + if (this.count) { + xmlStream.addAttribute(this.$count, model.length); + xmlStream.addAttribute('manualBreakCount', model.length); + } + + const {childXform} = this; + model.forEach(childModel => { + childXform.render(xmlStream, childModel); + }); + + xmlStream.closeNode(); + } else if (this.empty) { + xmlStream.leafNode(this.tag); + } + } +} + +module.exports = ColBreaksXform; diff --git a/lib/xlsx/xform/sheet/worksheet-xform.js b/lib/xlsx/xform/sheet/worksheet-xform.js index ce861ff..4195f6d 100644 --- a/lib/xlsx/xform/sheet/worksheet-xform.js +++ b/lib/xlsx/xform/sheet/worksheet-xform.js @@ -27,6 +27,7 @@ const PictureXform = require('./picture-xform'); const DrawingXform = require('./drawing-xform'); const TablePartXform = require('./table-part-xform'); const RowBreaksXform = require('./row-breaks-xform'); +const ColBreaksXform = require('./col-breaks-xform'); const HeaderFooterXform = require('./header-footer-xform'); const ConditionalFormattingsXform = require('./cf/conditional-formattings-xform'); const ExtListXform = require('./ext-lst-xform'); @@ -113,6 +114,7 @@ class WorkSheetXform extends BaseXform { autoFilter: new AutoFilterXform(), mergeCells: new ListXform({tag: 'mergeCells', count: true, childXform: new MergeCellXform()}), rowBreaks: new RowBreaksXform(), + colBreaks: new ColBreaksXform(), hyperlinks: new ListXform({ tag: 'hyperlinks', count: false, @@ -343,6 +345,7 @@ class WorkSheetXform extends BaseXform { this.map.pageSetup.render(xmlStream, model.pageSetup); this.map.headerFooter.render(xmlStream, model.headerFooter); this.map.rowBreaks.render(xmlStream, model.rowBreaks); + this.map.colBreaks.render(xmlStream, model.colBreaks); this.map.drawing.render(xmlStream, model.drawing); // Note: must be after rowBreaks this.map.picture.render(xmlStream, model.background); // Note: must be after drawing this.map.tableParts.render(xmlStream, model.tables); diff --git a/package.json b/package.json index 2744e64..898ba63 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "saxes": "^5.0.1", "tmp": "^0.2.0", "unzipper": "^0.10.11", - "uuid": "^8.3.0" + "uuid": "^9.0.0" }, "devDependencies": { "@babel/cli": "^7.10.5", diff --git a/spec/integration/worksheet-xlsx-writer.spec.js b/spec/integration/worksheet-xlsx-writer.spec.js index 38d7433..a869eaa 100644 --- a/spec/integration/worksheet-xlsx-writer.spec.js +++ b/spec/integration/worksheet-xlsx-writer.spec.js @@ -534,5 +534,27 @@ describe('WorksheetWriter', () => { row.addPageBreak(); expect(ws.rowBreaks.length).to.equal(2); }); + + it('adds multiple col breaks', () => { + const wb = new ExcelJS.stream.xlsx.WorkbookWriter(); + const ws = wb.addWorksheet('blort'); + + // initial values + ws.getCell('A1').value = 'A1'; + ws.getCell('B1').value = 'B1'; + ws.getCell('C1').value = 'C1'; + ws.getCell('D1').value = 'D1'; + ws.getCell('A2').value = 'A2'; + ws.getCell('B2').value = 'B2'; + ws.getCell('C2').value = 'C2'; + ws.getCell('D2').value = 'D2'; + + let col = ws.getColumn('A'); + col.addPageBreak(); + col = ws.getColumn('C'); + col.addPageBreak(); + + expect(ws.colBreaks.length).to.equal(2); + }); }); }); diff --git a/spec/unit/doc/worksheet.pagebreak.spec.js b/spec/unit/doc/worksheet.pagebreak.spec.js index 9f75449..bfbfe87 100644 --- a/spec/unit/doc/worksheet.pagebreak.spec.js +++ b/spec/unit/doc/worksheet.pagebreak.spec.js @@ -21,5 +21,27 @@ describe('Worksheet', () => { expect(ws.rowBreaks.length).to.equal(2); }); + + it('adds multiple col breaks', () => { + const wb = new Excel.Workbook(); + const ws = wb.addWorksheet('blort'); + + // initial values + ws.getCell('A1').value = 'A1'; + ws.getCell('B1').value = 'B1'; + ws.getCell('C1').value = 'C1'; + ws.getCell('D1').value = 'D1'; + ws.getCell('A2').value = 'A2'; + ws.getCell('B2').value = 'B2'; + ws.getCell('C2').value = 'C2'; + ws.getCell('D2').value = 'D2'; + + let col = ws.getColumn('A'); + col.addPageBreak(); + col = ws.getColumn('C'); + col.addPageBreak(); + + expect(ws.colBreaks.length).to.equal(2); + }); }); }); diff --git a/spec/unit/xlsx/xform/sheet/data/sheet.1.0.json b/spec/unit/xlsx/xform/sheet/data/sheet.1.0.json index 9e3a0ef..a86cfd2 100644 --- a/spec/unit/xlsx/xform/sheet/data/sheet.1.0.json +++ b/spec/unit/xlsx/xform/sheet/data/sheet.1.0.json @@ -73,6 +73,7 @@ }, "media": [], "rowBreaks": [], + "colBreaks": [], "tables": [], "conditionalFormattings": [] } diff --git a/spec/unit/xlsx/xform/sheet/data/sheet.1.1.json b/spec/unit/xlsx/xform/sheet/data/sheet.1.1.json index dd989ae..339392a 100644 --- a/spec/unit/xlsx/xform/sheet/data/sheet.1.1.json +++ b/spec/unit/xlsx/xform/sheet/data/sheet.1.1.json @@ -79,6 +79,7 @@ "comments": [], "media": [], "rowBreaks": [], + "colBreaks": [], "tables": [], "headerFooter": { "oddHeader": "&C&KCCCCCC&\"Aril\"2 exceljs", diff --git a/spec/unit/xlsx/xform/sheet/data/sheet.7.0.json b/spec/unit/xlsx/xform/sheet/data/sheet.7.0.json index f3805bd..08dd6b4 100644 --- a/spec/unit/xlsx/xform/sheet/data/sheet.7.0.json +++ b/spec/unit/xlsx/xform/sheet/data/sheet.7.0.json @@ -43,6 +43,7 @@ {"id": 2, "max": 2, "min": 0, "man": 1}, {"id": 5, "max": 2, "min": 0, "man": 1} ], + "colBreaks": [], "media": [], "tables": [], "conditionalFormattings": [] diff --git a/spec/unit/xlsx/xform/sheet/data/sheet.7.1.json b/spec/unit/xlsx/xform/sheet/data/sheet.7.1.json index 0860b45..09bd163 100644 --- a/spec/unit/xlsx/xform/sheet/data/sheet.7.1.json +++ b/spec/unit/xlsx/xform/sheet/data/sheet.7.1.json @@ -46,6 +46,7 @@ {"id": 2, "max": 2, "min": 0, "man": 1}, {"id": 5, "max": 2, "min": 0, "man": 1} ], + "colBreaks": [], "comments": [], "media": [], "tables": [], From 8c34c9260f8e16020732af2cd893a96a64676f54 Mon Sep 17 00:00:00 2001 From: Stefan Nedelchev Date: Sat, 15 Jul 2023 14:50:18 +0300 Subject: [PATCH 2/4] updated type definitions. added more unit tests --- index.d.ts | 5 +++-- spec/unit/xlsx/xform/sheet/data/sheet.7.0.json | 4 +++- spec/unit/xlsx/xform/sheet/data/sheet.7.1.json | 4 +++- spec/unit/xlsx/xform/sheet/data/sheet.7.2.xml | 3 +++ spec/unit/xlsx/xform/sheet/worksheet-xform.spec.js | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/index.d.ts b/index.d.ts index be2012e..fee4959 100644 --- a/index.d.ts +++ b/index.d.ts @@ -972,7 +972,7 @@ export interface Range extends Location { }>): boolean; } -export interface RowBreak { +export interface PageBreak { id: number; max: number; min: number; @@ -986,7 +986,8 @@ export interface WorksheetModel { properties: WorksheetProperties; pageSetup: Partial; headerFooter: Partial; - rowBreaks: RowBreak[]; + rowBreaks: PageBreak[]; + colBreaks: PageBreak[]; views: WorksheetView[]; autoFilter: AutoFilter; media: Media[]; diff --git a/spec/unit/xlsx/xform/sheet/data/sheet.7.0.json b/spec/unit/xlsx/xform/sheet/data/sheet.7.0.json index 08dd6b4..0287553 100644 --- a/spec/unit/xlsx/xform/sheet/data/sheet.7.0.json +++ b/spec/unit/xlsx/xform/sheet/data/sheet.7.0.json @@ -43,7 +43,9 @@ {"id": 2, "max": 2, "min": 0, "man": 1}, {"id": 5, "max": 2, "min": 0, "man": 1} ], - "colBreaks": [], + "colBreaks": [ + { "id": 3, "min": 0, "max": 1000, "man": 1 } + ], "media": [], "tables": [], "conditionalFormattings": [] diff --git a/spec/unit/xlsx/xform/sheet/data/sheet.7.1.json b/spec/unit/xlsx/xform/sheet/data/sheet.7.1.json index 09bd163..db2436a 100644 --- a/spec/unit/xlsx/xform/sheet/data/sheet.7.1.json +++ b/spec/unit/xlsx/xform/sheet/data/sheet.7.1.json @@ -46,7 +46,9 @@ {"id": 2, "max": 2, "min": 0, "man": 1}, {"id": 5, "max": 2, "min": 0, "man": 1} ], - "colBreaks": [], + "colBreaks": [ + { "id": 3, "min": 0, "max": 1000, "man": 1 } + ], "comments": [], "media": [], "tables": [], diff --git a/spec/unit/xlsx/xform/sheet/data/sheet.7.2.xml b/spec/unit/xlsx/xform/sheet/data/sheet.7.2.xml index 0b5bcb4..a27830c 100644 --- a/spec/unit/xlsx/xform/sheet/data/sheet.7.2.xml +++ b/spec/unit/xlsx/xform/sheet/data/sheet.7.2.xml @@ -40,5 +40,8 @@ + + + \ No newline at end of file diff --git a/spec/unit/xlsx/xform/sheet/worksheet-xform.spec.js b/spec/unit/xlsx/xform/sheet/worksheet-xform.spec.js index 8c571a0..8c3eb6a 100644 --- a/spec/unit/xlsx/xform/sheet/worksheet-xform.spec.js +++ b/spec/unit/xlsx/xform/sheet/worksheet-xform.spec.js @@ -130,7 +130,7 @@ const expectations = [ }, }, { - title: 'Sheet 7 - Row Breaks', + title: 'Sheet 7 - Page Breaks', create: () => new WorksheetXform(), initialModel: require('./data/sheet.7.0.json'), preparedModel: require('./data/sheet.7.1.json'), From 7259a84787e4a2e8d9c9c467c21069dd38a3097a Mon Sep 17 00:00:00 2001 From: Stefan Nedelchev Date: Sat, 15 Jul 2023 20:28:47 +0300 Subject: [PATCH 3/4] added addPageBreak to Column interface --- index.d.ts | 5 +++++ lib/doc/column.js | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index fee4959..6491be8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -673,6 +673,11 @@ export interface Column { */ eachCell(opt: { includeEmpty: boolean }, callback: (cell: Cell, rowNumber: number) => void): void; + /** + * Inserts a page break after the column + */ + addPageBreak(tp?: number, btm?: number): void; + defn: any; //todo } export interface PageSetup { diff --git a/lib/doc/column.js b/lib/doc/column.js index 533d7e6..05091c8 100644 --- a/lib/doc/column.js +++ b/lib/doc/column.js @@ -200,10 +200,10 @@ class Column { } // Page Breaks - addPageBreak(t, b) { + addPageBreak(tp, btn) { const ws = this._worksheet; - const top = Math.max(0, t - 1) || 0; - const bottom = Math.max(0, b - 1) || 65535; + const top = Math.max(0, tp - 1) || 0; + const bottom = Math.max(0, btn - 1) || 65535; const pb = { id: this._number, max: bottom, From fd38dfc42d33df3be7204b819c1f376bf7590125 Mon Sep 17 00:00:00 2001 From: linxl <1658370535@qq.com> Date: Sun, 16 Jul 2023 20:56:27 +0800 Subject: [PATCH 4/4] update readme and package.json version --- README.md | 7 +++++++ README_zh.md | 7 +++++++ package.json | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a13a224..eee78ef 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ Reverse engineered from Excel spreadsheet files as a project. npm install @zurmokeeper/exceljs ``` +# New Features! + +Since V4.4.3, the detailed update of the new version can be accessed from [here](https://github.com/zurmokeeper/excelize/releases). + # V4.4.3 New Features! Change Log: @@ -782,6 +786,9 @@ worksheet.getColumn(5).outlineLevel = 1; expect(worksheet.getColumn(4).collapsed).to.equal(false); expect(worksheet.getColumn(5).collapsed).to.equal(true); +// Insert a page break below the column +dobCol.addPageBreak(); + // iterate over all current cells in this column dobCol.eachCell(function(cell, rowNumber) { // ... diff --git a/README_zh.md b/README_zh.md index b539492..e9e9563 100644 --- a/README_zh.md +++ b/README_zh.md @@ -14,6 +14,10 @@ npm install @zurmokeeper/exceljs ``` +# 新的功能! + +自V4.4.3以后,新版本详细更新内容在[这里](https://github.com/zurmokeeper/excelize/releases). + # V4.4.3 新的功能! 变更日志: @@ -730,6 +734,9 @@ worksheet.getColumn(5).outlineLevel = 1; expect(worksheet.getColumn(4).collapsed).to.equal(false); expect(worksheet.getColumn(5).collapsed).to.equal(true); +// 在该列右边插入一个分页符 +dobCol.addPageBreak(); + // 遍历此列中的所有当前单元格 dobCol.eachCell(function(cell, rowNumber) { // ... diff --git a/package.json b/package.json index 898ba63..2b95a96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zurmokeeper/exceljs", - "version": "4.4.3", + "version": "4.4.4", "description": "Excel Workbook Manager - Read and Write xlsx and csv Files.", "private": false, "license": "MIT",