From 55f84b16b790dda0e047c207d2507eb2d5ad5ee3 Mon Sep 17 00:00:00 2001 From: Faucon <49079695+FauconSpartiate@users.noreply.github.com> Date: Fri, 27 Oct 2023 00:21:53 +0200 Subject: [PATCH] Publish 3.0.0 (#272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kawaljeet Singh Co-authored-by: Kawaljeet Singh <49296873+justkawal@users.noreply.github.com> Co-authored-by: Yann Gauteron <37099668+amigne@users.noreply.github.com> Co-authored-by: Yann Gauteron Co-authored-by: take4 <46077789+take4blue@users.noreply.github.com> Co-authored-by: Dennis Münch Co-authored-by: Nicholas Frawley Co-authored-by: FutoTan <35399423+FutoTan@users.noreply.github.com> Co-authored-by: Ghost <114514@anymoums.com> Co-authored-by: Taoduhui Co-authored-by: sunboyy --- .github/CODEOWNERS | 2 +- .github/workflows/dart.yml | 4 +- CHANGELOG.md | 102 ++- README.md | 623 ++++++++---------- example/excel_border.dart | 75 +++ ...stom_width.dart => excel_custom_size.dart} | 25 +- example/excel_example.dart | 18 +- example/excel_time_consuming.dart | 8 +- lib/src/excel.dart | 22 +- lib/src/parser/parse.dart | 39 +- lib/src/save/save_file.dart | 405 +++++------- lib/src/save/self_correct_span.dart | 9 +- lib/src/sharedStrings/shared_strings.dart | 14 +- lib/src/sheet/cell_index.dart | 10 +- lib/src/sheet/data_model.dart | 22 +- lib/src/sheet/sheet.dart | 480 +++++++++----- lib/src/utilities/constants.dart | 4 +- lib/src/utilities/span.dart | 59 +- lib/src/utilities/utility.dart | 8 +- pubspec.yaml | 12 +- test/excel_test.dart | 265 +++++--- test/test_resources/mergedBorders.xlsx | Bin 0 -> 16344 bytes test/test_resources/newXLSFile.xls | Bin 0 -> 25088 bytes test/test_resources/oldXLSFile.xls | Bin 0 -> 8704 bytes test/test_resources/rphSample.xlsx | Bin 0 -> 9227 bytes 25 files changed, 1241 insertions(+), 965 deletions(-) create mode 100644 example/excel_border.dart rename example/{excel_custom_width.dart => excel_custom_size.dart} (78%) create mode 100644 test/test_resources/mergedBorders.xlsx create mode 100644 test/test_resources/newXLSFile.xls create mode 100644 test/test_resources/oldXLSFile.xls create mode 100644 test/test_resources/rphSample.xlsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c77583b7..1f4430bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # This file is used to define a set of default reviewers. -* @AlexanderJohr @FauconSpartiate @justkawal +* @FauconSpartiate @justkawal diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 5bc36231..514417b9 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -28,8 +28,8 @@ jobs: run: dart pub get # Uncomment this step to verify the use of 'dart format' on each commit. - - name: Verify formatting - run: dart format --output=none --set-exit-if-changed . + # - name: Verify formatting + # run: dart format --output=none --set-exit-if-changed . # Consider passing '--fatal-infos' for slightly stricter analysis. - name: Analyze project source diff --git a/CHANGELOG.md b/CHANGELOG.md index fba882f2..e209b10e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,71 +1,143 @@ # Changelog + All notable changes to this project will be documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.0.0] - 2023-07-30 + +### Breaking Changes + +- Renamed `getColAutoFits()` to `getColumnAutoFits()`, and changed return type to `Map` in `Sheet` +- Renamed `getColWidths()` to `getColumnWidths()`, and changed return type to `Map` in `Sheet` +- Renamed `getColAutoFit()` to `getColumnAutoFit()` in `Sheet` +- Renamed `getColWidth()` to `getColumnWidth()` in `Sheet` +- Renamed `setColAutoFit()` to `setColumnAutoFit()` in `Sheet` +- Renamed `setColWidth()` to `setColumnWidth()` in `Sheet` + +### Added + +- Add setMergedCellStyle() to Sheet, allowing to set style for merged cells +- Add setDefaultRowHeight(), setDefaultColumnWidth() to Sheet +- Add defaultRowHeight and defaultColumnWidth properties to Sheet +- Add getRowHeights(), getRowHeight() and setRowHeight to Sheet +- Add pub topics + +### Improved + +- Support sharedStrings absolute path +- Loosen up dependency constraints +- Clean up markdown files +- Clean up code + +### Fixed + +- Fixed many instances of missing/wrong data by comparing strings instead of hashes +- Ignore shared text in 'rPh' element +- Fix findAndReplace() not doing anything + ## [2.1.0] - 2023-03-30 + ### Improved + - Add border functionality + ### Fixed + - Fix Header and Footer with special characters - Fix sheet.merge() ## [2.0.4] - 2023-03-12 + ### Improved + - Automated Publishing. ## [2.0.3] - 2023-03-12 + ### Improved + - Readme updated. ## [2.0.2] - 2023-03-12 + ### Improved + - Fix bug on header and footer. ## [2.0.0-null-safety-4] - 2022-02-15 + ### Improved + - Fix saving XLXS bug on archive 3.2.0 ## [2.0.0-null-safety-3] - 2021-04-29 + ### Improved + - Forcefully initializing the variables on re-creation ## [2.0.0-null-safety-2] - 2021-04-29 + ### Improved + - Fix of sharedStringTarget fail to initialize issue ## [2.0.0-null-safety-1] - 2021-04-29 + ### Improved + - Fix of value not updating in cell ## [2.0.0-null-safety] - 2021-03-28 + ### Improved + - Null-safety ## [1.1.5] - 2020-08-17 + ### Improved + - Fixes ## [1.1.4] - 2020-07-23 + ### Improved + - Improvement in speed of apeending the rows ## [1.1.3] - 2020-07-23 + ### Improved + - Improvement in speed of apeending the rows ## [1.1.2] - 2020-07-18 + ### Improved + - Iterating Sheet's Data Object to operate on particular cells ## [1.1.1] - 2020-07-18 + ### Improved + - Health Improvement ## [1.1.0] - 2020-06-26 + ### Improved + - Bugs on deleting sheet ## [1.0.9] - 2020-06-06 + ### Added + - Copy - Rename - Delete @@ -78,33 +150,46 @@ All notable changes to this project will be documented in this file. - Bold ### Improved + - Faster Processing ## [1.0.8] - 2020-05-23 + ### Removed + - Bugs related to appendRows ## [1.0.7] - 2020-05-21 + ### Removed + - Bugs related to removal of rows ## [1.0.6] - 2020-05-21 + ### Added Functionality + - Find and Replace - Add row / column from Iterables ## [1.0.5] - 2020-05-15 + ### Removed + - Bugs related to Spanning - Unwanted removal of rows and columns from spanned cells ## [1.0.4] - 2020-05-10 + ### Improved + - Analysis related changes - Vertical Alignment Issue ## [1.0.3] - 2020-05-10 + ### Added + - Merging of Rows and Columns - Un-Merging of Rows and Columns - Font Color @@ -112,16 +197,27 @@ All notable changes to this project will be documented in this file. - Setting Default Sheet ## [1.0.2] - 2020-02-18 + ### Improved + - Minor Bugs ## [1.0.1] - 2020-02-18 -### Improved -- Health Maintenance -## [1.0.1] - 2020-02-18 ### Added + - TextWrapping and (Clip in Google Sheets) / (ShrinkToFit in Microsoft Excel) - Horizontal and Vertical Alignment - Update Cell by Cell-Name ("A1") + +### Improved + +- Health Maintenance + +### Fixes + - Minor Bug Fixes + +## [1.0.0] - 2020-02-18 + +- Initial Release diff --git a/README.md b/README.md index 64c29736..2cb24020 100644 --- a/README.md +++ b/README.md @@ -1,228 +1,164 @@ # Excel -# If you find this tool useful! Please drop a ⭐️. - - - Platform - - - Test - - - Pub Package - - - License: MIT - - - Issue - - - Forks - - - Stars - -
-
- -### [Excel](https://www.pub.dev/packages/excel) is a flutter and dart library for reading, creating and updating excel-sheets for XLSX files. - -#### This library is [MIT](https://github.com/justkawal/excel/blob/40b8b1ed8c3c213d8911784ddd40bf97841977a1/LICENSE#L1) licensed So, it's free to use anytime, anywhere without any consent, because we believe in Open Source work. - -# Lets Get Started - -### 1. Depend on it - -Add this to your package's `pubspec.yaml` file: - -```yaml -dependencies: - excel: 2.1.0 -``` - -### 2. Install it - -You can install packages from the command line: - -with `pub`: - -```css -$ dart pub add excel -``` - -with `Flutter`: - -```css -$ flutter packages get -``` - -### 3. Import it - -Now in your `Dart` code, you can use: - -```dart - import 'package:excel/excel.dart'; -``` - -# Usage - -### Breaking Changes for those moving from 1.0.8 and below ----> 1.0.9 and above versions - -The necessary changes to be made to updateCell function in order to prevent the code from breaking. - -```dart - - excel.updateCell('SheetName', CellIndex.indexByString('A2'), 'Here value', backgroundColorHex: '#1AFF1A', horizontalAlign: HorizontalAlign.Right); - - // Now in the above code wrap the optional arguments with CellStyle() and pass it to optional cellStyle parameter. - // So the resulting code will look like - - excel.updateCell('SheetName', CellIndex.indexByString('A2'), 'Here value', cellStyle: CellStyle( backgroundColorHex: '#1AFF1A', horizontalAlign: HorizontalAlign.Right ) ); - -``` - -### Imports - -```dart - import 'dart:io'; - import 'package:path/path.dart'; - import 'package:excel/excel.dart'; - -``` - -## Password protected ? [protect](https://github.com/justkawal/protect.git) - -`Protect helps you to apply and remove password protection on your excel file.` +## If you find this tool useful, please drop a ⭐️ + + + Platform + + + Test + + + Pub Package + + + License: MIT + + + Issues + + + Forks + + + Stars + + +### [Excel](https://www.pub.dev/packages/excel) is a flutter and dart library for reading, creating and updating excel-sheets for XLSX files + +#### This library is [MIT](https://github.com/justkawal/excel/blob/40b8b1ed8c3c213d8911784ddd40bf97841977a1/LICENSE#L1) licensed So, it's free to use anytime, anywhere without any consent, because we believe in Open Source work + +Is your excel file password protected? [Protect](https://github.com/justkawal/protect.git) helps you to apply and remove password protection on your excel file. + +## Breaking changes from 2.x.x to 3.x.x + +- Renamed `getColAutoFits()` to `getColumnAutoFits()`, and changed return type to `Map` in `Sheet` +- Renamed `getColWidths()` to `getColumnWidths()`, and changed return type to `Map` in `Sheet` +- Renamed `getColAutoFit()` to `getColumnAutoFit()` in `Sheet` +- Renamed `getColWidth()` to `getColumnWidth()` in `Sheet` +- Renamed `setColAutoFit()` to `setColumnAutoFit()` in `Sheet` +- Renamed `setColWidth()` to `setColumnWidth()` in `Sheet` + +## Usage ### Read XLSX File ```dart - var file = 'Path_to_pre_existing_Excel_File/excel_file.xlsx'; - var bytes = File(file).readAsBytesSync(); - var excel = Excel.decodeBytes(bytes); - - for (var table in excel.tables.keys) { - print(table); //sheet Name - print(excel.tables[table].maxCols); - print(excel.tables[table].maxRows); - for (var row in excel.tables[table].rows) { - print('$row'); - } - } - +var file = 'Path_to_pre_existing_Excel_File/excel_file.xlsx'; +var bytes = File(file).readAsBytesSync(); +var excel = Excel.decodeBytes(bytes); +for (var table in excel.tables.keys) { + print(table); //sheet Name + print(excel.tables[table].maxColumns); + print(excel.tables[table].maxRows); + for (var row in excel.tables[table].rows) { + print('$row'); + } +} ``` -### Is your excel file password protected ? - -`Protect helps you to apply and remove password protection on your excel file.` [protect](https://github.com/justkawal/protect.git) - ### Read XLSX in Flutter Web Use `FilePicker` to pick files in Flutter Web. [FilePicker](https://pub.dev/packages/file_picker.git) ```dart - - /// Use FilePicker to pick files in Flutter Web - - FilePickerResult pickedFile = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['xlsx'], - allowMultiple: false, - ); - - /// file might be picked - - if (pickedFile != null) { - var bytes = pickedFile.files.single.bytes; - var excel = Excel.decodeBytes(bytes); - for (var table in excel.tables.keys) { - print(table); //sheet Name - print(excel.tables[table].maxCols); - print(excel.tables[table].maxRows); - for (var row in excel.tables[table].rows) { - print('$row'); - } +/// Use FilePicker to pick files in Flutter Web + +FilePickerResult pickedFile = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['xlsx'], + allowMultiple: false, +); + +/// file might be picked + +if (pickedFile != null) { + var bytes = pickedFile.files.single.bytes; + var excel = Excel.decodeBytes(bytes); + for (var table in excel.tables.keys) { + print(table); //sheet Name + print(excel.tables[table].maxColumns); + print(excel.tables[table].maxRows); + for (var row in excel.tables[table].rows) { + print('$row'); } } +} ``` ### Read XLSX from Flutter's Asset Folder ```dart - import 'package:flutter/services.dart' show ByteData, rootBundle; - - /* Your ......other important..... code here */ +import 'package:flutter/services.dart' show ByteData, rootBundle; - ByteData data = await rootBundle.load('assets/existing_excel_file.xlsx'); - var bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); - var excel = Excel.decodeBytes(bytes); +/* Your ......other important..... code here */ - for (var table in excel.tables.keys) { - print(table); //sheet Name - print(excel.tables[table].maxCols); - print(excel.tables[table].maxRows); - for (var row in excel.tables[table].rows) { - print('$row'); - } - } +ByteData data = await rootBundle.load('assets/existing_excel_file.xlsx'); +var bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); +var excel = Excel.decodeBytes(bytes); +for (var table in excel.tables.keys) { + print(table); //sheet Name + print(excel.tables[table].maxColumns); + print(excel.tables[table].maxRows); + for (var row in excel.tables[table].rows) { + print('$row'); + } +} ``` ### Create New XLSX File ```dart - var excel = Excel.createExcel(); // automatically creates 1 empty sheet: Sheet1 - +// automatically creates 1 empty sheet: Sheet1 +var excel = Excel.createExcel(); ``` ### Update Cell values ```dart - /* - * sheetObject.updateCell(cell, value, { CellStyle (Optional)}); - * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; - * cell can be identified with Cell Address or by 2D array having row and column Index; - * Cell Style options are optional - */ - - Sheet sheetObject = excel['SheetName']; - - CellStyle cellStyle = CellStyle(backgroundColorHex: '#1AFF1A', fontFamily : getFontFamily(FontFamily.Calibri)); +/* + * sheetObject.updateCell(cell, value, { CellStyle (Optional)}); + * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; + * cell can be identified with Cell Address or by 2D array having row and column Index; + * Cell Style options are optional + */ - cellStyle.underline = Underline.Single; // or Underline.Double +Sheet sheetObject = excel['SheetName']; +CellStyle cellStyle = CellStyle(backgroundColorHex: '#1AFF1A', fontFamily :getFontFamily(FontFamily.Calibri)); - var cell = sheetObject.cell(CellIndex.indexByString('A1')); - cell.value = 8; // dynamic values support provided; - cell.cellStyle = cellStyle; +cellStyle.underline = Underline.Single; // or Underline.Double - // printing cell-type - print('CellType: '+ cell.cellType.toString()); - /// - /// Inserting and removing column and rows +var cell = sheetObject.cell(CellIndex.indexByString('A1')); +cell.value = 8; // dynamic values support provided; +cell.cellStyle = cellStyle; - // insert column at index = 8 - sheetObject.insertColumn(8); +// printing cell-type +print('CellType: '+ cell.cellType.toString()); - // remove column at index = 18 - sheetObject.removeColumn(18); +/// +/// Inserting and removing column and rows - // insert row at index = 82 - sheetObject.insertRow(82); +// insert column at index = 8 +sheetObject.insertColumn(8); - // remove row at index = 80 - sheetObject.removeRow(80); +// remove column at index = 18 +sheetObject.removeColumn(18); +// insert row at index = 82 +sheetObject.insertRow(82); +// remove row at index = 80 +sheetObject.removeRow(80); ``` ### Cell-Style Options @@ -257,203 +193,185 @@ Each border must be a `Border` object. This object accepts two parameters : `bor supported styles and `borderColorHex` to change the border color. The `borderStyle` must be a value from the enumeration`BorderStyle`: -* `BorderStyle.None` -* `BorderStyle.DashDot` -* `BorderStyle.DashDotDot` -* `BorderStyle.Dashed` -* `BorderStyle.Dotted` -* `BorderStyle.Double` -* `BorderStyle.Hair` -* `BorderStyle.Medium` -* `BorderStyle.MediumDashDot` -* `BorderStyle.MediumDashDotDot` -* `BorderStyle.MediumDashed` -* `BorderStyle.SlantDashDot` -* `BorderStyle.Thick` -* `BorderStyle.Thin` +- `BorderStyle.None` +- `BorderStyle.DashDot` +- `BorderStyle.DashDotDot` +- `BorderStyle.Dashed` +- `BorderStyle.Dotted` +- `BorderStyle.Double` +- `BorderStyle.Hair` +- `BorderStyle.Medium` +- `BorderStyle.MediumDashDot` +- `BorderStyle.MediumDashDotDot` +- `BorderStyle.MediumDashed` +- `BorderStyle.SlantDashDot` +- `BorderStyle.Thick` +- `BorderStyle.Thin` ```dart - /* - * - * Defines thin borders on the left and right of the cell, red thin border on the top - * and blue medium border on the bottom. - * - */ - - CellStyle cellStyle = CellStyle( - leftBorder: Border(borderStyle: BorderStyle.Thin), - rightBorder: Border(borderStyle: BorderStyle.Thin), - topBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: 'FFFF0000'), - bottomBorder: Border(borderStyle: BorderStyle.Medium, borderColorHex: 'FF0000FF'), - ); +/* + * + * Defines thin borders on the left and right of the cell, red thin border on the top + * and blue medium border on the bottom. + * + */ + +CellStyle cellStyle = CellStyle( + leftBorder: Border(borderStyle: BorderStyle.Thin), + rightBorder: Border(borderStyle: BorderStyle.Thin), + topBorder: Border(borderStyle: BorderStyle.Thin, borderColorHex: 'FFFF0000'), + bottomBorder: Border(borderStyle: BorderStyle.Medium, borderColorHex: 'FF0000FF'), +); ``` - ### Make sheet RTL ```dart - - /* - * set rtl to true for making sheet to right-to-left - * default value of rtl = false ( which means the fresh or default sheet is ltr ) - * - */ - - var sheetObject = excel['SheetName']; - sheetObject.rtl = true; - +/* + * set rtl to true for making sheet to right-to-left + * default value of rtl = false ( which means the fresh or default sheet is ltr ) + * + */ + +var sheetObject = excel['SheetName']; +sheetObject.rtl = true; ``` ### Copy sheet contents to another sheet ```dart - - /* - * excel.copy(String 'existingSheetName', String 'anotherSheetName'); - * existingSheetName should exist in excel.tables.keys in order to successfully copy - * if anotherSheetName does not exist then it will be automatically created. - * - */ - - excel.copy('existingSheetName', 'anotherSheetName'); - +/* + * excel.copy(String 'existingSheetName', String 'anotherSheetName'); + * existingSheetName should exist in excel.tables.keys in order to successfully copy + * if anotherSheetName does not exist then it will be automatically created. + * + */ + +excel.copy('existingSheetName', 'anotherSheetName'); ``` ### Rename sheet ```dart +/* + * excel.rename(String 'existingSheetName', String 'newSheetName'); + * existingSheetName should exist in excel.tables.keys in order to successfully rename + * + */ - /* - * excel.rename(String 'existingSheetName', String 'newSheetName'); - * existingSheetName should exist in excel.tables.keys in order to successfully rename - * - */ - - excel.rename('existingSheetName', 'newSheetName'); - +excel.rename('existingSheetName', 'newSheetName'); ``` ### Delete sheet ```dart +/* + * excel.delete(String 'existingSheetName'); + * (existingSheetName should exist in excel.tables.keys) and (excel.tables.keys.length >= 2), in order to successfully delete. + * + */ - /* - * excel.delete(String 'existingSheetName'); - * (existingSheetName should exist in excel.tables.keys) and (excel.tables.keys.length >= 2), in order to successfully delete. - * - */ - - excel.delete('existingSheetName'); - +excel.delete('existingSheetName'); ``` ### Link sheet ```dart - - /* - * excel.link(String 'sheetName', Sheet sheetObject); - * - * Any operations performed on (object of 'sheetName') or sheetObject then the operation is performed on both. - * if 'sheetName' does not exist then it will be automatically created and linked with the sheetObject's operation. - * - */ - - excel.link('sheetName', sheetObject); - +/* + * excel.link(String 'sheetName', Sheet sheetObject); + * + * Any operations performed on (object of 'sheetName') or sheetObject then the operation is performed on both. + * if 'sheetName' does not exist then it will be automatically created and linked with the sheetObject's operation. + * + */ + +excel.link('sheetName', sheetObject); ``` ### Un-Link sheet ```dart +/* + * excel.unLink(String 'sheetName'); + * In order to successfully unLink the 'sheetName' then it must exist in excel.tables.keys + * + */ - /* - * excel.unLink(String 'sheetName'); - * In order to successfully unLink the 'sheetName' then it must exist in excel.tables.keys - * - */ +excel.unLink('sheetName'); - excel.unLink('sheetName'); - - // After calling the above function be sure to re-make a new reference of this. - - Sheet unlinked_sheetObject = excel['sheetName']; +// After calling the above function be sure to re-make a new reference of this. +Sheet unlinked_sheetObject = excel['sheetName']; ``` ### Merge Cells ```dart - /* - * sheetObject.merge(CellIndex starting_cell, CellIndex ending_cell, dynamic 'customValue'); - * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; - * starting_cell and ending_cell can be identified with Cell Address or by 2D array having row and column Index; - * customValue is optional - */ - - sheetObject.merge(CellIndex.indexByString('A1'), CellIndex.indexByString('E4'), customValue: 'Put this text after merge'); - +/* + * sheetObject.merge(CellIndex starting_cell, CellIndex ending_cell, dynamic 'customValue'); + * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; + * starting_cell and ending_cell can be identified with Cell Address or by 2D array having row and column Index; + * customValue is optional + */ + +sheetObject.merge(CellIndex.indexByString('A1'), CellIndex.indexByString('E4'), customValue: 'Put this text after merge'); ``` ### Get Merged Cells List ```dart - // Check which cells are merged - - sheetObject.spannedItems.forEach((cells) { - print('Merged:' + cells.toString()); - }); +// Check which cells are merged +sheetObject.spannedItems.forEach((cells) { + print('Merged:' + cells.toString()); +}); ``` ### Un-Merge Cells ```dart - /* - * sheetObject.unMerge(cell); - * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; - * cell should be identified with string only with an example as 'A1:E4'. - * to check if 'A1:E4' is un-merged or not - * call the method excel.getMergedCells(sheet); and verify that it is not present in it. - */ - - sheetObject.unMerge('A1:E4'); - +/* + * sheetObject.unMerge(cell); + * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; + * cell should be identified with string only with an example as 'A1:E4'. + * to check if 'A1:E4' is un-merged or not + * call the method excel.getMergedCells(sheet); and verify that it is not present in it. + */ + +sheetObject.unMerge('A1:E4'); ``` ### Find and Replace ```dart - /* - * int replacedCount = sheetObject.findAndReplace(source, target); - * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; - * source is the string or ( User's Custom Pattern Matching RegExp ) - * target is the string which is put in cells in place of source - * - * it returns the number of replacements made - */ - - int replacedCount = sheetObject.findAndReplace('Flutter', 'Google'); - +/* + * int replacedCount = sheetObject.findAndReplace(source, target); + * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; + * source is the string or ( User's Custom Pattern Matching RegExp ) + * target is the string which is put in cells in place of source + * + * it returns the number of replacements made + */ + +int replacedCount = sheetObject.findAndReplace('Flutter', 'Google'); ``` ### Insert Row Iterables ```dart - /* - * sheetObject.insertRowIterables(list-iterables, rowIndex, iterable-options?); - * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; - * list-iterables === list of iterables which has to be put in specific row - * rowIndex === the row in which the iterables has to be put - * Iterable options are optional - */ - - /// It will put the list-iterables in the 8th index row - List dataList = ['Google', 'loves', 'Flutter', 'and', 'Flutter', 'loves', 'Excel']; - - sheetObject.insertRowIterables(dataList, 8); - +/* + * sheetObject.insertRowIterables(list-iterables, rowIndex, iterable-options?); + * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; + * list-iterables === list of iterables which has to be put in specific row + * rowIndex === the row in which the iterables has to be put + * Iterable options are optional + */ + +/// It will put the list-iterables in the 8th index row +List dataList = ['Google', 'loves', 'Flutter', 'and', 'Flutter', 'loves', 'Excel']; + +sheetObject.insertRowIterables(dataList, 8); ``` ### Iterable Options @@ -466,46 +384,43 @@ The `borderStyle` must be a value from the enumeration`BorderStyle`: ### Append Row ```dart - /* - * sheetObject.appendRow(list-iterables); - * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; - * list-iterables === list of iterables - */ - - sheetObject.appendRow(['Flutter', 'till', 'Eternity']); +/* + * sheetObject.appendRow(list-iterables); + * sheetObject created by calling - // Sheet sheetObject = excel['SheetName']; + * list-iterables === list of iterables + */ +sheetObject.appendRow(['Flutter', 'till', 'Eternity']); ``` ### Get Default Opening Sheet ```dart - /* - * method which returns the name of the default sheet - * excel.getDefaultSheet(); - */ - - var defaultSheet = excel.getDefaultSheet(); - print('Default Sheet:' + defaultSheet.toString()); +/* + * method which returns the name of the default sheet + * excel.getDefaultSheet(); + */ +var defaultSheet = excel.getDefaultSheet(); +print('Default Sheet:' + defaultSheet.toString()); ``` ### Set Default Opening Sheet ```dart - /* - * method which sets the name of the default sheet - * returns bool if successful then true else false - * excel.setDefaultSheet(sheet); - * sheet = 'SheetName' - */ - - var isSet = excel.setDefaultSheet(sheet); - if (isSet) { - print('$sheet is set to default sheet.'); - } else { - print('Unable to set $sheet to default sheet.'); - } - +/* + * method which sets the name of the default sheet + * returns bool if successful then true else false + * excel.setDefaultSheet(sheet); + * sheet = 'SheetName' + */ + +var isSet = excel.setDefaultSheet(sheet); +if (isSet) { + print('$sheet is set to default sheet.'); +} else { + print('Unable to set $sheet to default sheet.'); +} ``` ## Saving @@ -513,11 +428,10 @@ The `borderStyle` must be a value from the enumeration`BorderStyle`: ### On Flutter Web ```dart - // when you are in flutter web then save() downloads the excel file. - - // Call function save() to download the file - var fileBytes = excel.save(fileName: 'My_Excel_File_Name.xlsx'); +// when you are in flutter web then save() downloads the excel file. +// Call function save() to download the file +var fileBytes = excel.save(fileName: 'My_Excel_File_Name.xlsx'); ``` ### On Android / iOS @@ -525,17 +439,10 @@ The `borderStyle` must be a value from the enumeration`BorderStyle`: For getting saving directory on Android or iOS, Use: [path_provider](https://pub.dev/packages/path_provider) ```dart - var fileBytes = excel.save(); - var directory = await getApplicationDocumentsDirectory(); +var fileBytes = excel.save(); +var directory = await getApplicationDocumentsDirectory(); - File(join('$directory/output_file_name.xlsx')) - ..createSync(recursive: true) - ..writeAsBytesSync(fileBytes); +File(join('$directory/output_file_name.xlsx')) + ..createSync(recursive: true) + ..writeAsBytesSync(fileBytes); ``` - -## Features coming in next version - -On-going implementation for future: - -- Formulas -- Conversion to PDF diff --git a/example/excel_border.dart b/example/excel_border.dart new file mode 100644 index 00000000..ebb6c2d4 --- /dev/null +++ b/example/excel_border.dart @@ -0,0 +1,75 @@ +import 'dart:io'; + +import '../lib/excel.dart'; + +void main(List args) { + var excel = Excel.createExcel(); + final Sheet sheet = excel[excel.getDefaultSheet()!]; + + sheet.merge(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: 1), + CellIndex.indexByColumnRow(columnIndex: 10, rowIndex: 5)); + + sheet.merge(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: 10), + CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: 10)); + + Border border = Border( + borderColorHex: "#FF000000", + borderStyle: BorderStyle.Thin, + ); + + sheet.updateCell( + CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: 1), + "Merged cell border", + ); + + sheet.setMergedCellStyle( + CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: 1), + CellStyle( + fontSize: 25, + topBorder: border, + bottomBorder: border, + leftBorder: border, + rightBorder: border, + diagonalBorder: border, + diagonalBorderDown: true, + ), + ); + + sheet.setMergedCellStyle( + CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: 10), + CellStyle( + topBorder: border, + bottomBorder: border, + leftBorder: border, + rightBorder: border, + diagonalBorder: border, + diagonalBorderDown: true, + diagonalBorderUp: true, + ), + ); + + sheet.updateCell( + CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 1), + "Normal border", + cellStyle: CellStyle( + fontSize: 25, + topBorder: border, + bottomBorder: border, + leftBorder: border, + rightBorder: border, + diagonalBorder: border, + ), + ); + + sheet.setColumnWidth(0, 50); + + // Create the example excel file in the current directory + String outputFile = "excel_custom.xlsx"; + + List? fileBytes = excel.save(); + if (fileBytes != null) { + File(outputFile) + ..createSync(recursive: true) + ..writeAsBytesSync(fileBytes); + } +} diff --git a/example/excel_custom_width.dart b/example/excel_custom_size.dart similarity index 78% rename from example/excel_custom_width.dart rename to example/excel_custom_size.dart index 42a7076c..1fe3de9a 100644 --- a/example/excel_custom_width.dart +++ b/example/excel_custom_size.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; -import 'package:path/path.dart'; import '../lib/excel.dart'; @@ -35,22 +34,28 @@ void main(List args) { .value = getRandString(); } - sheet.setColWidth(0, 10.0); - sheet.setColWidth(1, 10.0); - sheet.setColAutoFit(0); - sheet.setColAutoFit(1); - sheet.setColAutoFit(2); - sheet.setColWidth(50, 10.0); + sheet.setDefaultColumnWidth(); + sheet.setDefaultRowHeight(); + + sheet.setColumnAutoFit(0); + sheet.setColumnAutoFit(1); + sheet.setColumnAutoFit(2); + + sheet.setColumnWidth(0, 10.0); + sheet.setColumnWidth(1, 10.0); + sheet.setColumnWidth(50, 10.0); + + sheet.setRowHeight(1, 100); sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0), CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: 10)); - String outputFile = - "/Users/igdmitrov/Downloads/excel_custom-${DateTime.now().toIso8601String()}.xlsx"; + // Create the example excel file in the current directory + String outputFile = "excel_custom.xlsx"; List? fileBytes = excel.save(); if (fileBytes != null) { - File(join(outputFile)) + File(outputFile) ..createSync(recursive: true) ..writeAsBytesSync(fileBytes); } diff --git a/example/excel_example.dart b/example/excel_example.dart index 072b0152..aa2e76ea 100644 --- a/example/excel_example.dart +++ b/example/excel_example.dart @@ -16,7 +16,7 @@ void main(List args) { /// for (var table in excel.tables.keys) { print(table); - print(excel.tables[table]!.maxCols); + print(excel.tables[table]!.maxColumns); print(excel.tables[table]!.maxRows); for (var row in excel.tables[table]!.rows) { print("${row.map((e) => e?.value)}"); @@ -118,17 +118,17 @@ void main(List args) { print("Unable to set ${sheet.sheetName} to default sheet."); } - var colIterableSheet = excel['ColumnIterables']; + var columnIterableSheet = excel['ColumnIterables']; - var colIterables = ['A', 'B', 'C', 'D', 'E']; - int colIndex = 0; + var columnIterables = ['A', 'B', 'C', 'D', 'E']; + int columnIndex = 0; - colIterables.forEach((colValue) { - colIterableSheet.cell(CellIndex.indexByColumnRow( - rowIndex: colIterableSheet.maxRows, - columnIndex: colIndex, + columnIterables.forEach((columnValue) { + columnIterableSheet.cell(CellIndex.indexByColumnRow( + rowIndex: columnIterableSheet.maxRows, + columnIndex: columnIndex, )) - ..value = colValue; + ..value = columnValue; }); // Saving the file diff --git a/example/excel_time_consuming.dart b/example/excel_time_consuming.dart index 6f04178c..63dacb4f 100644 --- a/example/excel_time_consuming.dart +++ b/example/excel_time_consuming.dart @@ -9,14 +9,14 @@ void main(List args) { Sheet sh = excel['Sheet1']; for (int i = 0; i < 8; i++) { sh.cell(CellIndex.indexByColumnRow(rowIndex: 0, columnIndex: i)).value = - 'Col $i'; + 'Column $i'; //sh.cell(CellIndex.indexByColumnRow(rowIndex: 0, columnIndex: i)).cellStyle =CellStyle(bold: true); } for (int row = 1; row < 9000; row++) { - for (int col = 0; col < 80; col++) { + for (int column = 0; column < 80; column++) { sh - .cell(CellIndex.indexByColumnRow(rowIndex: row, columnIndex: col)) - .value = '$row$col value'; + .cell(CellIndex.indexByColumnRow(rowIndex: row, columnIndex: column)) + .value = '$row$column value'; } } print('Generating executed in ${stopwatch.elapsed}'); diff --git a/lib/src/excel.dart b/lib/src/excel.dart index cd204adf..b30fa931 100644 --- a/lib/src/excel.dart +++ b/lib/src/excel.dart @@ -16,13 +16,14 @@ Excel _newExcel(Archive archive) { case _spreasheetXlsx: return Excel._(archive); default: - throw UnsupportedError('Excel format unsupported.'); + throw UnsupportedError( + 'Excel format unsupported. Only .xlsx files are supported'); } } /// Decode a excel file. class Excel { - late bool _colorChanges; + late bool _styleChanges; late bool _mergeChanges; late bool _rtlChanges; @@ -46,12 +47,18 @@ class Excel { late String _stylesTarget; late String _sharedStringsTarget; + String get _absSharedStringsTarget { + if (_sharedStringsTarget.isNotEmpty && _sharedStringsTarget[0] == "/") { + return _sharedStringsTarget.substring(1); + } + return "xl/${_sharedStringsTarget}"; + } String? _defaultSheet; late Parser parser; Excel._(Archive archive) { - _colorChanges = false; + _styleChanges = false; _mergeChanges = false; _rtlChanges = false; _sheets = {}; @@ -82,7 +89,12 @@ class Excel { } factory Excel.decodeBytes(List data) { - return _newExcel(ZipDecoder().decodeBytes(data)); + try { + return _newExcel(ZipDecoder().decodeBytes(data)); + } catch (e) { + throw UnsupportedError( + 'Excel format unsupported. Only .xlsx files are supported'); + } } factory Excel.decodeBuffer(InputStream input) { @@ -531,7 +543,7 @@ class Excel { _availSheet(sheet); if (cellStyle != null) { - _colorChanges = true; + _styleChanges = true; _sheetMap[sheet]!.updateCell(cellIndex, value, cellStyle: cellStyle); } else { _sheetMap[sheet]!.updateCell(cellIndex, value); diff --git a/lib/src/parser/parse.dart b/lib/src/parser/parse.dart index c075d7e6..5dcea0cf 100644 --- a/lib/src/parser/parse.dart +++ b/lib/src/parser/parse.dart @@ -20,10 +20,10 @@ class Parser { } _normalizeTable(Sheet sheet) { - if (sheet._maxRows == 0 || sheet._maxCols == 0) { + if (sheet._maxRows == 0 || sheet._maxColumns == 0) { sheet._sheetData.clear(); } - sheet._countRowAndCol(); + sheet._countRowsAndColumns(); } _putContentXml() { @@ -71,7 +71,7 @@ class Parser { _parseSharedStrings() { var sharedStrings = - _excel._archive.findFile('xl/${_excel._sharedStringsTarget}'); + _excel._archive.findFile(_excel._absSharedStringsTarget); if (sharedStrings == null) { _excel._sharedStringsTarget = 'sharedStrings.xml'; @@ -127,10 +127,9 @@ class Parser { var content = utf8.encode( ""); - _excel._archive.addFile(ArchiveFile( - 'xl/${_excel._sharedStringsTarget}', content.length, content)); - sharedStrings = - _excel._archive.findFile('xl/${_excel._sharedStringsTarget}'); + _excel._archive.addFile( + ArchiveFile("xl/sharedStrings.xml", content.length, content)); + sharedStrings = _excel._archive.findFile("xl/sharedStrings.xml"); } sharedStrings!.decompress(); var document = XmlDocument.parse(utf8.decode(sharedStrings.content)); @@ -188,11 +187,12 @@ class Parser { } spannedCells[sheetName] = spanList; - List startIndex = _cellCoordsFromCellId(startCell), - endIndex = _cellCoordsFromCellId(endCell); - _Span spanObj = _Span(); - spanObj._start = [startIndex[0], startIndex[1]]; - spanObj._end = [endIndex[0], endIndex[1]]; + CellIndex startIndex = CellIndex.indexByString(startCell), + endIndex = CellIndex.indexByString(endCell); + _Span spanObj = _Span.fromCellIndex( + start: startIndex, + end: endIndex, + ); if (!_excel._sheetMap[sheetName]!._spanList.contains(spanObj)) { _excel._sheetMap[sheetName]!._spanList.add(spanObj); } @@ -204,10 +204,10 @@ class Parser { // Remove those cells which are present inside the _excel._sheetMap.forEach((sheetName, sheetObject) { if (spannedCells.containsKey(sheetName)) { - sheetObject._sheetData.forEach((row, colMap) { - colMap.forEach((col, dataObject) { - if (!(spannedCells[sheetName].contains(getCellId(col, row)))) { - _excel[sheetName]._sheetData[row]?.remove(col); + sheetObject._sheetData.forEach((row, columnMap) { + columnMap.forEach((column, dataObject) { + if (!(spannedCells[sheetName].contains(getCellId(column, row)))) { + _excel[sheetName]._sheetData[row]?.remove(column); } }); }); @@ -522,8 +522,8 @@ class Parser { } _parseCell(XmlElement node, Sheet sheetObject, int rowIndex, String name) { - int? colIndex = _getCellNumber(node); - if (colIndex == null) { + int? columnIndex = _getCellNumber(node); + if (columnIndex == null) { return; } @@ -611,7 +611,8 @@ class Parser { } } sheetObject.updateCell( - CellIndex.indexByColumnRow(columnIndex: colIndex, rowIndex: rowIndex), + CellIndex.indexByColumnRow( + columnIndex: columnIndex, rowIndex: rowIndex), value, cellStyle: _excel._cellStyleList[s]); } diff --git a/lib/src/save/save_file.dart b/lib/src/save/save_file.dart index 1de601dd..091e1ffa 100644 --- a/lib/src/save/save_file.dart +++ b/lib/src/save/save_file.dart @@ -10,22 +10,21 @@ class Save { _innerCellStyle = []; } - void _addNewCol(XmlElement cols, int min, int max, double value) { - cols.children.add(XmlElement(XmlName('col'), [ + void _addNewColumn(XmlElement columns, int min, int max, double width) { + columns.children.add(XmlElement(XmlName('col'), [ XmlAttribute(XmlName('min'), (min + 1).toString()), XmlAttribute(XmlName('max'), (max + 1).toString()), - XmlAttribute(XmlName('width'), value.toStringAsFixed(2)), + XmlAttribute(XmlName('width'), width.toStringAsFixed(2)), XmlAttribute(XmlName('bestFit'), "1"), XmlAttribute(XmlName('customWidth'), "1"), ], [])); } - double _calcAutoFitColWidth(Sheet sheet, int col) { + double _calcAutoFitColumnWidth(Sheet sheet, int column) { var maxNumOfCharacters = 0; sheet._sheetData.forEach((key, value) { - if (value.containsKey(col) && value[col]!._isFormula == false) { - maxNumOfCharacters = - max(value[col]!.value.toString().length, maxNumOfCharacters); + if (value.containsKey(column) && value[column]!._isFormula == false) { + maxNumOfCharacters = max(value[column]!.value.toString().length, maxNumOfCharacters); } }); @@ -42,8 +41,7 @@ class Save { } else { var content = file.content as Uint8List; var compress = !_noCompression.contains(file.name); - copy = ArchiveFile(file.name, content.length, content) - ..compress = compress; + copy = ArchiveFile(file.name, content.length, content)..compress = compress; } clone.addFile(copy); } @@ -62,8 +60,7 @@ class Save { } */ // Manage value's type - XmlElement _createCell( - String sheet, int columnIndex, int rowIndex, dynamic value) { + XmlElement _createCell(String sheet, int columnIndex, int rowIndex, dynamic value) { if (value is SharedString) { _excel._sharedStrings.add(value); } @@ -75,14 +72,11 @@ class Save { if (value is SharedString) XmlAttribute(XmlName('t'), 's'), ]; - if (_excel._colorChanges && + if (_excel._styleChanges && (_excel._sheetMap[sheet]?._sheetData != null) && _excel._sheetMap[sheet]!._sheetData[rowIndex] != null && - _excel._sheetMap[sheet]!._sheetData[rowIndex]![columnIndex] - ?.cellStyle != - null) { - CellStyle cellStyle = _excel - ._sheetMap[sheet]!._sheetData[rowIndex]![columnIndex]!.cellStyle!; + _excel._sheetMap[sheet]!._sheetData[rowIndex]![columnIndex]?.cellStyle != null) { + CellStyle cellStyle = _excel._sheetMap[sheet]!._sheetData[rowIndex]![columnIndex]!.cellStyle!; int upperLevelPos = _checkPosition(_excel._cellStyleList, cellStyle); if (upperLevelPos == -1) { int lowerLevelPos = _checkPosition(_innerCellStyle, cellStyle); @@ -96,20 +90,17 @@ class Save { 1, XmlAttribute(XmlName('s'), '$upperLevelPos'), ); - } else if (_excel._cellStyleReferenced.containsKey(sheet) && - _excel._cellStyleReferenced[sheet]!.containsKey(rC)) { + } else if (_excel._cellStyleReferenced.containsKey(sheet) && _excel._cellStyleReferenced[sheet]!.containsKey(rC)) { attributes.insert( 1, - XmlAttribute( - XmlName('s'), '${_excel._cellStyleReferenced[sheet]![rC]}'), + XmlAttribute(XmlName('s'), '${_excel._cellStyleReferenced[sheet]![rC]}'), ); } var children = value == null ? [] : [ - if (value is Formula) - XmlElement(XmlName('f'), [], [XmlText(value.formula.toString())]), + if (value is Formula) XmlElement(XmlName('f'), [], [XmlText(value.formula.toString())]), XmlElement(XmlName('v'), [], [ XmlText(value is SharedString ? _excel._sharedStrings.indexOf(value).toString() @@ -121,10 +112,13 @@ class Save { return XmlElement(XmlName('c'), attributes, children); } - /// - XmlElement _createNewRow(XmlElement table, int rowIndex) { - var row = XmlElement(XmlName('row'), - [XmlAttribute(XmlName('r'), (rowIndex + 1).toString())], []); + /// Create a new row in the sheet. + XmlElement _createNewRow(XmlElement table, int rowIndex, double? height) { + var row = XmlElement(XmlName('row'), [ + XmlAttribute(XmlName('r'), (rowIndex + 1).toString()), + if (height != null) XmlAttribute(XmlName('ht'), height.toStringAsFixed(2)), + if (height != null) XmlAttribute(XmlName('customHeight'), '1'), + ], []); table.children.add(row); return row; } @@ -138,8 +132,8 @@ class Save { List<_BorderSet> innerBorderSet = <_BorderSet>[]; _excel._sheetMap.forEach((sheetName, sheetObject) { - sheetObject._sheetData.forEach((_, colMap) { - colMap.forEach((_, dataObject) { + sheetObject._sheetData.forEach((_, columnMap) { + columnMap.forEach((_, dataObject) { if (dataObject.cellStyle != null) { int pos = _checkPosition(_innerCellStyle, dataObject.cellStyle!); if (pos == -1) { @@ -160,46 +154,36 @@ class Save { fontFamily: cellStyle.fontFamily); /// If `-1` is returned then it indicates that `_fontStyle` is not present in the `_fs` - if (_fontStyleIndex(_excel._fontStyleList, _fs) == -1 && - _fontStyleIndex(innerFontStyle, _fs) == -1) { + if (_fontStyleIndex(_excel._fontStyleList, _fs) == -1 && _fontStyleIndex(innerFontStyle, _fs) == -1) { innerFontStyle.add(_fs); } /// Filling the inner usable extra list of background color String backgroundColor = cellStyle.backgroundColor; - if (!_excel._patternFill.contains(backgroundColor) && - !innerPatternFill.contains(backgroundColor)) { + if (!_excel._patternFill.contains(backgroundColor) && !innerPatternFill.contains(backgroundColor)) { innerPatternFill.add(backgroundColor); } final _bs = _createBorderSetFromCellStyle(cellStyle); - if (!_excel._borderSetList.contains(_bs) && - !innerBorderSet.contains(_bs)) { + if (!_excel._borderSetList.contains(_bs) && !innerBorderSet.contains(_bs)) { innerBorderSet.add(_bs); } }); - XmlElement fonts = - _excel._xmlFiles['xl/styles.xml']!.findAllElements('fonts').first; + XmlElement fonts = _excel._xmlFiles['xl/styles.xml']!.findAllElements('fonts').first; var fontAttribute = fonts.getAttributeNode('count'); if (fontAttribute != null) { - fontAttribute.value = - '${_excel._fontStyleList.length + innerFontStyle.length}'; + fontAttribute.value = '${_excel._fontStyleList.length + innerFontStyle.length}'; } else { - fonts.attributes.add(XmlAttribute(XmlName('count'), - '${_excel._fontStyleList.length + innerFontStyle.length}')); + fonts.attributes.add(XmlAttribute(XmlName('count'), '${_excel._fontStyleList.length + innerFontStyle.length}')); } innerFontStyle.forEach((fontStyleElement) { fonts.children.add(XmlElement(XmlName('font'), [], [ /// putting color - if (fontStyleElement._fontColorHex != null && - fontStyleElement._fontColorHex != "FF000000") - XmlElement( - XmlName('color'), - [XmlAttribute(XmlName('rgb'), fontStyleElement._fontColorHex!)], - []), + if (fontStyleElement._fontColorHex != null && fontStyleElement._fontColorHex != "FF000000") + XmlElement(XmlName('color'), [XmlAttribute(XmlName('rgb'), fontStyleElement._fontColorHex!)], []), /// putting bold if (fontStyleElement.isBold) XmlElement(XmlName('b'), [], []), @@ -208,46 +192,35 @@ class Save { if (fontStyleElement.isItalic) XmlElement(XmlName('i'), [], []), /// putting single underline - if (fontStyleElement.underline != Underline.None && - fontStyleElement.underline == Underline.Single) - XmlElement(XmlName('u'), [], []), + if (fontStyleElement.underline != Underline.None && fontStyleElement.underline == Underline.Single) XmlElement(XmlName('u'), [], []), /// putting double underline if (fontStyleElement.underline != Underline.None && fontStyleElement.underline != Underline.Single && fontStyleElement.underline == Underline.Double) - XmlElement( - XmlName('u'), [XmlAttribute(XmlName('val'), 'double')], []), + XmlElement(XmlName('u'), [XmlAttribute(XmlName('val'), 'double')], []), /// putting fontFamily if (fontStyleElement.fontFamily != null && fontStyleElement.fontFamily!.toLowerCase().toString() != 'null' && fontStyleElement.fontFamily != '' && fontStyleElement.fontFamily!.isNotEmpty) - XmlElement(XmlName('name'), [ - XmlAttribute(XmlName('val'), fontStyleElement.fontFamily.toString()) - ], []), + XmlElement(XmlName('name'), [XmlAttribute(XmlName('val'), fontStyleElement.fontFamily.toString())], []), /// putting fontSize - if (fontStyleElement.fontSize != null && - fontStyleElement.fontSize.toString().isNotEmpty) - XmlElement(XmlName('sz'), [ - XmlAttribute(XmlName('val'), fontStyleElement.fontSize.toString()) - ], []), + if (fontStyleElement.fontSize != null && fontStyleElement.fontSize.toString().isNotEmpty) + XmlElement(XmlName('sz'), [XmlAttribute(XmlName('val'), fontStyleElement.fontSize.toString())], []), ])); }); - XmlElement fills = - _excel._xmlFiles['xl/styles.xml']!.findAllElements('fills').first; + XmlElement fills = _excel._xmlFiles['xl/styles.xml']!.findAllElements('fills').first; var fillAttribute = fills.getAttributeNode('count'); if (fillAttribute != null) { - fillAttribute.value = - '${_excel._patternFill.length + innerPatternFill.length}'; + fillAttribute.value = '${_excel._patternFill.length + innerPatternFill.length}'; } else { - fills.attributes.add(XmlAttribute(XmlName('count'), - '${_excel._patternFill.length + innerPatternFill.length}')); + fills.attributes.add(XmlAttribute(XmlName('count'), '${_excel._patternFill.length + innerPatternFill.length}')); } innerPatternFill.forEach((color) { @@ -257,44 +230,33 @@ class Save { XmlElement(XmlName('patternFill'), [ XmlAttribute(XmlName('patternType'), 'solid') ], [ - XmlElement(XmlName('fgColor'), - [XmlAttribute(XmlName('rgb'), color)], []), - XmlElement( - XmlName('bgColor'), [XmlAttribute(XmlName('rgb'), color)], []) + XmlElement(XmlName('fgColor'), [XmlAttribute(XmlName('rgb'), color)], []), + XmlElement(XmlName('bgColor'), [XmlAttribute(XmlName('rgb'), color)], []) ]) ])); - } else if (color == "none" || - color == "gray125" || - color == "lightGray") { + } else if (color == "none" || color == "gray125" || color == "lightGray") { fills.children.add(XmlElement(XmlName('fill'), [], [ - XmlElement(XmlName('patternFill'), - [XmlAttribute(XmlName('patternType'), color)], []) + XmlElement(XmlName('patternFill'), [XmlAttribute(XmlName('patternType'), color)], []) ])); } } else { - _damagedExcel( - text: - "Corrupted Styles Found. Can't process further, Open up issue in github."); + _damagedExcel(text: "Corrupted Styles Found. Can't process further, Open up issue in github."); } }); - XmlElement borders = - _excel._xmlFiles['xl/styles.xml']!.findAllElements('borders').first; + XmlElement borders = _excel._xmlFiles['xl/styles.xml']!.findAllElements('borders').first; var borderAttribute = borders.getAttributeNode('count'); if (borderAttribute != null) { - borderAttribute.value = - '${_excel._borderSetList.length + innerBorderSet.length}'; + borderAttribute.value = '${_excel._borderSetList.length + innerBorderSet.length}'; } else { - borders.attributes.add(XmlAttribute(XmlName('count'), - '${_excel._borderSetList.length + innerBorderSet.length}')); + borders.attributes.add(XmlAttribute(XmlName('count'), '${_excel._borderSetList.length + innerBorderSet.length}')); } innerBorderSet.forEach((border) { var borderElement = XmlElement(XmlName('border')); if (border.diagonalBorderDown) { - borderElement.attributes - .add(XmlAttribute(XmlName('diagonalDown'), '1')); + borderElement.attributes.add(XmlAttribute(XmlName('diagonalDown'), '1')); } if (border.diagonalBorderUp) { borderElement.attributes.add(XmlAttribute(XmlName('diagonalUp'), '1')); @@ -316,8 +278,7 @@ class Save { } final color = borderValue.borderColorHex; if (color != null) { - element.children.add(XmlElement( - XmlName('color'), [XmlAttribute(XmlName('rgb'), color)])); + element.children.add(XmlElement(XmlName('color'), [XmlAttribute(XmlName('rgb'), color)])); } borderElement.children.add(element); } @@ -325,16 +286,13 @@ class Save { borders.children.add(borderElement); }); - XmlElement celx = - _excel._xmlFiles['xl/styles.xml']!.findAllElements('cellXfs').first; + XmlElement celx = _excel._xmlFiles['xl/styles.xml']!.findAllElements('cellXfs').first; var cellAttribute = celx.getAttributeNode('count'); if (cellAttribute != null) { - cellAttribute.value = - '${_excel._cellStyleList.length + _innerCellStyle.length}'; + cellAttribute.value = '${_excel._cellStyleList.length + _innerCellStyle.length}'; } else { - celx.attributes.add(XmlAttribute(XmlName('count'), - '${_excel._cellStyleList.length + _innerCellStyle.length}')); + celx.attributes.add(XmlAttribute(XmlName('count'), '${_excel._cellStyleList.length + _innerCellStyle.length}')); } _innerCellStyle.forEach((cellStyle) { @@ -352,50 +310,37 @@ class Save { VerticalAlign verticalAlign = cellStyle.verticalAlignment; int rotation = cellStyle.rotation; TextWrapping? textWrapping = cellStyle.wrap; - int backgroundIndex = innerPatternFill.indexOf(backgroundColor), - fontIndex = _fontStyleIndex(innerFontStyle, _fs); + int backgroundIndex = innerPatternFill.indexOf(backgroundColor), fontIndex = _fontStyleIndex(innerFontStyle, _fs); _BorderSet _bs = _createBorderSetFromCellStyle(cellStyle); int borderIndex = innerBorderSet.indexOf(_bs); var attributes = [ - XmlAttribute(XmlName('borderId'), - '${borderIndex == -1 ? 0 : borderIndex + _excel._borderSetList.length}'), - XmlAttribute(XmlName('fillId'), - '${backgroundIndex == -1 ? 0 : backgroundIndex + _excel._patternFill.length}'), - XmlAttribute(XmlName('fontId'), - '${fontIndex == -1 ? 0 : fontIndex + _excel._fontStyleList.length}'), + XmlAttribute(XmlName('borderId'), '${borderIndex == -1 ? 0 : borderIndex + _excel._borderSetList.length}'), + XmlAttribute(XmlName('fillId'), '${backgroundIndex == -1 ? 0 : backgroundIndex + _excel._patternFill.length}'), + XmlAttribute(XmlName('fontId'), '${fontIndex == -1 ? 0 : fontIndex + _excel._fontStyleList.length}'), XmlAttribute(XmlName('numFmtId'), '0'), XmlAttribute(XmlName('xfId'), '0'), ]; - if ((_excel._patternFill.contains(backgroundColor) || - innerPatternFill.contains(backgroundColor)) && + if ((_excel._patternFill.contains(backgroundColor) || innerPatternFill.contains(backgroundColor)) && backgroundColor != "none" && backgroundColor != "gray125" && backgroundColor.toLowerCase() != "lightgray") { attributes.add(XmlAttribute(XmlName('applyFill'), '1')); } - if (_fontStyleIndex(_excel._fontStyleList, _fs) != -1 && - _fontStyleIndex(innerFontStyle, _fs) != -1) { + if (_fontStyleIndex(_excel._fontStyleList, _fs) != -1 && _fontStyleIndex(innerFontStyle, _fs) != -1) { attributes.add(XmlAttribute(XmlName('applyFont'), '1')); } var children = []; - if (horizontalAlign != HorizontalAlign.Left || - textWrapping != null || - verticalAlign != VerticalAlign.Bottom || - rotation != 0) { + if (horizontalAlign != HorizontalAlign.Left || textWrapping != null || verticalAlign != VerticalAlign.Bottom || rotation != 0) { attributes.add(XmlAttribute(XmlName('applyAlignment'), '1')); var childAttributes = []; if (textWrapping != null) { - childAttributes.add(XmlAttribute( - XmlName(textWrapping == TextWrapping.Clip - ? 'shrinkToFit' - : 'wrapText'), - '1')); + childAttributes.add(XmlAttribute(XmlName(textWrapping == TextWrapping.Clip ? 'shrinkToFit' : 'wrapText'), '1')); } if (verticalAlign != VerticalAlign.Bottom) { @@ -404,13 +349,11 @@ class Save { } if (horizontalAlign != HorizontalAlign.Left) { - String hor = - horizontalAlign == HorizontalAlign.Right ? 'right' : 'center'; + String hor = horizontalAlign == HorizontalAlign.Right ? 'right' : 'center'; childAttributes.add(XmlAttribute(XmlName('horizontal'), '$hor')); } if (rotation != 0) { - childAttributes - .add(XmlAttribute(XmlName('textRotation'), '$rotation')); + childAttributes.add(XmlAttribute(XmlName('textRotation'), '$rotation')); } children.add(XmlElement(XmlName('alignment'), childAttributes, [])); @@ -421,7 +364,7 @@ class Save { } List? _save() { - if (_excel._colorChanges) { + if (_excel._styleChanges) { _processStylesFile(); } _setSheetElements(); @@ -446,28 +389,21 @@ class Save { return ZipEncoder().encode(_cloneArchive(_excel._archive)); } - _setColumnWidth(String sheetName) { - final sheetObject = _excel._sheetMap[sheetName]; - if (sheetObject == null) return; - - var xmlFile = _excel._xmlFiles[_excel._xmlSheetId[sheetName]]; - if (xmlFile == null) return; - - final colElements = xmlFile.findAllElements('cols'); + void _setColumns(Sheet sheetObject, XmlDocument xmlFile) { + final columnElements = xmlFile.findAllElements('cols'); - if (sheetObject.getColWidths.isEmpty && - sheetObject.getColAutoFits.isEmpty) { - if (colElements.isEmpty) { + if (sheetObject.getColumnWidths.isEmpty && sheetObject.getColumnAutoFits.isEmpty) { + if (columnElements.isEmpty) { return; } - final cols = colElements.first; + final columns = columnElements.first; final worksheet = xmlFile.findAllElements('worksheet').first; - worksheet.children.remove(cols); + worksheet.children.remove(columns); return; } - if (colElements.isEmpty) { + if (columnElements.isEmpty) { final worksheet = xmlFile.findAllElements('worksheet').first; final sheetData = xmlFile.findAllElements('sheetData').first; final index = worksheet.children.indexOf(sheetData); @@ -475,43 +411,66 @@ class Save { worksheet.children.insert(index, XmlElement(XmlName('cols'), [], [])); } - var cols = colElements.first; + var columns = columnElements.first; - if (cols.children.isNotEmpty) { - cols.children.clear(); + if (columns.children.isNotEmpty) { + columns.children.clear(); } - final autoFits = sheetObject.getColAutoFits.asMap(); - final customWidths = sheetObject.getColWidths.asMap(); + final autoFits = sheetObject.getColumnAutoFits; + final customWidths = sheetObject.getColumnWidths; final columnCount = max(autoFits.length, customWidths.length); - List colWidths = []; + List columnWidths = []; int min = 0; + double defaultColumnWidth = sheetObject.defaultColumnWidth ?? _excelDefaultColumnWidth; + for (var index = 0; index < columnCount; index++) { - double value = _defaultColumnWidth; + double width = defaultColumnWidth; - if (autoFits.containsKey(index) && - autoFits[index] == true && - (!customWidths.containsKey(index) || - customWidths[index] == _defaultColumnWidth)) { - value = _calcAutoFitColWidth(sheetObject, index); + if (autoFits.containsKey(index) && (!customWidths.containsKey(index))) { + width = _calcAutoFitColumnWidth(sheetObject, index); } else { if (customWidths.containsKey(index)) { - value = customWidths[index]!; + width = customWidths[index]!; } } - colWidths.add(value); + columnWidths.add(width); - if (index != 0 && colWidths[index - 1] != value) { - _addNewCol(cols, min, index - 1, colWidths[index - 1]); + if (index != 0 && columnWidths[index - 1] != width) { + _addNewColumn(columns, min, index - 1, columnWidths[index - 1]); min = index; } if (index == (columnCount - 1)) { - _addNewCol(cols, index, index, value); + _addNewColumn(columns, index, index, width); + } + } + } + + void _setRows(String sheetName, Sheet sheetObject) { + final customHeights = sheetObject.getRowHeights; + + for (var rowIndex = 0; rowIndex < sheetObject._maxRows; rowIndex++) { + double? height; + + if (customHeights.containsKey(rowIndex)) { + height = customHeights[rowIndex]; + } + + if (sheetObject._sheetData[rowIndex] == null) { + continue; + } + var foundRow = _createNewRow(_excel._sheets[sheetName]! as XmlElement, rowIndex, height); + for (var columnIndex = 0; columnIndex < sheetObject._maxColumns; columnIndex++) { + var data = sheetObject._sheetData[rowIndex]![columnIndex]; + if (data == null) { + continue; + } + _updateCell(sheetName, foundRow, columnIndex, rowIndex, data.value); } } } @@ -520,8 +479,7 @@ class Save { if (sheetName == null || _excel._xmlFiles['xl/workbook.xml'] == null) { return false; } - List sheetList = - _excel._xmlFiles['xl/workbook.xml']!.findAllElements('sheet').toList(); + List sheetList = _excel._xmlFiles['xl/workbook.xml']!.findAllElements('sheet').toList(); XmlElement elementFound = XmlElement(XmlName('')); int position = -1; @@ -541,10 +499,7 @@ class Save { return true; } - _excel._xmlFiles['xl/workbook.xml']! - .findAllElements('sheets') - .first - .children + _excel._xmlFiles['xl/workbook.xml']!.findAllElements('sheets').first.children ..removeAt(position) ..insert(0, elementFound); @@ -580,25 +535,17 @@ class Save { _excel._sheetMap[s]!._spanList.isNotEmpty && _excel._xmlSheetId.containsKey(s) && _excel._xmlFiles.containsKey(_excel._xmlSheetId[s])) { - Iterable? iterMergeElement = _excel - ._xmlFiles[_excel._xmlSheetId[s]] - ?.findAllElements('mergeCells'); + Iterable? iterMergeElement = _excel._xmlFiles[_excel._xmlSheetId[s]]?.findAllElements('mergeCells'); late XmlElement mergeElement; if (iterMergeElement?.isNotEmpty ?? false) { mergeElement = iterMergeElement!.first; } else { - if ((_excel._xmlFiles[_excel._xmlSheetId[s]] - ?.findAllElements('worksheet') - .length ?? - 0) > - 0) { + if ((_excel._xmlFiles[_excel._xmlSheetId[s]]?.findAllElements('worksheet').length ?? 0) > 0) { int index = _excel._xmlFiles[_excel._xmlSheetId[s]]! .findAllElements('worksheet') .first .children - .indexOf(_excel._xmlFiles[_excel._xmlSheetId[s]]! - .findAllElements("sheetData") - .first); + .indexOf(_excel._xmlFiles[_excel._xmlSheetId[s]]!.findAllElements("sheetData").first); if (index == -1) { _damagedExcel(); } @@ -606,28 +553,21 @@ class Save { .findAllElements('worksheet') .first .children - .insert( - index + 1, - XmlElement(XmlName('mergeCells'), - [XmlAttribute(XmlName('count'), '0')])); - - mergeElement = _excel._xmlFiles[_excel._xmlSheetId[s]]! - .findAllElements('mergeCells') - .first; + .insert(index + 1, XmlElement(XmlName('mergeCells'), [XmlAttribute(XmlName('count'), '0')])); + + mergeElement = _excel._xmlFiles[_excel._xmlSheetId[s]]!.findAllElements('mergeCells').first; } else { _damagedExcel(); } } - List _spannedItems = - List.from(_excel._sheetMap[s]!.spannedItems); + List _spannedItems = List.from(_excel._sheetMap[s]!.spannedItems); [ ['count', _spannedItems.length.toString()], ].forEach((value) { if (mergeElement.getAttributeNode(value[0]) == null) { - mergeElement.attributes - .add(XmlAttribute(XmlName(value[0]), value[1])); + mergeElement.attributes.add(XmlAttribute(XmlName(value[0]), value[1])); } else { mergeElement.getAttributeNode(value[0])!.value = value[1]; } @@ -636,8 +576,7 @@ class Save { mergeElement.children.clear(); _spannedItems.forEach((value) { - mergeElement.children.add(XmlElement(XmlName('mergeCell'), - [XmlAttribute(XmlName('ref'), '$value')], [])); + mergeElement.children.add(XmlElement(XmlName('mergeCell'), [XmlAttribute(XmlName('ref'), '$value')], [])); }); } }); @@ -684,49 +623,31 @@ class Save { _setRTL() { _excel._rtlChangeLook.forEach((s) { var sheetObject = _excel._sheetMap[s]; - if (sheetObject != null && - _excel._xmlSheetId.containsKey(s) && - _excel._xmlFiles.containsKey(_excel._xmlSheetId[s])) { - var itrSheetViewsRTLElement = _excel._xmlFiles[_excel._xmlSheetId[s]] - ?.findAllElements('sheetViews'); + if (sheetObject != null && _excel._xmlSheetId.containsKey(s) && _excel._xmlFiles.containsKey(_excel._xmlSheetId[s])) { + var itrSheetViewsRTLElement = _excel._xmlFiles[_excel._xmlSheetId[s]]?.findAllElements('sheetViews'); if (itrSheetViewsRTLElement?.isNotEmpty ?? false) { - var itrSheetViewRTLElement = _excel._xmlFiles[_excel._xmlSheetId[s]] - ?.findAllElements('sheetView'); + var itrSheetViewRTLElement = _excel._xmlFiles[_excel._xmlSheetId[s]]?.findAllElements('sheetView'); if (itrSheetViewRTLElement?.isNotEmpty ?? false) { /// clear all the children of the sheetViews here - _excel._xmlFiles[_excel._xmlSheetId[s]] - ?.findAllElements('sheetViews') - .first - .children - .clear(); + _excel._xmlFiles[_excel._xmlSheetId[s]]?.findAllElements('sheetViews').first.children.clear(); } - _excel._xmlFiles[_excel._xmlSheetId[s]] - ?.findAllElements('sheetViews') - .first - .children - .add(XmlElement( + _excel._xmlFiles[_excel._xmlSheetId[s]]?.findAllElements('sheetViews').first.children.add(XmlElement( XmlName('sheetView'), [ - if (sheetObject.isRTL) - XmlAttribute(XmlName('rightToLeft'), '1'), + if (sheetObject.isRTL) XmlAttribute(XmlName('rightToLeft'), '1'), XmlAttribute(XmlName('workbookViewId'), '0'), ], )); } else { - _excel._xmlFiles[_excel._xmlSheetId[s]] - ?.findAllElements('worksheet') - .first - .children - .add(XmlElement(XmlName('sheetViews'), [], [ + _excel._xmlFiles[_excel._xmlSheetId[s]]?.findAllElements('worksheet').first.children.add(XmlElement(XmlName('sheetViews'), [], [ XmlElement( XmlName('sheetView'), [ - if (sheetObject.isRTL) - XmlAttribute(XmlName('rightToLeft'), '1'), + if (sheetObject.isRTL) XmlAttribute(XmlName('rightToLeft'), '1'), XmlAttribute(XmlName('workbookViewId'), '0'), ], ) @@ -742,10 +663,7 @@ class Save { var uniqueCount = 0; var count = 0; - XmlElement shareString = _excel - ._xmlFiles['xl/${_excel._sharedStringsTarget}']! - .findAllElements('sst') - .first; + XmlElement shareString = _excel._xmlFiles['xl/${_excel._sharedStringsTarget}']!.findAllElements('sst').first; shareString.children.clear(); @@ -773,41 +691,57 @@ class Save { _excel._sharedStrings = _SharedStringsMaintainer.instance; _excel._sharedStrings.clear(); - _excel._sheetMap.forEach((sheet, value) { + _excel._sheetMap.forEach((sheetName, sheetObject) { /// /// Create the sheet's xml file if it does not exist. - if (_excel._sheets[sheet] == null) { - parser._createSheet(sheet); + if (_excel._sheets[sheetName] == null) { + parser._createSheet(sheetName); } /// Clear the previous contents of the sheet if it exists, /// in order to reduce the time to find and compare with the sheet rows /// and hence just do the work of putting the data only i.e. creating new rows - if (_excel._sheets[sheet]?.children.isNotEmpty ?? false) { - _excel._sheets[sheet]!.children.clear(); + if (_excel._sheets[sheetName]?.children.isNotEmpty ?? false) { + _excel._sheets[sheetName]!.children.clear(); } - _setColumnWidth(sheet); - /// `Above function is important in order to wipe out the old contents of the sheet.` - for (var rowIndex = 0; rowIndex < value._maxRows; rowIndex++) { - if (value._sheetData[rowIndex] == null) { - continue; - } - var foundRow = - _createNewRow(_excel._sheets[sheet]! as XmlElement, rowIndex); - for (var colIndex = 0; colIndex < value._maxCols; colIndex++) { - var data = value._sheetData[rowIndex]![colIndex]; - if (data == null) { - continue; - } - if (data.value != null) { - _updateCell(sheet, foundRow, colIndex, rowIndex, data.value); - } + + XmlDocument? xmlFile = _excel._xmlFiles[_excel._xmlSheetId[sheetName]]; + if (xmlFile == null) return; + + // Set default column width and height for the sheet. + double? defaultRowHeight = sheetObject.defaultRowHeight; + double? defaultColumnWidth = sheetObject.defaultColumnWidth; + + XmlElement worksheetElement = xmlFile.findAllElements('worksheet').first; + + XmlElement? sheetFormatPrElement = + worksheetElement.findElements('sheetFormatPr').isNotEmpty ? worksheetElement.findElements('sheetFormatPr').first : null; + + if (sheetFormatPrElement != null) { + sheetFormatPrElement.attributes.clear(); + + if (defaultRowHeight == null && defaultColumnWidth == null) { + worksheetElement.children.remove(sheetFormatPrElement); } + } else if (defaultRowHeight != null || defaultColumnWidth != null) { + sheetFormatPrElement = XmlElement(XmlName('sheetFormatPr'), [], []); + worksheetElement.children.insert(0, sheetFormatPrElement); + } + + if (defaultRowHeight != null) { + sheetFormatPrElement!.attributes.add(XmlAttribute(XmlName('defaultRowHeight'), defaultRowHeight.toStringAsFixed(2))); + } + if (defaultColumnWidth != null) { + sheetFormatPrElement!.attributes.add(XmlAttribute(XmlName('defaultColWidth'), defaultColumnWidth.toStringAsFixed(2))); } - _setHeaderFooter(sheet); + _setColumns(sheetObject, xmlFile); + + _setRows(sheetName, sheetObject); + + _setHeaderFooter(sheetName); }); } @@ -834,8 +768,7 @@ class Save { return cell; } */ - XmlElement _updateCell(String sheet, XmlElement row, int columnIndex, - int rowIndex, dynamic value) { + XmlElement _updateCell(String sheet, XmlElement row, int columnIndex, int rowIndex, dynamic value) { var cell = _createCell(sheet, columnIndex, rowIndex, value); row.children.add(cell); return cell; diff --git a/lib/src/save/self_correct_span.dart b/lib/src/save/self_correct_span.dart index 53900b73..de76c24e 100644 --- a/lib/src/save/self_correct_span.dart +++ b/lib/src/save/self_correct_span.dart @@ -52,9 +52,12 @@ _selfCorrectSpanMap(Excel _excel) { } } } - _Span spanObj1 = _Span(); - spanObj1._start = [startRow, startColumn]; - spanObj1._end = [endRow, endColumn]; + _Span spanObj1 = _Span( + rowSpanStart: startRow, + columnSpanStart: startColumn, + rowSpanEnd: endRow, + columnSpanEnd: endColumn, + ); spanList[i] = spanObj1; } _excel._sheetMap[key]!._spanList = List<_Span?>.from(spanList); diff --git a/lib/src/sharedStrings/shared_strings.dart b/lib/src/sharedStrings/shared_strings.dart index 8a550ceb..f77a4e82 100644 --- a/lib/src/sharedStrings/shared_strings.dart +++ b/lib/src/sharedStrings/shared_strings.dart @@ -85,7 +85,10 @@ class SharedString { String toString() { var buffer = StringBuffer(); node.findAllElements('t').forEach((child) { - buffer.write(Parser._parseValue(child)); + if (child.parentElement == null || + child.parentElement!.name.local != 'rPh') { + buffer.write(Parser._parseValue(child)); + } }); return buffer.toString(); } @@ -94,5 +97,12 @@ class SharedString { int get hashCode => _hashCode; @override - operator ==(Object other) => other.hashCode == _hashCode; + operator ==(Object other) { + if (other.hashCode == _hashCode) { + if (other.toString() == toString()) { + return true; + } + } + return false; + } } diff --git a/lib/src/sheet/cell_index.dart b/lib/src/sheet/cell_index.dart index 27c10d3e..6677083c 100644 --- a/lib/src/sheet/cell_index.dart +++ b/lib/src/sheet/cell_index.dart @@ -2,9 +2,9 @@ part of excel; // ignore: must_be_immutable class CellIndex extends Equatable { - CellIndex._({int? col, int? row}) { - assert(col != null && row != null); - this._columnIndex = col!; + CellIndex._({int? column, int? row}) { + assert(column != null && row != null); + this._columnIndex = column!; this._rowIndex = row!; } @@ -15,7 +15,7 @@ class CellIndex extends Equatable { ///``` static CellIndex indexByColumnRow({int? columnIndex, int? rowIndex}) { assert(columnIndex != null && rowIndex != null); - return CellIndex._(col: columnIndex!, row: rowIndex!); + return CellIndex._(column: columnIndex!, row: rowIndex!); } /// @@ -25,7 +25,7 @@ class CellIndex extends Equatable { ///``` static CellIndex indexByString(String cellIndex) { List li = _cellCoordsFromCellId(cellIndex); - return CellIndex._(row: li[0], col: li[1]); + return CellIndex._(row: li[0], column: li[1]); } /// Avoid using it as it is very process expensive function. diff --git a/lib/src/sheet/data_model.dart b/lib/src/sheet/data_model.dart index 839951fd..836e6d05 100644 --- a/lib/src/sheet/data_model.dart +++ b/lib/src/sheet/data_model.dart @@ -9,7 +9,7 @@ class Data extends Equatable { late String _sheetName; bool _isFormula = false; late int _rowIndex; - late int _colIndex; + late int _columnIndex; /// ///It will clone the object by changing the `this` reference of previous DataObject and putting `new this` reference, with copying the values too @@ -18,7 +18,7 @@ class Data extends Equatable { : this._( sheet, dataObject._rowIndex, - dataObject.colIndex, + dataObject.columnIndex, value_: dataObject._value, cellStyleVal: dataObject._cellStyle, isFormulaVal: dataObject._isFormula, @@ -31,7 +31,7 @@ class Data extends Equatable { Data._( Sheet sheet, int row, - int col, { + int column, { dynamic value_, CellStyle? cellStyleVal, bool isFormulaVal = false, @@ -44,12 +44,12 @@ class Data extends Equatable { _cellType = cellTypeVal; _sheetName = sheet.sheetName; _rowIndex = row; - _colIndex = col; + _columnIndex = column; } /// returns the newData object when called from Sheet Class - static Data newData(Sheet sheet, int row, int col) { - return Data._(sheet, row, col); + static Data newData(Sheet sheet, int row, int column) { + return Data._(sheet, row, column); } /// returns the cell type @@ -68,8 +68,8 @@ class Data extends Equatable { } /// returns the column Index - int get colIndex { - return _colIndex; + int get columnIndex { + return _columnIndex; } /// returns the sheet-name @@ -80,7 +80,7 @@ class Data extends Equatable { /// returns the string based cellId as A1, A2 or Z5 CellIndex get cellIndex { return CellIndex.indexByColumnRow( - columnIndex: _colIndex, rowIndex: _rowIndex); + columnIndex: _columnIndex, rowIndex: _rowIndex); } /// Helps to set the formula @@ -113,14 +113,14 @@ class Data extends Equatable { /// sets the user defined CellStyle in this current cell set cellStyle(CellStyle? _) { - _sheet._excel._colorChanges = true; + _sheet._excel._styleChanges = true; _cellStyle = _; } @override List get props => [ _value, - _colIndex, + _columnIndex, _rowIndex, _cellStyle, _sheetName, diff --git a/lib/src/sheet/sheet.dart b/lib/src/sheet/sheet.dart index 93c4f204..9f18cc5f 100644 --- a/lib/src/sheet/sheet.dart +++ b/lib/src/sheet/sheet.dart @@ -5,9 +5,12 @@ class Sheet { late String _sheet; late bool _isRTL; late int _maxRows; - late int _maxCols; - List _colWidth = []; - List _colAutoFit = []; + late int _maxColumns; + double? _defaultColumnWidth; + double? _defaultRowHeight; + Map _columnWidths = {}; + Map _rowHeights = {}; + Map _columnAutoFit = {}; late FastList _spannedItems; late List<_Span?> _spanList; late Map> _sheetData; @@ -22,9 +25,10 @@ class Sheet { spanL_: oldSheetObject._spanList, spanI_: oldSheetObject._spannedItems, maxRowsVal: oldSheetObject._maxRows, - maxColsVal: oldSheetObject._maxCols, - colWidthVal: oldSheetObject._colWidth, - colAutoFitVal: oldSheetObject._colAutoFit, + maxColumnsVal: oldSheetObject._maxColumns, + columnWidthsVal: oldSheetObject._columnWidths, + rowHeightsVal: oldSheetObject._rowHeights, + columnAutoFitVal: oldSheetObject._columnAutoFit, isRTLVal: oldSheetObject._isRTL, headerFooter: oldSheetObject._headerFooter); @@ -33,10 +37,11 @@ class Sheet { List<_Span?>? spanL_, FastList? spanI_, int? maxRowsVal, - int? maxColsVal, + int? maxColumnsVal, bool? isRTLVal, - List? colWidthVal, - List? colAutoFitVal, + Map? columnWidthsVal, + Map? rowHeightsVal, + Map? columnAutoFitVal, HeaderFooter? headerFooter}) { _excel = excel; _sheet = sheetName; @@ -45,7 +50,7 @@ class Sheet { _spannedItems = FastList(); _isRTL = false; _maxRows = 0; - _maxCols = 0; + _maxColumns = 0; _headerFooter = headerFooter; if (spanL_ != null) { @@ -55,8 +60,8 @@ class Sheet { if (spanI_ != null) { _spannedItems = FastList.from(spanI_); } - if (maxColsVal != null) { - _maxCols = maxColsVal; + if (maxColumnsVal != null) { + _maxColumns = maxColumnsVal; } if (maxRowsVal != null) { _maxRows = maxRowsVal; @@ -65,11 +70,14 @@ class Sheet { _isRTL = isRTLVal; _excel._rtlChangeLookup = sheetName; } - if (colWidthVal != null) { - _colWidth = List.from(colWidthVal); + if (columnWidthsVal != null) { + _columnWidths = Map.from(columnWidthsVal); } - if (colAutoFitVal != null) { - _colAutoFit = List.from(colAutoFitVal); + if (rowHeightsVal != null) { + _rowHeights = Map.from(rowHeightsVal); + } + if (columnAutoFitVal != null) { + _columnAutoFit = Map.from(columnAutoFitVal); } /// copy the data objects into a temp folder and then while putting it into `_sheetData` change the data objects references. @@ -85,7 +93,7 @@ class Sheet { }); }); } - _countRowAndCol(); + _countRowsAndColumns(); } /// @@ -107,7 +115,7 @@ class Sheet { /// returns the `DataObject` at position of `cellIndex` /// Data cell(CellIndex cellIndex) { - _checkMaxCol(cellIndex.columnIndex); + _checkMaxColumn(cellIndex.columnIndex); _checkMaxRow(cellIndex.rowIndex); if (cellIndex._columnIndex < 0 || cellIndex._rowIndex < 0) { _damagedExcel( @@ -115,14 +123,14 @@ class Sheet { '${cellIndex._columnIndex < 0 ? "Column" : "Row"} Index: ${cellIndex._columnIndex < 0 ? cellIndex._columnIndex : cellIndex._rowIndex} Negative index does not exist.'); } - /// increasing the rowCount + /// increasing the row count if (_maxRows < (cellIndex._rowIndex + 1)) { _maxRows = cellIndex._rowIndex + 1; } - /// increasing the colCount - if (_maxCols < (cellIndex._columnIndex + 1)) { - _maxCols = cellIndex._columnIndex + 1; + /// increasing the column count + if (_maxColumns < (cellIndex._columnIndex + 1)) { + _maxColumns = cellIndex._columnIndex + 1; } /// checking if the map has been already initialized or not? @@ -158,12 +166,12 @@ class Sheet { return _data; } - if (_maxRows > 0 && maxCols > 0) { + if (_maxRows > 0 && maxColumns > 0) { _data = List.generate(_maxRows, (rowIndex) { - return List.generate(_maxCols, (colIndex) { + return List.generate(_maxColumns, (columnIndex) { if (_sheetData[rowIndex] != null && - _sheetData[rowIndex]![colIndex] != null) { - return _sheetData[rowIndex]![colIndex]; + _sheetData[rowIndex]![columnIndex] != null) { + return _sheetData[rowIndex]![columnIndex]; } return null; }); @@ -196,10 +204,10 @@ class Sheet { /// returns `2-D dynamic List` of the sheet cell data in that range. /// List?> selectRange(CellIndex start, {CellIndex? end}) { - _checkMaxCol(start.columnIndex); + _checkMaxColumn(start.columnIndex); _checkMaxRow(start.rowIndex); if (end != null) { - _checkMaxCol(end.columnIndex); + _checkMaxColumn(end.columnIndex); _checkMaxRow(end.rowIndex); } @@ -226,7 +234,7 @@ class Sheet { var mapData = _sheetData[i]; if (mapData != null) { List row = []; - for (var j = _startColumn; j <= (_endColumn ?? maxCols); j++) { + for (var j = _startColumn; j <= (_endColumn ?? maxColumns); j++) { row.add(mapData[j]); } _selectedRange.add(row); @@ -270,16 +278,16 @@ class Sheet { } /// - /// updates count of rows and cols + /// updates count of rows and columns /// - _countRowAndCol() { - int maximumColIndex = -1, maximumRowIndex = -1; + _countRowsAndColumns() { + int maximumColumnIndex = -1, maximumRowIndex = -1; List sortedKeys = _sheetData.keys.toList()..sort(); sortedKeys.forEach((rowKey) { if (_sheetData[rowKey] != null && _sheetData[rowKey]!.isNotEmpty) { List keys = _sheetData[rowKey]!.keys.toList()..sort(); - if (keys.isNotEmpty && keys.last > maximumColIndex) { - maximumColIndex = keys.last; + if (keys.isNotEmpty && keys.last > maximumColumnIndex) { + maximumColumnIndex = keys.last; } } }); @@ -288,16 +296,16 @@ class Sheet { maximumRowIndex = sortedKeys.last; } - _maxCols = maximumColIndex + 1; + _maxColumns = maximumColumnIndex + 1; _maxRows = maximumRowIndex + 1; } /// /// If `sheet` exists and `columnIndex < maxColumns` then it removes column at index = `columnIndex` /// - void removeColumn(int colIndex) { - _checkMaxCol(colIndex); - if (colIndex < 0 || colIndex >= maxCols) { + void removeColumn(int columnIndex) { + _checkMaxColumn(columnIndex); + if (columnIndex < 0 || columnIndex >= maxColumns) { return; } @@ -315,20 +323,25 @@ class Sheet { endColumn = spanObj.columnSpanEnd, endRow = spanObj.rowSpanEnd; - if (colIndex <= endColumn) { - _Span newSpanObj = _Span(); - if (colIndex < startColumn) { + if (columnIndex <= endColumn) { + if (columnIndex < startColumn) { startColumn -= 1; } endColumn -= 1; if (/* startColumn >= endColumn */ - (colIndex == (endColumn + 1)) && - (colIndex == - (colIndex < startColumn ? startColumn + 1 : startColumn))) { + (columnIndex == (endColumn + 1)) && + (columnIndex == + (columnIndex < startColumn + ? startColumn + 1 + : startColumn))) { _spanList[i] = null; } else { - newSpanObj._start = [startRow, startColumn]; - newSpanObj._end = [endRow, endColumn]; + _Span newSpanObj = _Span( + rowSpanStart: startRow, + columnSpanStart: startColumn, + rowSpanEnd: endRow, + columnSpanEnd: endColumn, + ); _spanList[i] = newSpanObj; } updateSpanCell = true; @@ -349,37 +362,35 @@ class Sheet { } Map> _data = Map>(); - if (colIndex <= maxCols - 1) { + if (columnIndex <= maxColumns - 1) { /// do the shifting task List sortedKeys = _sheetData.keys.toList()..sort(); sortedKeys.forEach((rowKey) { - Map colMap = Map(); - List sortedColKeys = _sheetData[rowKey]!.keys.toList()..sort(); - sortedColKeys.forEach((colKey) { + Map columnMap = Map(); + List sortedColumnKeys = _sheetData[rowKey]!.keys.toList()..sort(); + sortedColumnKeys.forEach((columnKey) { if (_sheetData[rowKey] != null && - _sheetData[rowKey]![colKey] != null) { - if (colKey < colIndex) { - colMap[colKey] = _sheetData[rowKey]![colKey]!; + _sheetData[rowKey]![columnKey] != null) { + if (columnKey < columnIndex) { + columnMap[columnKey] = _sheetData[rowKey]![columnKey]!; } - if (colIndex == colKey) { - _sheetData[rowKey]!.remove(colKey); + if (columnIndex == columnKey) { + _sheetData[rowKey]!.remove(columnKey); } - if (colIndex < colKey) { - colMap[colKey - 1] = _sheetData[rowKey]![colKey]!; - _sheetData[rowKey]!.remove(colKey); + if (columnIndex < columnKey) { + columnMap[columnKey - 1] = _sheetData[rowKey]![columnKey]!; + _sheetData[rowKey]!.remove(columnKey); } } }); - _data[rowKey] = Map.from(colMap); + _data[rowKey] = Map.from(columnMap); }); _sheetData = Map>.from(_data); } - if (_maxCols - 1 <= colIndex) { - _maxCols -= 1; + if (_maxColumns - 1 <= columnIndex) { + _maxColumns -= 1; } - - //_countRowAndCol(); } /// @@ -389,11 +400,11 @@ class Sheet { /// /// If the `sheet` does not exists then it will be created automatically. /// - void insertColumn(int colIndex) { - if (colIndex < 0) { + void insertColumn(int columnIndex) { + if (columnIndex < 0) { return; } - _checkMaxCol(colIndex); + _checkMaxColumn(columnIndex); bool updateSpanCell = false; @@ -408,14 +419,17 @@ class Sheet { endColumn = spanObj.columnSpanEnd, endRow = spanObj.rowSpanEnd; - if (colIndex <= endColumn) { - _Span newSpanObj = _Span(); - if (colIndex <= startColumn) { + if (columnIndex <= endColumn) { + if (columnIndex <= startColumn) { startColumn += 1; } endColumn += 1; - newSpanObj._start = [startRow, startColumn]; - newSpanObj._end = [endRow, endColumn]; + _Span newSpanObj = _Span( + rowSpanStart: startRow, + columnSpanStart: startColumn, + rowSpanEnd: endRow, + columnSpanEnd: endColumn, + ); _spanList[i] = newSpanObj; updateSpanCell = true; _excel._mergeChanges = true; @@ -433,29 +447,29 @@ class Sheet { if (_sheetData.isNotEmpty) { Map> _data = Map>(); List sortedKeys = _sheetData.keys.toList()..sort(); - if (colIndex <= maxCols - 1) { + if (columnIndex <= maxColumns - 1) { /// do the shifting task sortedKeys.forEach((rowKey) { - Map colMap = Map(); + Map columnMap = Map(); - /// getting the cols keys in descending order so as to shifting becomes easy - List sortedColKeys = _sheetData[rowKey]!.keys.toList() + /// getting the column keys in descending order so as to shifting becomes easy + List sortedColumnKeys = _sheetData[rowKey]!.keys.toList() ..sort((a, b) { return b.compareTo(a); }); - sortedColKeys.forEach((colKey) { + sortedColumnKeys.forEach((columnKey) { if (_sheetData[rowKey] != null && - _sheetData[rowKey]![colKey] != null) { - if (colKey < colIndex) { - colMap[colKey] = _sheetData[rowKey]![colKey]!; + _sheetData[rowKey]![columnKey] != null) { + if (columnKey < columnIndex) { + columnMap[columnKey] = _sheetData[rowKey]![columnKey]!; } - if (colIndex <= colKey) { - colMap[colKey + 1] = _sheetData[rowKey]![colKey]!; + if (columnIndex <= columnKey) { + columnMap[columnKey + 1] = _sheetData[rowKey]![columnKey]!; } } }); - colMap[colIndex] = Data.newData(this, rowKey, colIndex); - _data[rowKey] = Map.from(colMap); + columnMap[columnIndex] = Data.newData(this, rowKey, columnIndex); + _data[rowKey] = Map.from(columnMap); }); _sheetData = Map>.from(_data); } else { @@ -464,21 +478,21 @@ class Sheet { /// and mock the user as if the 2-D list is being saved /// /// As when user calls DataObject.cells then we will output 2-D list - pretending. - _sheetData[sortedKeys.first]![colIndex] = - Data.newData(this, sortedKeys.first, colIndex); + _sheetData[sortedKeys.first]![columnIndex] = + Data.newData(this, sortedKeys.first, columnIndex); } } else { /// here simply just take the first row and put the columnIndex as the _sheetData was previously null _sheetData = Map>(); - _sheetData[0] = {colIndex: Data.newData(this, 0, colIndex)}; + _sheetData[0] = {columnIndex: Data.newData(this, 0, columnIndex)}; } - if (_maxCols - 1 <= colIndex) { - _maxCols += 1; + if (_maxColumns - 1 <= columnIndex) { + _maxColumns += 1; } else { - _maxCols = colIndex + 1; + _maxColumns = columnIndex + 1; } - //_countRowAndCol(); + //_countRowsAndColumns(); } /// @@ -503,7 +517,6 @@ class Sheet { endRow = spanObj.rowSpanEnd; if (rowIndex <= endRow) { - _Span newSpanObj = _Span(); if (rowIndex < startRow) { startRow -= 1; } @@ -513,8 +526,12 @@ class Sheet { (rowIndex == (rowIndex < startRow ? startRow + 1 : startRow))) { _spanList[i] = null; } else { - newSpanObj._start = [startRow, startColumn]; - newSpanObj._end = [endRow, endColumn]; + _Span newSpanObj = _Span( + rowSpanStart: startRow, + columnSpanStart: startColumn, + rowSpanEnd: endRow, + columnSpanEnd: endColumn, + ); _spanList[i] = newSpanObj; } updateSpanCell = true; @@ -552,10 +569,10 @@ class Sheet { }); _sheetData = Map>.from(_data); } - //_countRowAndCol(); + //_countRowsAndColumns(); } else { _maxRows = 0; - _maxCols = 0; + _maxColumns = 0; } if (_maxRows - 1 <= rowIndex) { @@ -591,13 +608,16 @@ class Sheet { endRow = spanObj.rowSpanEnd; if (rowIndex <= endRow) { - _Span newSpanObj = _Span(); if (rowIndex <= startRow) { startRow += 1; } endRow += 1; - newSpanObj._start = [startRow, startColumn]; - newSpanObj._end = [endRow, endColumn]; + _Span newSpanObj = _Span( + rowSpanStart: startRow, + columnSpanStart: startColumn, + rowSpanEnd: endRow, + columnSpanEnd: endColumn, + ); _spanList[i] = newSpanObj; updateSpanCell = true; _excel._mergeChanges = true; @@ -639,7 +659,7 @@ class Sheet { _maxRows = rowIndex + 1; } - //_countRowAndCol(); + //_countRowsAndColumns(); } /// @@ -657,7 +677,7 @@ class Sheet { if (columnIndex < 0 || rowIndex < 0) { return; } - _checkMaxCol(columnIndex); + _checkMaxColumn(columnIndex); _checkMaxRow(rowIndex); int newRowIndex = rowIndex, newColumnIndex = columnIndex; @@ -676,7 +696,7 @@ class Sheet { /// Puts the cellStyle if (cellStyle != null) { _sheetData[newRowIndex]![newColumnIndex]!._cellStyle = cellStyle; - _excel._colorChanges = true; + _excel._styleChanges = true; } } @@ -691,8 +711,8 @@ class Sheet { endColumn = end._columnIndex, endRow = end._rowIndex; - _checkMaxCol(startColumn); - _checkMaxCol(endColumn); + _checkMaxColumn(startColumn); + _checkMaxColumn(endColumn); _checkMaxRow(startRow); _checkMaxRow(endRow); @@ -712,6 +732,10 @@ class Sheet { endColumn = gotPosition[2]; endRow = gotPosition[3]; + // Update maxColumns maxRows + _maxColumns = _maxColumns > endColumn ? _maxColumns : endColumn + 1; + _maxRows = _maxRows > endRow ? _maxRows : endRow + 1; + bool getValue = true; Data value = Data.newData(this, startRow, startColumn); @@ -749,9 +773,12 @@ class Sheet { _spannedItems.add(sp); } - _Span s = _Span(); - s._start = [startRow, startColumn]; - s._end = [endRow, endColumn]; + _Span s = _Span( + rowSpanStart: startRow, + columnSpanStart: startColumn, + rowSpanEnd: endRow, + columnSpanEnd: endColumn, + ); _spanList.add(s); _excel._mergeChangeLookup = sheetName; @@ -772,20 +799,18 @@ class Sheet { List lis = unmergeCells.split(RegExp(r":")); if (lis.length == 2) { bool remove = false; - List start, end; - start = - _cellCoordsFromCellId(lis[0]); // [x,y] => [startRow, startColumn] - end = _cellCoordsFromCellId(lis[1]); // [x,y] => [endRow, endColumn] + CellIndex start = CellIndex.indexByString(lis[0]), + end = CellIndex.indexByString(lis[1]); for (int i = 0; i < _spanList.length; i++) { _Span? spanObject = _spanList[i]; if (spanObject == null) { continue; } - if (spanObject.columnSpanStart == start[1] && - spanObject.rowSpanStart == start[0] && - spanObject.columnSpanEnd == end[1] && - spanObject.rowSpanEnd == end[0]) { + if (spanObject.columnSpanStart == start.columnIndex && + spanObject.rowSpanStart == start.rowIndex && + spanObject.columnSpanEnd == end.columnIndex && + spanObject.rowSpanEnd == end.rowIndex) { _spanList[i] = null; remove = true; } @@ -799,6 +824,89 @@ class Sheet { } } + /// + /// Sets the cellStyle of the merged cells. + /// + /// It will get the merged cells only by giving the starting position of merged cells. + /// + void setMergedCellStyle(CellIndex start, CellStyle mergedCellStyle) { + List> _mergedCells = spannedItems + .map( + (e) => e.split(":").map((e) => CellIndex.indexByString(e)).toList(), + ) + .toList(); + + List _startIndices = _mergedCells.map((e) => e[0]).toList(); + List _endIndices = _mergedCells.map((e) => e[1]).toList(); + + if (_mergedCells.isEmpty || + start._columnIndex < 0 || + start._rowIndex < 0 || + !_startIndices.contains(start)) { + return; + } + + CellIndex end = _endIndices[_startIndices.indexOf(start)]; + + bool hasBorder = mergedCellStyle.topBorder != Border() || + mergedCellStyle.bottomBorder != Border() || + mergedCellStyle.leftBorder != Border() || + mergedCellStyle.rightBorder != Border() || + mergedCellStyle.diagonalBorderUp || + mergedCellStyle.diagonalBorderDown; + if (hasBorder) { + for (var i = start.rowIndex; i <= end.rowIndex; i++) { + for (var j = start.columnIndex; j <= end.columnIndex; j++) { + CellStyle cellStyle = mergedCellStyle.copyWith( + topBorderVal: Border(), + bottomBorderVal: Border(), + leftBorderVal: Border(), + rightBorderVal: Border(), + diagonalBorderUpVal: false, + diagonalBorderDownVal: false, + ); + + if (i == start.rowIndex) { + cellStyle = cellStyle.copyWith( + topBorderVal: mergedCellStyle.topBorder, + ); + } + if (i == end.rowIndex) { + cellStyle = cellStyle.copyWith( + bottomBorderVal: mergedCellStyle.bottomBorder, + ); + } + if (j == start.columnIndex) { + cellStyle = cellStyle.copyWith( + leftBorderVal: mergedCellStyle.leftBorder, + ); + } + if (j == end.columnIndex) { + cellStyle = cellStyle.copyWith( + rightBorderVal: mergedCellStyle.rightBorder, + ); + } + + if (i == j || + start.rowIndex == end.rowIndex || + start.columnIndex == end.columnIndex) { + cellStyle = cellStyle.copyWith( + diagonalBorderUpVal: mergedCellStyle.diagonalBorderUp, + diagonalBorderDownVal: mergedCellStyle.diagonalBorderDown, + ); + } + + if (i == start.rowIndex && j == start.columnIndex) { + cell(start).cellStyle = cellStyle; + } else { + _putData(i, j, null); + _sheetData[i]![j]!.cellStyle = cellStyle; + } + } + } + } + } + /// /// Helps to find the interaction between the pre-existing span position and updates if with new span if there any interaction(Cross-Sectional Spanning) exists. /// @@ -919,7 +1027,7 @@ class Sheet { if (startingColumn > 0) { columnIndex = startingColumn; } - _checkMaxCol(columnIndex + row.length); + _checkMaxColumn(columnIndex + row.length); int rowsLength = _maxRows, maxIterationIndex = row.length - 1, currentRowPosition = 0; // position in [row] iterables @@ -953,11 +1061,6 @@ class Sheet { } } } - //int tempo_max_col = columnIndex + row.length - 1; - - //if (_maxCols - 1 < tempo_max_col) { - // _maxCols = tempo_max_col + 1; - //} } /// @@ -990,70 +1093,117 @@ class Sheet { _sheetData[rowIndex]![columnIndex]!._cellType = _getCellType(value.runtimeType); - if ((_maxCols - 1) < columnIndex) { - _maxCols = columnIndex + 1; + if ((_maxColumns - 1) < columnIndex) { + _maxColumns = columnIndex + 1; } if ((_maxRows - 1) < rowIndex) { _maxRows = rowIndex + 1; } - //_countRowAndCol(); + //_countRowsAndColumns(); } + double? get defaultRowHeight => _defaultRowHeight; + + double? get defaultColumnWidth => _defaultColumnWidth; + /// - /// returns list of auto fit columns + /// returns map of auto fit columns /// - List get getColAutoFits { - return _colAutoFit; + Map get getColumnAutoFits => _columnAutoFit; + + /// + /// returns map of custom width columns + /// + Map get getColumnWidths => _columnWidths; + + /// + /// returns map of custom height rows + /// + Map get getRowHeights => _rowHeights; + + /// + /// returns auto fit state of column index + /// + bool getColumnAutoFit(int columnIndex) { + if (_columnAutoFit.containsKey(columnIndex)) { + return _columnAutoFit[columnIndex]!; + } + return false; } /// - /// returns list of custom width columns + /// returns width of column index /// - List get getColWidths { - return _colWidth; + double getColumnWidth(int columnIndex) { + if (_columnWidths.containsKey(columnIndex)) { + return _columnWidths[columnIndex]!; + } + return _defaultColumnWidth!; } /// - /// Get Column AutoFit + /// returns height of row index /// - bool getColAutoFit(int colIndex) { - _checkMaxCol(colIndex); - return _colAutoFit[colIndex]; + double getRowHeight(int rowIndex) { + if (_rowHeights.containsKey(rowIndex)) { + return _rowHeights[rowIndex]!; + } + return _defaultRowHeight!; } /// - /// Get Column Width + /// Set the default column width. + /// + /// If both `setDefaultRowHeight` and `setDefaultColumnWidth` are not called, + /// then the default row height and column width will be set by Excel. + /// + /// The default row height is 15.0 and the default column width is 8.43. /// - double getColWidth(int colIndex) { - _checkMaxCol(colIndex); - return _colWidth[colIndex]; + void setDefaultColumnWidth([double columnWidth = _excelDefaultColumnWidth]) { + if (columnWidth < 0) return; + _defaultColumnWidth = columnWidth; } /// - /// Set Column AutoFit + /// Set the default row height. + /// + /// If both `setDefaultRowHeight` and `setDefaultColumnWidth` are not called, + /// then the default row height and column width will be set by Excel. /// - void setColAutoFit(int colIndex) { - _checkMaxCol(colIndex); + /// The default row height is 15.0 and the default column width is 8.43. + /// + void setDefaultRowHeight([double rowHeight = _excelDefaultRowHeight]) { + if (rowHeight < 0) return; + _defaultRowHeight = rowHeight; + } - while (colIndex >= _colAutoFit.length) { - _colAutoFit.add(false); - } - _colAutoFit[colIndex] = true; + /// + /// Set Column AutoFit + /// + void setColumnAutoFit(int columnIndex) { + _checkMaxColumn(columnIndex); + if (columnIndex < 0) return; + _columnAutoFit[columnIndex] = true; } /// /// Set Column Width /// - void setColWidth(int colIndex, double colWidth) { - _checkMaxCol(colIndex); - if (colWidth < 0) return; + void setColumnWidth(int columnIndex, double columnWidth) { + _checkMaxColumn(columnIndex); + if (columnWidth < 0) return; + _columnWidths[columnIndex] = columnWidth; + } - while (colIndex >= _colWidth.length) { - _colWidth.add(_defaultColumnWidth); - } - _colWidth[colIndex] = colWidth; + /// + /// Set Row Height + /// + void setRowHeight(int rowIndex, double rowHeight) { + _checkMaxRow(rowIndex); + if (rowHeight < 0) return; + _rowHeights[rowIndex] = rowHeight; } CellType _getCellType(var type) { @@ -1123,7 +1273,7 @@ class Sheet { } } - int rowsLength = maxRows, columnLength = maxCols; + int rowsLength = maxRows, columnLength = maxColumns; RegExp sourceRegx; if (source.runtimeType == RegExp) { sourceRegx = source; @@ -1144,7 +1294,7 @@ class Sheet { _sheetData[i]![j] != null && sourceRegx.hasMatch(_sheetData[i]![j]!.value.toString()) && (first == -1 || first != replaceCount)) { - _sheetData[i]![j]! + _sheetData[i]![j]!.value = _sheetData[i]![j]! .value .toString() .replaceAll(sourceRegx, target.toString()); @@ -1195,7 +1345,7 @@ class Sheet { }); } } - //_countRowAndCol(); + //_countRowsAndColumns(); return isNotInside; } @@ -1230,12 +1380,12 @@ class Sheet { /// ///Check if columnIndex is not out of `Excel Column limits`. /// - _checkMaxCol(int colIndex) { - if (_maxCols >= 16384 || colIndex >= 16384) { + _checkMaxColumn(int columnIndex) { + if (_maxColumns >= 16384 || columnIndex >= 16384) { throw ArgumentError('Reached Max (16384) or (XFD) columns value.'); } - if (colIndex < 0) { - throw ArgumentError('Negative colIndex found: $colIndex'); + if (columnIndex < 0) { + throw ArgumentError('Negative columnIndex found: $columnIndex'); } } @@ -1299,14 +1449,14 @@ class Sheet { } if (rowIndex < _maxRows) { if (_sheetData[rowIndex] != null) { - return List.generate(_maxCols, (colIndex) { - if (_sheetData[rowIndex]![colIndex] != null) { - return _sheetData[rowIndex]![colIndex]!; + return List.generate(_maxColumns, (columnIndex) { + if (_sheetData[rowIndex]![columnIndex] != null) { + return _sheetData[rowIndex]![columnIndex]!; } return null; }); } else { - return List.generate(_maxCols, (_) => null); + return List.generate(_maxColumns, (_) => null); } } return []; @@ -1320,10 +1470,10 @@ class Sheet { } /// - ///returns count of `cols` having data in `sheet` + ///returns count of `columns` having data in `sheet` /// - int get maxCols { - return _maxCols; + int get maxColumns { + return _maxColumns; } HeaderFooter? get headerFooter { diff --git a/lib/src/utilities/constants.dart b/lib/src/utilities/constants.dart index e8080fef..7a3a6e87 100644 --- a/lib/src/utilities/constants.dart +++ b/lib/src/utilities/constants.dart @@ -14,4 +14,6 @@ const _relationships = const _spreasheetXlsx = 'xlsx'; -const _defaultColumnWidth = 14.75; +// reference: https://support.microsoft.com/en-gb/office/change-the-column-width-and-row-height-72f5e3cc-994d-43e8-ae58-9774a0905f46 +const _excelDefaultColumnWidth = 8.43; +const _excelDefaultRowHeight = 15.0; diff --git a/lib/src/utilities/span.dart b/lib/src/utilities/span.dart index fda4e475..9f71ab9a 100644 --- a/lib/src/utilities/span.dart +++ b/lib/src/utilities/span.dart @@ -1,45 +1,32 @@ part of excel; // For Spanning the columns and rows -// ignore: must_be_immutable class _Span extends Equatable { - late List __start; - late List __end; - - _Span() { - __start = []; - __end = []; - } - - set _start(List val) { - __start = val; - } - - set _end(List val) { - __end = val; - } - - int get rowSpanStart { - return __start[0]; - } - - int get rowSpanEnd { - return __end[0]; - } - - int get columnSpanStart { - return __start[1]; - } - - int get columnSpanEnd { - return __end[1]; - } + final int rowSpanStart; + final int columnSpanStart; + final int rowSpanEnd; + final int columnSpanEnd; + + _Span({ + required this.rowSpanStart, + required this.columnSpanStart, + required this.rowSpanEnd, + required this.columnSpanEnd, + }); + + _Span.fromCellIndex({ + required CellIndex start, + required CellIndex end, + }) : rowSpanStart = start.rowIndex, + columnSpanStart = start.columnIndex, + rowSpanEnd = end.rowIndex, + columnSpanEnd = end.columnIndex; @override List get props => [ - __start[0], - __start[1], - __end[0], - __end[1], + rowSpanStart, + columnSpanStart, + rowSpanEnd, + columnSpanEnd, ]; } diff --git a/lib/src/utilities/utility.dart b/lib/src/utilities/utility.dart index b4805b40..c0d8266b 100644 --- a/lib/src/utilities/utility.dart +++ b/lib/src/utilities/utility.dart @@ -2,8 +2,8 @@ part of excel; List _noCompression = ['mimetype', 'Thumbnails/thumbnail.png']; -String getCellId(int colI, int rowI) { - return '${_numericToLetters(colI + 1)}${rowI + 1}'; +String getCellId(int columnIndex, int rowIndex) { + return '${_numericToLetters(columnIndex + 1)}${rowIndex + 1}'; } String _isColorAppropriate(String value) { @@ -206,8 +206,8 @@ List _isLocationChangeRequired( /// `getColumnAlphabet(0); // returns A` /// `getColumnAlphabet(5); // returns F` /// -String getColumnAlphabet(int collIndex) { - return '${_numericToLetters(collIndex + 1)}'; +String getColumnAlphabet(int columnIndex) { + return '${_numericToLetters(columnIndex + 1)}'; } /// diff --git a/pubspec.yaml b/pubspec.yaml index a0871335..d8821a4c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,15 +1,19 @@ name: excel description: A flutter and dart library for reading, creating, editing and updating excel sheets with compatible both on client and server side. -version: 2.1.0 +version: 3.0.0 homepage: https://github.com/justkawal/excel +topics: + - excel + - office + - storage environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.17.0 <4.0.0" dependencies: - archive: ">=3.1.0 <4.0.0" + archive: ^3.0.0 xml: ">=5.0.0 <7.0.0" - collection: ^1.17.0 + collection: ^1.15.0 equatable: ^2.0.0 dev_dependencies: diff --git a/test/excel_test.dart b/test/excel_test.dart index 2895aeb2..c035c53a 100644 --- a/test/excel_test.dart +++ b/test/excel_test.dart @@ -16,9 +16,8 @@ void main() { var file = './test/test_resources/example.xlsx'; var bytes = File(file).readAsBytesSync(); var excel = Excel.decodeBytes(bytes); - expect(excel.tables['Sheet1']!.maxCols, equals(3)); - expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), - equals('Washington')); + expect(excel.tables['Sheet1']!.maxColumns, equals(3)); + expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), equals('Washington')); }); group('Sheet Operations', () { @@ -30,44 +29,35 @@ void main() { sheetObject.insertRowIterables(['Country', 'Capital', 'Head'], 0); sheetObject.insertRowIterables(['Russia', 'Moscow', 'Putin'], 1); expect(excel.sheets.entries.length, equals(2)); - expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), - equals('Washington')); - expect(excel.tables['SheetTmp']!.maxCols, equals(3)); - expect(excel.tables['SheetTmp']!.rows[1][2]!.value.toString(), - equals('Putin')); + expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), equals('Washington')); + expect(excel.tables['SheetTmp']!.maxColumns, equals(3)); + expect(excel.tables['SheetTmp']!.rows[1][2]!.value.toString(), equals('Putin')); }); test('copy Sheet', () { excel.copy('SheetTmp', 'SheetTmp2'); expect(excel.sheets.entries.length, equals(3)); - expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), - equals('Washington')); - expect(excel.tables['SheetTmp']!.maxCols, equals(3)); - expect(excel.tables['SheetTmp']!.rows[1][2]!.value.toString(), - equals('Putin')); - expect(excel.tables['SheetTmp2']!.rows[1][2]!.value.toString(), - equals('Putin')); + expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), equals('Washington')); + expect(excel.tables['SheetTmp']!.maxColumns, equals(3)); + expect(excel.tables['SheetTmp']!.rows[1][2]!.value.toString(), equals('Putin')); + expect(excel.tables['SheetTmp2']!.rows[1][2]!.value.toString(), equals('Putin')); }); test('rename Sheet', () { excel.rename('SheetTmp2', 'SheetTmp3'); expect(excel.sheets.entries.length, equals(3)); expect(excel.tables['Sheettmp2'], equals(null)); - expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), - equals('Washington')); - expect(excel.tables['SheetTmp']!.maxCols, equals(3)); - expect(excel.tables['SheetTmp']!.rows[1][2]!.value.toString(), - equals('Putin')); - expect(excel.tables['SheetTmp3']!.rows[1][2]!.value.toString(), - equals('Putin')); + expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), equals('Washington')); + expect(excel.tables['SheetTmp']!.maxColumns, equals(3)); + expect(excel.tables['SheetTmp']!.rows[1][2]!.value.toString(), equals('Putin')); + expect(excel.tables['SheetTmp3']!.rows[1][2]!.value.toString(), equals('Putin')); }); test('delete Sheet', () { excel.delete('SheetTmp3'); excel.delete('SheetTmp'); expect(excel.sheets.entries.length, equals(1)); - expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), - equals('Washington')); + expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), equals('Washington')); }); }); @@ -89,11 +79,9 @@ void main() { // delete tmp folder new Directory('./tmp').delete(recursive: true); expect(newExcel.sheets.entries.length, equals(1)); - expect(newExcel.tables['Sheet1']!.rows[1][1]!.value.toString(), - equals('Washington')); - expect(newExcel.tables['Sheet1']!.maxCols, equals(3)); - expect(newExcel.tables['Sheet1']!.rows[4][1]!.value.toString(), - equals('Moscow')); + expect(newExcel.tables['Sheet1']!.rows[1][1]!.value.toString(), equals('Washington')); + expect(newExcel.tables['Sheet1']!.maxColumns, equals(3)); + expect(newExcel.tables['Sheet1']!.rows[4][1]!.value.toString(), equals('Moscow')); }); test('Saving XLSX File with superscript', () async { @@ -114,38 +102,25 @@ void main() { new Directory('./tmp').delete(recursive: true); expect(newExcel.sheets.entries.length, equals(1)); - expect(newExcel.tables['Sheet1']!.rows[0][0]!.value.toString(), - equals('Text and superscript text')); - expect(newExcel.tables['Sheet1']!.rows[1][0]!.value.toString(), - equals('Text and superscript text')); - expect(newExcel.tables['Sheet1']!.rows[2][0]!.value.toString(), - equals('Text in A3')); + expect(newExcel.tables['Sheet1']!.rows[0][0]!.value.toString(), equals('Text and superscript text')); + expect(newExcel.tables['Sheet1']!.rows[1][0]!.value.toString(), equals('Text and superscript text')); + expect(newExcel.tables['Sheet1']!.rows[2][0]!.value.toString(), equals('Text in A3')); }); - test( - 'Add already shared strings and make sure that they are reused by checking increased usage count but equal unique count', - () { + test('Add already shared strings and make sure that they are reused by checking increased usage count but equal unique count', () { var file = './test/test_resources/example.xlsx'; var bytes = File(file).readAsBytesSync(); var archive = ZipDecoder().decodeBytes(bytes); var sharedStringsArchive = archive.findFile('xl/sharedStrings.xml')!; - var oldSharedStringsDocument = - XmlDocument.parse(utf8.decode(sharedStringsArchive.content)); - var oldCount = oldSharedStringsDocument - .findAllElements('sst') - .first - .getAttributeNode("count"); - var oldUniqueCount = oldSharedStringsDocument - .findAllElements('sst') - .first - .getAttributeNode("uniqueCount"); + var oldSharedStringsDocument = XmlDocument.parse(utf8.decode(sharedStringsArchive.content)); + var oldCount = oldSharedStringsDocument.findAllElements('sst').first.getAttributeNode("count"); + var oldUniqueCount = oldSharedStringsDocument.findAllElements('sst').first.getAttributeNode("uniqueCount"); var excel = Excel.decodeBytes(bytes); Sheet? sheetObject = excel.tables['Sheet1']!; - sheetObject - .insertRowIterables(['ISRAEL', 'Jerusalem', 'Benjamin Netanyahu'], 4); + sheetObject.insertRowIterables(['ISRAEL', 'Jerusalem', 'Benjamin Netanyahu'], 4); var fileBytes = excel.encode(); if (fileBytes != null) { File(Directory.current.path + '/tmp/exampleOut.xlsx') @@ -159,16 +134,9 @@ void main() { var newArchive = ZipDecoder().decodeBytes(newFileBytes); var newSharedStringsArchive = newArchive.findFile('xl/sharedStrings.xml')!; - var newSharedStringsDocument = - XmlDocument.parse(utf8.decode(newSharedStringsArchive.content)); - var newCount = newSharedStringsDocument - .findAllElements('sst') - .first - .getAttributeNode("count"); - var newUniqueCount = newSharedStringsDocument - .findAllElements('sst') - .first - .getAttributeNode("uniqueCount"); + var newSharedStringsDocument = XmlDocument.parse(utf8.decode(newSharedStringsArchive.content)); + var newCount = newSharedStringsDocument.findAllElements('sst').first.getAttributeNode("count"); + var newUniqueCount = newSharedStringsDocument.findAllElements('sst').first.getAttributeNode("uniqueCount"); // delete tmp folder new Directory('./tmp').delete(recursive: true); @@ -196,12 +164,9 @@ void main() { new Directory('./tmp').delete(recursive: true); expect(newExcel.sheets.entries.length, equals(1)); - expect(newExcel.tables['Sheet1']!.rows[0][0]!.value.toString(), - equals('Text and superscript text')); - expect(newExcel.tables['Sheet1']!.rows[1][0]!.value.toString(), - equals('Text and superscript text')); - expect(newExcel.tables['Sheet1']!.rows[2][0]!.value.toString(), - equals('Text in A3')); + expect(newExcel.tables['Sheet1']!.rows[0][0]!.value.toString(), equals('Text and superscript text')); + expect(newExcel.tables['Sheet1']!.rows[1][0]!.value.toString(), equals('Text and superscript text')); + expect(newExcel.tables['Sheet1']!.rows[2][0]!.value.toString(), equals('Text in A3')); }); group('Header/Footer', () { @@ -223,10 +188,8 @@ void main() { var newFile = './tmp/exampleOut.xlsx'; var newFileBytes = File(newFile).readAsBytesSync(); var newExcel = Excel.decodeBytes(newFileBytes); - expect( - newExcel.tables['Sheet1']!.headerFooter!.oddHeader!, equals('Foo')); - expect( - newExcel.tables['Sheet1']!.headerFooter!.oddFooter!, equals('Bar')); + expect(newExcel.tables['Sheet1']!.headerFooter!.oddHeader!, equals('Foo')); + expect(newExcel.tables['Sheet1']!.headerFooter!.oddFooter!, equals('Bar')); // delete tmp folder only when test is successful (diagnosis) new Directory('./tmp').delete(recursive: true); @@ -280,13 +243,11 @@ void main() { final borderEmpty = Border(); final borderMedium = Border(borderStyle: BorderStyle.Medium); - final borderMediumRed = - Border(borderStyle: BorderStyle.Medium, borderColorHex: 'FFFF0000'); + final borderMediumRed = Border(borderStyle: BorderStyle.Medium, borderColorHex: 'FFFF0000'); final borderHair = Border(borderStyle: BorderStyle.Hair); final borderDouble = Border(borderStyle: BorderStyle.Double); - final cellStyleA1 = - sheetObject.cell(CellIndex.indexByString('A1')).cellStyle; + final cellStyleA1 = sheetObject.cell(CellIndex.indexByString('A1')).cellStyle; expect(cellStyleA1?.leftBorder, equals(borderMedium)); expect(cellStyleA1?.rightBorder, equals(borderMedium)); expect(cellStyleA1?.topBorder, anyOf(isNull, equals(borderEmpty))); @@ -295,21 +256,18 @@ void main() { expect(cellStyleA1?.diagonalBorderUp, isFalse); expect(cellStyleA1?.diagonalBorderDown, isFalse); - final cellStyleB3 = - sheetObject.cell(CellIndex.indexByString('B3')).cellStyle; + final cellStyleB3 = sheetObject.cell(CellIndex.indexByString('B3')).cellStyle; expect(cellStyleB3?.leftBorder, equals(borderMedium)); expect(cellStyleB3?.rightBorder, equals(borderMedium)); expect(cellStyleB3?.topBorder, equals(borderHair)); expect(cellStyleB3?.bottomBorder, equals(borderHair)); - final cellStyleA5 = - sheetObject.cell(CellIndex.indexByString('A5')).cellStyle; + final cellStyleA5 = sheetObject.cell(CellIndex.indexByString('A5')).cellStyle; expect(cellStyleA5?.diagonalBorder, equals(borderDouble)); expect(cellStyleA5?.diagonalBorderUp, isFalse); expect(cellStyleA5?.diagonalBorderDown, isTrue); - final cellStyleC5 = - sheetObject.cell(CellIndex.indexByString('C5')).cellStyle; + final cellStyleC5 = sheetObject.cell(CellIndex.indexByString('C5')).cellStyle; expect(cellStyleC5?.diagonalBorder, equals(borderDouble)); expect(cellStyleC5?.diagonalBorderUp, isTrue); expect(cellStyleC5?.diagonalBorderDown, isFalse); @@ -342,9 +300,7 @@ void main() { // Loop from i = 1, as Excel does not set None type. final border = Border(borderStyle: borderStyles[i]); - final cellStyle = sheetObject - .cell(CellIndex.indexByString('B${2 * (i + 1)}')) - .cellStyle; + final cellStyle = sheetObject.cell(CellIndex.indexByString('B${2 * (i + 1)}')).cellStyle; expect(cellStyle?.leftBorder, equals(border)); expect(cellStyle?.rightBorder, equals(border)); @@ -353,6 +309,83 @@ void main() { } }); + test('test support for merged cells with borders', () async { + final file = './test/test_resources/mergedBorders.xlsx'; + final bytes = File(file).readAsBytesSync(); + final excel = Excel.decodeBytes(bytes); + final Sheet sheetObject = excel.tables['Sheet1']!; + + final borderStyles = [ + BorderStyle.None, + BorderStyle.DashDot, + BorderStyle.DashDotDot, + BorderStyle.Dashed, + BorderStyle.Dotted, + BorderStyle.Double, + BorderStyle.Hair, + BorderStyle.Medium, + BorderStyle.MediumDashDot, + BorderStyle.MediumDashDotDot, + BorderStyle.MediumDashed, + BorderStyle.SlantDashDot, + BorderStyle.Thick, + BorderStyle.Thin, + ]; + + sheetObject.merge(CellIndex.indexByString('B2'), CellIndex.indexByString('D4')); + + for (var i = 1; i < borderStyles.length; ++i) { + // Loop from i = 1, as Excel does not set None type. + final border = Border(borderStyle: borderStyles[i], borderColorHex: "FF000000"); + final start = CellIndex.indexByString('B${(4 * i + 2)}'); + final end = CellIndex.indexByString('D${(4 * i + 4)}'); + + sheetObject.merge(start, end); + + sheetObject.setMergedCellStyle( + start, + CellStyle( + leftBorder: border, + rightBorder: border, + topBorder: border, + bottomBorder: border, + ), + ); + } + + for (var i = 1; i < borderStyles.length; ++i) { + CellIndex cellIndexStart = CellIndex.indexByString('B${(4 * i + 2)}'); + CellIndex cellIndexEnd = CellIndex.indexByString('D${(4 * i + 4)}'); + + for (var j = cellIndexStart.rowIndex; j <= cellIndexEnd.rowIndex; j++) { + for (var k = cellIndexStart.columnIndex; k <= cellIndexEnd.columnIndex; k++) { + final cellStyle = sheetObject.cell(CellIndex.indexByColumnRow(columnIndex: k, rowIndex: j)).cellStyle; + + final borderStyle = Border( + borderStyle: borderStyles[i], + borderColorHex: "FF000000", + ); + + if (j == cellIndexStart.rowIndex) { + expect(cellStyle?.topBorder, equals(borderStyle)); + } + + if (j == cellIndexEnd.rowIndex) { + expect(cellStyle?.bottomBorder, equals(borderStyle)); + } + + if (k == cellIndexStart.columnIndex) { + expect(cellStyle?.leftBorder, equals(borderStyle)); + } + + if (k == cellIndexEnd.columnIndex) { + expect(cellStyle?.rightBorder, equals(borderStyle)); + } + } + } + } + }); + test('saving XLSX File with borders', () async { final file = './test/test_resources/borders.xlsx'; final bytes = File(file).readAsBytesSync(); @@ -372,12 +405,10 @@ void main() { final borderEmpty = Border(); final borderMedium = Border(borderStyle: BorderStyle.Medium); - final borderMediumRed = - Border(borderStyle: BorderStyle.Medium, borderColorHex: 'FFFF0000'); + final borderMediumRed = Border(borderStyle: BorderStyle.Medium, borderColorHex: 'FFFF0000'); final Sheet sheetObject = newExcel.tables['Sheet1']!; - final cellStyleB1 = - sheetObject.cell(CellIndex.indexByString('B1')).cellStyle; + final cellStyleB1 = sheetObject.cell(CellIndex.indexByString('B1')).cellStyle; expect(cellStyleB1?.leftBorder, equals(borderMedium)); expect(cellStyleB1?.rightBorder, equals(borderMedium)); expect(cellStyleB1?.topBorder, equals(borderEmpty)); @@ -387,4 +418,64 @@ void main() { new Directory('./tmp').delete(recursive: true); }); }); + + group('rPh tag', () { + test('Read Cell shared text without rPh elements', () { + var file = './test/test_resources/rphSample.xlsx'; + var bytes = File(file).readAsBytesSync(); + var excel = Excel.decodeBytes(bytes); + expect(excel.tables['Sheet1']!.rows[1][0]!.value.toString(), equals('plainText')); + expect(excel.tables['Sheet1']!.rows[1][1]!.value.toString(), equals('Hellow world')); + expect(excel.tables['Sheet1']!.rows[1][2]!.value.toString(), equals('世界よこんにちは')); + expect(excel.tables['Sheet1']!.rows[2][2]!.value.toString(), equals('ようこそユーザー')); + expect(excel.tables['Sheet1']!.rows[3][2]!.value.toString(), equals('ロケール選択')); + expect(excel.tables['Sheet1']!.rows[4][2]!.value.toString(), equals('ロケール選択')); + }); + + test('saving XLSX File without rPh elements', () async { + final file = './test/test_resources/rphSample.xlsx'; + final bytes = File(file).readAsBytesSync(); + final excel = Excel.decodeBytes(bytes); + excel.tables['Sheet1']!.rows[3][2]!.value = 'ロケール選択'; + + final outFilePath = Directory.current.path + '/tmp/rphSampleOut.xlsx'; + final fileBytes = excel.encode(); + if (fileBytes != null) { + File(outFilePath) + ..createSync(recursive: true) + ..writeAsBytesSync(fileBytes); + } + + final newFileBytes = File(outFilePath).readAsBytesSync(); + final newExcel = Excel.decodeBytes(newFileBytes); + expect(newExcel.tables['Sheet1']!.rows[3][2]!.value.toString(), equals('ロケール選択')); + + // delete tmp folder only when test is successful (diagnosis) + new Directory('./tmp').delete(recursive: true); + }); + }); + + group(".xls file handling", () { + test("Exception when opening old .xls file", () { + final file = './test/test_resources/oldXLSFile.xls'; + final bytes = File(file).readAsBytesSync(); + try { + Excel.decodeBytes(bytes); + } catch (e) { + expect(e, isA()); + expect(e.toString(), equals('Unsupported operation: Excel format unsupported. Only .xlsx files are supported')); + } + }); + + test("Exception when opening new .xls file", () { + final file = './test/test_resources/newXLSFile.xls'; + final bytes = File(file).readAsBytesSync(); + try { + Excel.decodeBytes(bytes); + } catch (e) { + expect(e, isA()); + expect(e.toString(), equals('Unsupported operation: Excel format unsupported. Only .xlsx files are supported')); + } + }); + }); } diff --git a/test/test_resources/mergedBorders.xlsx b/test/test_resources/mergedBorders.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..cf1839390b9f6a0c9763dffc27184e03e08a9479 GIT binary patch literal 16344 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 literal 0 HcmV?d00001 diff --git a/test/test_resources/newXLSFile.xls b/test/test_resources/newXLSFile.xls new file mode 100644 index 0000000000000000000000000000000000000000..e173a36794e941dd985b9c68a10125f3edaa3f7a GIT binary patch literal 25088 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 literal 0 HcmV?d00001 diff --git a/test/test_resources/oldXLSFile.xls b/test/test_resources/oldXLSFile.xls new file mode 100644 index 0000000000000000000000000000000000000000..765033f6bb08941ad77ba965bcb59dc56a0138eb GIT binary patch literal 8704 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 LdhSU2^V|Ocu4OF$ literal 0 HcmV?d00001