Skip to content
Open
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,20 @@ sheetObject.insertRow(82);
sheetObject.removeRow(80);
```

### Cell comments

```dart
/*
* To access comments, simply assign a Data? object to a variable and access the comment
*
* @Return: String?
* @Desc: cell's comment
*/

Data? cell
String? cellComment = cell.comment; // Returns null if no comments are available
```

### Cell-Style Options

| key | description |
Expand Down Expand Up @@ -390,6 +404,19 @@ excel.unLink('sheetName');
Sheet unlinked_sheetObject = excel['sheetName'];
```

### Get sheet(s) visibility

```dart
/*
* To access sheet(s) visibility
* @Return: Map<String, String>
* @Desc: map of sheet names and their visibility status
*/

Excel excel
excel.getSheetsVisibility();
```

### Merge Cells

```dart
Expand Down
17 changes: 17 additions & 0 deletions lib/src/excel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,23 @@ class Excel {
}
}

/// Returns a map of sheet names and their visibility status.
Map<String, String> getSheetsVisibility() {
Map<String, String> sheetVisibility = {};

Iterable<XmlElement>? elements =
_xmlFiles['xl/workbook.xml']?.findAllElements('sheet');
if (elements != null) {
for (var element in elements) {
String name = element.getAttribute('name') ?? 'Unknown';
String state = element.getAttribute('state') ?? 'visible';
sheetVisibility[name] = state;
}
}

return sheetVisibility;
}

///
///Updates the contents of `sheet` of the `cellIndex: CellIndex.indexByColumnRow(0, 0);` where indexing starts from 0
///
Expand Down
70 changes: 70 additions & 0 deletions lib/src/parser/parse.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,33 @@ class Parser {
}
}
});

// Parse comments
final comments = _parseComments(_excel._archive);

// Store comments in the appropriate data structure
for (final sheetEntry in comments.entries) {
final sheetName = sheetEntry.key;
final sheetComments = sheetEntry.value;

// Iterate through all comments for the sheet
for (final entry in sheetComments.entries) {
final cellRef = entry.key;
final comment = entry.value;

final sheet = _excel._sheetMap[sheetName];
var cell = sheet?._getCell(cellRef);
if (cell == null) {
// Create a new Data object if there is no value, but there is a comment
final cellIndex = CellIndex.indexByString(cellRef);
cell =
Data.newData(sheet!, cellIndex.rowIndex, cellIndex.columnIndex);
sheet._sheetData[cellIndex.rowIndex] ??= {};
sheet._sheetData[cellIndex.rowIndex]![cellIndex.columnIndex] = cell;
}
cell._comment = comment;
}
}
}

/// Parses and processes merged cells within the spreadsheet.
Expand Down Expand Up @@ -673,6 +700,49 @@ class Parser {
return buffer.toString();
}

/// Returns the comments for each sheet in the workbook.
///
/// Extracted from the comments sheet from the workbook.
Map<String, Map<String, String>> _parseComments(Archive archive) {
final comments = <String, Map<String, String>>{};
final sheetNames = _excel._sheetMap.keys.toList();

for (final sheetName in sheetNames) {
final sheetComments = <String, String>{};
final commentsFile = _getCommentsFileForSheet(archive, sheetName);

if (commentsFile != null && commentsFile.name.isNotEmpty) {
final document = XmlDocument.parse(utf8.decode(commentsFile.content));
for (final comment in document.findAllElements('comment')) {
final ref = comment.getAttribute('ref');
final text = comment.findElements('text').first.innerText;
if (ref != null) {
sheetComments[ref] = text;
}
}
}

if (sheetComments.isNotEmpty) {
comments[sheetName] = sheetComments;
}
}

return comments;
}

/// Returns the comments file for the given [sheetName].
///
/// The [sheetName] is used to determine the index of the sheet in the workbook
ArchiveFile? _getCommentsFileForSheet(Archive archive, String sheetName) {
final sheetIndex = _excel._sheetMap.keys.toList().indexOf(sheetName) + 1;
final commentsFileName = 'xl/comments$sheetIndex.xml';

return archive.files.firstWhere(
(file) => file.name == commentsFileName,
orElse: () => ArchiveFile('', 0, []), // Return an empty ArchiveFile
);
}

int _getAvailableRid() {
_rId.sort((a, b) {
return int.parse(a.substring(3)).compareTo(int.parse(b.substring(3)));
Expand Down
12 changes: 11 additions & 1 deletion lib/src/sheet/data_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ part of excel;
class Data extends Equatable {
CellStyle? _cellStyle;
CellValue? _value;
String? _comment;
Sheet _sheet;
String _sheetName;
int _rowIndex;
Expand All @@ -18,6 +19,7 @@ class Data extends Equatable {
dataObject._rowIndex,
dataObject.columnIndex,
value: dataObject._value,
comment: dataObject._comment,
cellStyleVal: dataObject._cellStyle,
);

Expand All @@ -32,12 +34,14 @@ class Data extends Equatable {
NumFormat? numberFormat,
CellStyle? cellStyleVal,
bool isFormulaVal = false,
String? comment,
}) : _sheet = sheet,
_value = value,
_cellStyle = cellStyleVal,
_sheetName = sheet.sheetName,
_rowIndex = row,
_columnIndex = column;
_columnIndex = column,
_comment = comment;

/// returns the newData object when called from Sheet Class
static Data newData(Sheet sheet, int row, int column) {
Expand Down Expand Up @@ -97,13 +101,19 @@ class Data extends Equatable {
_cellStyle = _;
}

/// returns the comment stored in this cell;
///
/// It will return `null` if no comment is stored in this cell.
String? get comment => _comment;

@override
List<Object?> get props => [
_value,
_columnIndex,
_rowIndex,
_cellStyle,
_sheetName,
_comment,
];
}

Expand Down
5 changes: 5 additions & 0 deletions lib/src/sheet/sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ class Sheet {
return _sheetData[cellIndex.rowIndex]![cellIndex.columnIndex]!;
}

Data? _getCell(String cellRef) {
final cellIndex = CellIndex.indexByString(cellRef);
return _sheetData[cellIndex.rowIndex]?[cellIndex.columnIndex];
}

///
/// returns `2-D dynamic List` of the sheet elements
///
Expand Down
135 changes: 135 additions & 0 deletions test/excel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1101,4 +1101,139 @@ void main() {
reason: 'Decoding the file should not throw any exception',
);
});

test('Read cell comments and values', () {
var file = './test/test_resources/comments.xlsx';
var bytes = File(file).readAsBytesSync();
var excel = Excel.decodeBytes(bytes);

// Sheet1
var sheet1 = excel['Sheet1'];

// Read the comment and value from cell A1 Sheet 1
var cellA1Sheet1 = sheet1.cell(CellIndex.indexByString('A1'));
var commentA1Sheet1 = cellA1Sheet1.comment;
var valueA1Sheet1 = cellA1Sheet1.value;
expect(commentA1Sheet1, null);
expect(valueA1Sheet1.toString(), 'Value 1');

// Read the comment and value from cell A2 Sheet 1
var cellA2Sheet1 = sheet1.cell(CellIndex.indexByString('A2'));
var commentA2Sheet1 = cellA2Sheet1.comment;
var valueA2Sheet1 = cellA2Sheet1.value;
expect(commentA2Sheet1, 'A2Sheet1: This is a random sample comment');
expect(valueA2Sheet1.toString(), 'Value 2');

// Read the comment and value from cell A3 Sheet 1
var cellA3Sheet1 = sheet1.cell(CellIndex.indexByString('A3'));
var commentA3Sheet1 = cellA3Sheet1.comment;
var valueA3Sheet1 = cellA3Sheet1.value;
expect(commentA3Sheet1, 'A3Sheet1: Another comment');
expect(valueA3Sheet1.toString(), 'Value 3');

// Read the comment and value from cell A4 Sheet 1
var cellA4Sheet1 = sheet1.cell(CellIndex.indexByString('A4'));
var commentA4Sheet1 = cellA4Sheet1.comment;
var valueA4Sheet1 = cellA4Sheet1.value;
expect(commentA4Sheet1, null);
expect(valueA4Sheet1.toString(), 'Value 4');

// Read the comment and value from cell A5 Sheet 1
var cellA5Sheet1 = sheet1.cell(CellIndex.indexByString('A5'));
var commentA5Sheet1 = cellA5Sheet1.comment;
var valueA5Sheet1 = cellA5Sheet1.value;
expect(commentA5Sheet1, 'A5Sheet1: Comment with different font and size');
expect(valueA5Sheet1.toString(), 'Value 5');

// Read the comment and value from cell B2 Sheet 1
var cellB2Sheet1 = sheet1.cell(CellIndex.indexByString('B2'));
var commentB2Sheet1 = cellB2Sheet1.comment;
var valueB2Sheet1 = cellB2Sheet1.value;
expect(commentB2Sheet1, 'B2Sheet1: Empty Value');
expect(valueB2Sheet1, null);

// Another Sheet
var anotherSheet = excel['Different Name'];

// Read the comment and value from cell A1 Another Sheet
var cellA1AnotherSheet = anotherSheet.cell(CellIndex.indexByString('A1'));
var commentA1AnotherSheet = cellA1AnotherSheet.comment;
var valueA1AnotherSheet = cellA1AnotherSheet.value;
expect(commentA1AnotherSheet, null);
expect(valueA1AnotherSheet.toString(), '1 Value');

// Read the comment and value from cell A2 Another Sheet
var cellA2AnotherSheet = anotherSheet.cell(CellIndex.indexByString('A2'));
var commentA2AnotherSheet = cellA2AnotherSheet.comment;
var valueA2AnotherSheet = cellA2AnotherSheet.value;
expect(commentA2AnotherSheet, 'A2DN: Empty Value');
expect(valueA2AnotherSheet, null);

// Read the comment and value from cell A3 Another Sheet
var cellA3AnotherSheet = anotherSheet.cell(CellIndex.indexByString('A3'));
var commentA3AnotherSheet = cellA3AnotherSheet.comment;
var valueA3AnotherSheet = cellA3AnotherSheet.value;
expect(commentA3AnotherSheet, 'A3DN: Another comment');
expect(valueA3AnotherSheet.toString(), 'Value 3');

// Read the comment and value from cell A4 Another Sheet
var cellA4AnotherSheet = anotherSheet.cell(CellIndex.indexByString('A4'));
var commentA4AnotherSheet = cellA4AnotherSheet.comment;
var valueA4AnotherSheet = cellA4AnotherSheet.value;
expect(commentA4AnotherSheet, null);
expect(valueA4AnotherSheet.toString(), 'Value 4');

// Read the comment and value from cell A5 Another Sheet
var cellA5AnotherSheet = anotherSheet.cell(CellIndex.indexByString('A5'));
var commentA5AnotherSheet = cellA5AnotherSheet.comment;
var valueA5AnotherSheet = cellA5AnotherSheet.value;
expect(commentA5AnotherSheet, 'A5DN: Comment with different font and size');
expect(valueA5AnotherSheet.toString(), 'Value 5');

// Read the comment and value from cell B2 Another Sheet
var cellB2AnotherSheet = anotherSheet.cell(CellIndex.indexByString('B2'));
var commentB2AnotherSheet = cellB2AnotherSheet.comment;
var valueB2AnotherSheet = cellB2AnotherSheet.value;
expect(commentB2AnotherSheet, 'B2DN: Random Comment');
expect(valueB2AnotherSheet.toString(), '2 Value');

// Read the comment and value from cell C3 Another Sheet
var cellC3AnotherSheet = anotherSheet.cell(CellIndex.indexByString('C3'));
var commentC3AnotherSheet = cellC3AnotherSheet.comment;
var valueC3AnotherSheet = cellC3AnotherSheet.value;
expect(commentC3AnotherSheet, 'C3DN: C3');
expect(valueC3AnotherSheet.toString(), '3 Value');

// Read the comment and value from cell D4 Another Sheet
var cellD4AnotherSheet = anotherSheet.cell(CellIndex.indexByString('D4'));
var commentD4AnotherSheet = cellD4AnotherSheet.comment;
var valueD4AnotherSheet = cellD4AnotherSheet.value;
expect(commentD4AnotherSheet, null);
expect(valueD4AnotherSheet.toString(), '4 Value');

// Read the comment and value from cell E5 Another Sheet
var cellE5AnotherSheet = anotherSheet.cell(CellIndex.indexByString('E5'));
var commentE5AnotherSheet = cellE5AnotherSheet.comment;
var valueE5AnotherSheet = cellE5AnotherSheet.value;
expect(commentE5AnotherSheet, null);
expect(valueE5AnotherSheet.toString(), '5 Value');
});

test('Check sheet visibility', () {
var file = './test/test_resources/hidden_sheets.xlsx';
var bytes = File(file).readAsBytesSync();
var excel = Excel.decodeBytes(bytes);
// Get the visibility status of sheets
Map<String, String> sheetVisibility = excel.getSheetsVisibility();

// Expected visibility status
Map<String, String> expectedVisibility = {
'Sheet1': 'visible',
'Sheet2': 'hidden',
'Sheet3': 'visible',
};

// Check if the visibility status matches the expected values
expect(sheetVisibility, equals(expectedVisibility));
});
}
Binary file added test/test_resources/comments.xlsx
Binary file not shown.
Binary file added test/test_resources/hidden_sheets.xlsx
Binary file not shown.