Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ 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).

## [5.0.1-mki.1]
### Fixed
- Column widths & row heights parsing (cols/rows width/ht + defaults).
- More robust cell parsing: formula-first (<f>), safe sharedStrings lookup, full inlineStr handling, and bounds checks for numFmtId and style indices.

### Added
- Added toJson / fromJson + toString = jsonEncode(toJson()) methods
- Added generateXFile wich generate a XFile with mimeType

## [5.0.0]
- Update packages to latest versions
- Migrate from `dart:html` to `package:web`
Expand Down
1 change: 1 addition & 0 deletions lib/excel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:convert';
import 'dart:math';
import 'package:archive/archive.dart';
import 'package:collection/collection.dart';
import 'package:cross_file/cross_file.dart';
import 'package:equatable/equatable.dart';
import 'package:xml/xml.dart';
import 'src/web_helper/client_save_excel.dart'
Expand Down
180 changes: 177 additions & 3 deletions lib/src/excel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,176 @@ class Excel {
}

factory Excel.decodeBuffer(InputStream input) {
return _newExcel(ZipDecoder().decodeStream(input));
return _newExcel(ZipDecoder().decodeStream(input, verify: true));
}

factory Excel.fromJson(Map<String, dynamic> json) {
final excel = Excel.createExcel();

final defaultName = excel.sheets.keys.first;
final firstTarget = json.keys.first;
if (defaultName != firstTarget) {
excel.rename(defaultName, firstTarget);
}

if (json.isEmpty) return excel;

json.forEach((sheetName, rawRows) {
if (rawRows is! List) {
excel._availSheet(sheetName);
return;
}

final rows =
rawRows.whereType<Map<String, dynamic>>().toList(growable: false);

excel._availSheet(sheetName);

if (rows.isEmpty) return;

final headers = rows.first.keys.toList(growable: false);

excel.appendRow(
sheetName,
[for (final h in headers) TextCellValue(h)],
);

for (final record in rows) {
excel.appendRow(
sheetName,
[for (final h in headers) _toCellValue(record[h])],
);
}
});

if (excel.sheets.isNotEmpty) {
excel.setDefaultSheet(excel.sheets.keys.first);
}

return excel;
}

static CellValue? _toCellValue(dynamic v) {
if (v == null) return null;

if (v is CellValue) return v;

if (v is bool) return BoolCellValue(v);

if (v is num) {
final isInt = v == v.truncate();
return isInt ? IntCellValue(v.toInt()) : DoubleCellValue(v.toDouble());
}

if (v is String) {
final s = v.trim();

if (s.startsWith('=')) return FormulaCellValue(s);

final b = bool.tryParse(s);
if (b != null) return BoolCellValue(b);

final i = int.tryParse(s);
if (i != null) return IntCellValue(i);
final d = double.tryParse(s);
if (d != null) return DoubleCellValue(d);

final timeRe = RegExp(r'^\d{2}:\d{2}:\d{2}(?:\.\d+)?$');
if (timeRe.hasMatch(s)) {
final parts = s.split(':');
final secParts = parts[2].split('.');
final h = int.parse(parts[0]);
final m = int.parse(parts[1]);
final sec = int.parse(secParts[0]);
final ms = secParts.length > 1
? int.parse(secParts[1].padRight(3, '0').substring(0, 3))
: 0;
return TimeCellValue(hour: h, minute: m, second: sec, millisecond: ms);
}

final dt = DateTime.tryParse(s);
if (dt != null) {
final u = dt.toUtc();
final isMidnight = u.hour == 0 &&
u.minute == 0 &&
u.second == 0 &&
u.millisecond == 0 &&
u.microsecond == 0;
if (isMidnight) {
return DateCellValue(year: u.year, month: u.month, day: u.day);
} else {
return DateTimeCellValue(
year: u.year,
month: u.month,
day: u.day,
hour: u.hour,
minute: u.minute,
second: u.second,
millisecond: u.millisecond,
microsecond: u.microsecond,
);
}
}

return TextCellValue(s);
}

return TextCellValue(v.toString());
}

Map<String, dynamic> toJson() {
int index = 0;
Map<String, dynamic> json = {};

for (final sheet in this.sheets.keys) {
List<Data?> keys = [];
json.addAll({sheet: []});
final rows = sheets[sheet]?.rows ?? [];

for (final row in rows) {
try {
if (index == 0) {
keys = row;
index++;
} else {
Map<String, dynamic> temp = {};
int index = 0;
String tk = '';

for (final key in keys) {
if (key != null && key.value != null) {
tk = key.value.toString();

temp[tk] = switch (row[index]?.value) {
(final v) when v is IntCellValue => v.value,
(final v) when v is DoubleCellValue => v.value,
(final v) when v is BoolCellValue => v.value,
(final v) when v is FormulaCellValue => v.formula,
(final v) when v is TextCellValue => v.value.toString(),
(final v) when v is TimeCellValue => v.toString(),
(final v) when v is DateCellValue => v.toString(),
(final v) when v is DateTimeCellValue => v.toString(),
_ => null,
};

index++;
}
}

json[sheet].add(temp);
}
} catch (e) {
rethrow;
}
}
index = 0;
}
return json;
}

@override
String toString() {
return jsonEncode(toJson());
}

///
Expand Down Expand Up @@ -331,11 +500,16 @@ class Excel {
///
///```
List<int>? save({String fileName = 'FlutterExcel.xlsx'}) {
Save s = Save._(this, parser);
var onValue = s._save();
var onValue = encode();
return helper.SavingHelper.saveFile(onValue, fileName);
}

/// Generate XFile with mimeType and path = fileName.
XFile? generateXFile({String fileName = 'FlutterExcel.xlsx'}) {
var onValue = encode();
return helper.SavingHelper.generateXFile(onValue, fileName);
}

///
///returns the name of the `defaultSheet` (the sheet which opens firstly when xlsx file is opened in `excel based software`).
///
Expand Down
Loading