LCOV - code coverage report
Current view: top level - lib/utils - event_excel_parser.dart Coverage Total Hit
Test: lcov.info Lines: 0.0 % 51 0
Test Date: 2025-08-12 03:21:51 Functions: - 0 0

            Line data    Source code
       1              : import 'package:excel/excel.dart';
       2              : 
       3              : /// 엑셀 파일에서 이벤트 데이터를 파싱하는 유틸리티 클래스
       4              : class EventExcelParser {
       5              :   /// 엑셀 파일 바이트 데이터에서 이벤트 데이터 목록을 추출합니다.
       6              :   /// 
       7              :   /// [bytes] 엑셀 파일의 바이트 데이터
       8              :   /// 
       9              :   /// 반환값:
      10              :   /// - processedRows: 처리된 총 행 수
      11              :   /// - validEvents: 유효한 이벤트 데이터 목록
      12              :   /// - invalidRows: 유효하지 않은 행 수
      13            0 :   static Map<String, dynamic> parseExcelBytes(List<int> bytes) {
      14            0 :     final excel = Excel.decodeBytes(bytes);
      15            0 :     final events = <Map<String, dynamic>>[];
      16              :     int invalidRows = 0;
      17              :     int processedRows = 0;
      18              : 
      19              :     // 첫 번째 시트 사용
      20            0 :     final sheet = excel.tables.keys.first;
      21            0 :     final rows = excel.tables[sheet]?.rows;
      22              : 
      23            0 :     if (rows == null || rows.isEmpty) {
      24            0 :       return {
      25              :         'processedRows': 0,
      26            0 :         'validEvents': [],
      27              :         'invalidRows': 0,
      28              :         'error': '엑셀 파일에 데이터가 없습니다.'
      29              :       };
      30              :     }
      31              : 
      32              :     // 헤더 행 추출 (첫 번째 행)
      33            0 :     final headers = <String>[];
      34            0 :     for (final cell in rows[0]) {
      35            0 :       if (cell?.value != null) {
      36            0 :         headers.add(cell!.value.toString().trim().toLowerCase());
      37              :       }
      38              :     }
      39              : 
      40              :     // 필수 필드 확인
      41            0 :     if (!headers.contains('title') || !headers.contains('startdate')) {
      42            0 :       return {
      43              :         'processedRows': 0,
      44            0 :         'validEvents': [],
      45              :         'invalidRows': 0,
      46              :         'error': '필수 필드(title, startDate)가 없습니다.'
      47              :       };
      48              :     }
      49              : 
      50              :     // 데이터 행 처리 (헤더 제외)
      51            0 :     for (var i = 1; i < rows.length; i++) {
      52            0 :       processedRows++;
      53            0 :       final row = rows[i];
      54            0 :       final event = <String, dynamic>{};
      55              : 
      56              :       // 각 셀 처리
      57            0 :       for (var j = 0; j < headers.length && j < row.length; j++) {
      58            0 :         final header = headers[j];
      59            0 :         final value = row[j]?.value;
      60              : 
      61              :         // 날짜 필드 특별 처리
      62            0 :         if (header == 'startdate' || header == 'enddate') {
      63            0 :           DateTime? date = _parseDate(value);
      64              :           
      65              :           // 날짜 값 설정
      66              :           if (date != null) {
      67              :             // 헤더 이름 정규화 (startdate -> startDate)
      68            0 :             final normalizedHeader = header == 'startdate' ? 'startDate' : 'endDate';
      69            0 :             event[normalizedHeader] = date.toIso8601String();
      70              :           } else {
      71              :             // 날짜 파싱 실패 시 startDate는 필수이므로 현재 날짜 사용
      72            0 :             if (header == 'startdate') {
      73            0 :               event['startDate'] = DateTime.now().toIso8601String();
      74              :             } else {
      75              :               // endDate는 선택적이므로 null 설정
      76            0 :               event['endDate'] = null;
      77              :             }
      78              :           }
      79              :         } else {
      80              :           // 문자열 필드의 경우 빈 문자열은 null로 처리
      81            0 :           final stringValue = value?.toString().trim() ?? '';
      82              :           
      83              :           // 헤더 이름 정규화 (특별한 경우)
      84              :           String normalizedHeader = header;
      85            0 :           if (header == 'title' || header == 'description' || header == 'location' || 
      86            0 :               header == 'status' || header == 'type' || header == 'maxparticipants') {
      87              :             // 첫 글자를 소문자로 유지하고 나머지는 원래 형식 유지
      88            0 :             normalizedHeader = header == 'maxparticipants' ? 'maxParticipants' : header;
      89              :           }
      90              :           
      91              :           // 빈 문자열은 null로 처리
      92            0 :           event[normalizedHeader] = stringValue.isEmpty ? null : stringValue;
      93              :         }
      94              :       }
      95              : 
      96              :       // 필수 필드가 있는 경우만 추가
      97            0 :       if (event.containsKey('title') && event.containsKey('startDate')) {
      98            0 :         events.add(event);
      99              :       } else {
     100            0 :         invalidRows++;
     101              :       }
     102              :     }
     103              : 
     104            0 :     return {
     105              :       'processedRows': processedRows,
     106              :       'validEvents': events,
     107              :       'invalidRows': invalidRows,
     108            0 :       'error': events.isEmpty ? '유효한 이벤트 데이터가 없습니다.' : null
     109              :     };
     110              :   }
     111              : 
     112              :   /// 다양한 형식의 날짜 값을 DateTime 객체로 파싱합니다.
     113            0 :   static DateTime? _parseDate(dynamic value) {
     114              :     DateTime? date;
     115              :     
     116              :     // DateTime 타입인 경우
     117            0 :     if (value is DateTime) {
     118              :       return value;
     119              :     } 
     120              :     
     121              :     // 문자열인 경우
     122              :     if (value != null) {
     123            0 :       final strValue = value.toString();
     124              :       
     125            0 :       if (strValue.isNotEmpty) {
     126              :         // YYYY-MM-DD 형식 시도
     127              :         try {
     128            0 :           date = DateTime.parse(strValue);
     129              :         } catch (_) {
     130              :           // 실패 시 다른 형식 시도
     131              :         }
     132              :         
     133              :         // YYYY/MM/DD 형식 시도
     134            0 :         if (date == null && strValue.contains('/')) {
     135              :           try {
     136            0 :             final parts = strValue.split('/');
     137            0 :             if (parts.length == 3) {
     138            0 :               date = DateTime(
     139            0 :                 int.parse(parts[0]),
     140            0 :                 int.parse(parts[1]),
     141            0 :                 int.parse(parts[2]),
     142              :               );
     143              :             }
     144              :           } catch (_) {
     145              :             // 실패 시 무시
     146              :           }
     147              :         }
     148              :       }
     149              :     }
     150              :     
     151              :     return date;
     152              :   }
     153              : }
        

Generated by: LCOV version 2.3.1-1