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 : }
|