Commit 896ae7a

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-03-08 07:48:53
implement record parsing actor
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent a6c5680
lib/model/export_import/csv_converter.dart
@@ -39,7 +39,7 @@ class CsvConverter {
     return csvCreator.convert(table);
   }
 
-  /// Attempts to parse a csv string.
+  /// Attempts to parse a csv string automatically.
   /// 
   /// Validates that the first line of the file contains columns present 
   /// in [availableColumns]. When a column is present multiple times only 
@@ -60,25 +60,27 @@ class CsvConverter {
     }
 
     // Convert data to records.
-    return parseRecords(lines.map((e) => e.cast<String>()).toList(), titles, columns);
+    return parseRecords(lines, titles, columns);
   }
 
-  /// Returns
-  List<List<dynamic>> getCsvLines(String csvString) {
+  /// Parses lines from csv files according to settings.
+  /// 
+  /// Works around different EOL types n
+  List<List<String>> getCsvLines(String csvString) {
     final converter = CsvToListConverter(
       fieldDelimiter: settings.fieldDelimiter,
       textDelimiter: settings.textDelimiter,
       shouldParseNumbers: false,
     );
-    final csvLines = converter.convert(csvString, eol: '\r\n');
-    if (csvLines.length < 2) return converter.convert(csvString, eol: '\n');
+    final csvLines = converter.convert<String>(csvString, eol: '\r\n');
+    if (csvLines.length < 2) return converter.convert<String>(csvString, eol: '\n');
     return csvLines;
   }
 
   /// Map column names in the first csv-line to matching [ExportColumn].
-  Map<String, ExportColumn> getColumns(List<String> firstLine) {
+  Map<String, ExportColumn> getColumns(List<String> headline) {
     final Map<String, ExportColumn> columns = {};
-    for (final titleText in firstLine) {
+    for (final titleText in headline) {
       final formattedTitleText = titleText.trim();
       final column = availableColumns.firstWhere(
             (c) => c.csvTitle == formattedTitleText
lib/model/export_import/csv_record_parsing_actor.dart
@@ -1,27 +1,50 @@
 
 import 'package:blood_pressure_app/model/export_import/column.dart';
+import 'package:blood_pressure_app/model/export_import/csv_converter.dart';
+import 'package:blood_pressure_app/model/export_import/record_parsing_result.dart';
 
-/// A intermediate class that allows to manage record parsing.
+/// A intermediate class usable by UI components to drive csv parsing.
 class CsvRecordParsingActor {
   /// Create an intermediate object to manage a record parsing process.
-  CsvRecordParsingActor(this.csvString) {
-
+  CsvRecordParsingActor(this._converter, String csvString) {
+    final lines = _converter.getCsvLines(csvString);
+    _headline = lines.removeAt(0);
+    dataLines = lines;
+    _columnNames = _headline ?? [];
+    _columnParsers = _converter.getColumns(_columnNames);
   }
 
-  /// The initial csv String provided to the parser
-  final String csvString;
+  final CsvConverter _converter;
+
+  /// All lines without the first line.
+  late final List<List<String>> dataLines;
+
+  List<String>? _headline;
+
+  /// The first line in the csv file.
+  List<String>? get headline => _headline;
+
+  late List<String> _columnNames;
 
   /// All columns defined in the csv headline.
-  List<String> columnNames;
+  List<String> get columnNames => _columnNames;
+
+  late Map<String, ExportColumn> _columnParsers;
 
   /// The current interpretation of columns in the csv data.
   ///
   /// There is no guarantee that every column in [columnNames] has a parser.
-  Map<String, ExportColumn> columnParsers;
+  Map<String, ExportColumn> get columnParsers => _columnParsers;
 
-  final List<void Function(CsvRecordParsingActor)> _listeners = [];
+  /// Override a columns with a custom one.
+  void changeColumnParser(String columnName, ExportColumn parser) {
+    assert(_columnNames.contains(columnName));
+    _columnParsers[columnName] = parser;
+  }
 
-  /// Register a listener that gets called once a value gets changed.
-  void addListener(void Function(CsvRecordParsingActor state) listener)
-    => _listeners.add(listener);
+  /// Try to parse the data with the current configuration.
+  RecordParsingResult attemptParse() =>
+    _converter.parseRecords(dataLines, columnNames, columnParsers, false);
 }
+
+// TODO: consider joining headline and columnNames OR support no headline.
lib/screens/subsettings/export_import/export_import_screen.dart
@@ -14,6 +14,7 @@ import 'package:provider/provider.dart';
 
 /// Screen to configure and perform exports and imports of blood pressure values.
 class ExportImportScreen extends StatelessWidget {
+  /// Create a screen that shows options for ex- and importing data.
   const ExportImportScreen({super.key});
 
   @override
lib/screens/subsettings/foreign_db_import_screen.dart
@@ -19,8 +19,8 @@ class ForeignDBImportScreen extends StatefulWidget {
   /// Create a screen to import data from a database with unknown structure.
   /// 
   /// Parses selected data to a [BloodPressureRecord] list.
-  const ForeignDBImportScreen({super.key, 
-    required this.db, 
+  const ForeignDBImportScreen({super.key,
+    required this.db,
     required this.bottomAppBars,
   });
 
@@ -56,7 +56,7 @@ class _ForeignDBImportScreenState extends State<ForeignDBImportScreen> {
           } else {
             return RowDataFieldType.values
                 .whereNot((e) => e == RowDataFieldType.timestamp
-                    || selections.contains(e),)
+                || selections.contains(e),)
                 .map((e) => e.localize(localizations))
                 .toList();
           }
@@ -149,7 +149,7 @@ class _ForeignDBImportScreenState extends State<ForeignDBImportScreen> {
 
 class _ColumnImportData {
   _ColumnImportData._create(this.columns);
-  
+
   static Future<_ColumnImportData> loadFromDB(Database db) async {
     final engine = SqlEngine();
 
@@ -169,7 +169,7 @@ class _ColumnImportData {
 
         if (colNames.isNotEmpty) columns[rootNode.tableName] = colNames;
       }
-    } 
+    }
     return _ColumnImportData._create(columns);
   }
 
@@ -184,7 +184,7 @@ class _ColumnImportData {
 Future<List<BloodPressureRecord>?> showForeignDBImportDialoge(
     BuildContext context,
     bool bottomAppBars,
-    Database db) =>
+    Database db,) =>
     showDialog<List<BloodPressureRecord>>(
       context: context, builder: (context) =>
         Dialog.fullscreen(
pubspec.lock
@@ -337,26 +337,26 @@ packages:
     dependency: transitive
     description:
       name: leak_tracker
-      sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
+      sha256: f8cdf1383f5b4672a2693d875f1f239af6bd7e4a8925a17ef7219226db932624
       url: "https://pub.dev"
     source: hosted
-    version: "10.0.0"
+    version: "10.0.1"
   leak_tracker_flutter_testing:
     dependency: transitive
     description:
       name: leak_tracker_flutter_testing
-      sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
+      sha256: a2055640bf5bc903475e4bbdb34e04f8bf698542bee41edec47d337a5939e1ae
       url: "https://pub.dev"
     source: hosted
-    version: "2.0.1"
+    version: "2.0.3"
   leak_tracker_testing:
     dependency: transitive
     description:
       name: leak_tracker_testing
-      sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
+      sha256: e62042d479c4c139dd774125ed4dfbde646b8f07ac228e3c1b57a3d91d6d9df4
       url: "https://pub.dev"
     source: hosted
-    version: "2.0.1"
+    version: "2.0.2"
   lints:
     dependency: transitive
     description:
@@ -868,4 +868,4 @@ packages:
     version: "3.1.2"
 sdks:
   dart: ">=3.2.0 <4.0.0"
-  flutter: ">=3.16.0"
+  flutter: ">=3.18.0-18.0.pre.54"