From b6e77ad1fac5fc8a642274af01649e02bb3d1aab Mon Sep 17 00:00:00 2001 From: sihoo Date: Tue, 29 Sep 2026 13:05:55 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=91=EC=85=80=20=EC=9D=BC=EA=B4=84?= =?UTF-8?q?=EB=93=B1=EB=A1=9D:=20=EC=9D=BC=EB=B6=80=20=EC=97=91=EC=85=80?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=9D=BD=EA=B8=B0=20=EC=8B=A4=ED=8C=A8?= =?UTF-8?q?=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "custom numFmtId starts at 164 but found a value of 0" 에러 - 구글시트/WPS 등에서 내보낸 일부 xlsx 파일은 styles.xml에 커스텀 숫자서식 id를 164 미만으로 적어두는데, excel 패키지가 이를 엄격하게 검증하며 예외를 던져 파일 전체를 못 읽었다. 파싱 전에 zip 안의 styles.xml만 열어 그 규격 위반 서식 선언을 제거하고 다시 압축해서 넘기도록 전처리 단계를 추가. 합성 xlsx로 버그 재현 + 수정 후 정상 파싱까지 직접 검증함. Co-Authored-By: Claude Sonnet 5 --- ...teacher_student_management_controller.dart | 67 ++++++++++++++++++- pubspec.lock | 2 +- pubspec.yaml | 1 + 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/lib/function/teacher_student_management_controller.dart b/lib/function/teacher_student_management_controller.dart index deac7e9..38cea7d 100644 --- a/lib/function/teacher_student_management_controller.dart +++ b/lib/function/teacher_student_management_controller.dart @@ -1,5 +1,6 @@ // 👥 교사용 학생 계정 관리 화면의 기능(서버 통신/상태/엑셀 일괄등록) 담당 컨트롤러. import 'dart:convert'; +import 'package:archive/archive.dart'; import 'package:excel/excel.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; @@ -91,7 +92,7 @@ class TeacherStudentManagementController extends ChangeNotifier { /// A열=학번, B열=이름, C열=학년(선택, 숫자) 순서를 기대한다. /// 학번이나 이름이 비어있는 줄은 조용히 무시한다. List parseExcelBytes(Uint8List bytes) { - final excel = Excel.decodeBytes(bytes); + final excel = Excel.decodeBytes(_sanitizeXlsxBytes(bytes)); if (excel.tables.isEmpty) return []; final sheet = excel.tables.values.first; @@ -108,6 +109,70 @@ class TeacherStudentManagementController extends ChangeNotifier { return rows; } + /// 🩹 [엑셀 파싱 버그 우회] 일부 엑셀 파일(구글시트/WPS 등에서 내보낸 파일 포함)은 + /// styles.xml에 커스텀 숫자서식 id를 164 미만(예: 0)으로 잘못 적어두는데, excel + /// 패키지가 이걸 엄격하게 걸러내며 예외를 던져서 파일 전체를 못 읽게 만든다. + /// 우리는 셀 값(학번/이름/학년)만 필요하고 서식은 필요 없으니, zip 안의 styles.xml만 + /// 열어서 그 잘못된 커스텀 서식 선언을 지우고 다시 압축해서 넘긴다. + Uint8List _sanitizeXlsxBytes(Uint8List bytes) { + try { + final zip = ZipDecoder().decodeBytes(bytes); + final stylesFile = zip.files.cast().firstWhere( + (f) => f?.name == 'xl/styles.xml', + orElse: () => null, + ); + if (stylesFile == null) return bytes; + + final xml = utf8.decode(stylesFile.content as List); + final fixedXml = _stripInvalidCustomNumFmts(xml); + if (fixedXml == xml) return bytes; // 고칠 게 없었으면 원본 그대로. + + final newZip = Archive(); + for (final file in zip.files) { + if (!file.isFile) continue; + if (file.name == 'xl/styles.xml') { + final content = utf8.encode(fixedXml); + newZip.addFile(ArchiveFile('xl/styles.xml', content.length, content)); + } else { + newZip.addFile(ArchiveFile(file.name, file.size, file.content)); + } + } + final rezipped = ZipEncoder().encode(newZip); + return rezipped != null ? Uint8List.fromList(rezipped) : bytes; + } catch (_) { + return bytes; // 손대다 실패하면 원본 그대로 시도 (기존 동작 유지). + } + } + + /// styles.xml 문자열에서 `` 태그 중 N이 164 미만인 + /// (규격 위반인) 것만 통째로 제거한다. 그 서식을 참조하던 셀은 기본 서식으로 처리될 + /// 뿐이라 값 읽기에는 영향이 없다. + String _stripInvalidCustomNumFmts(String xml) { + final buffer = StringBuffer(); + int i = 0; + while (true) { + final tagStart = xml.indexOf('', tagStart); + if (tagEnd == -1) { + buffer.write(xml.substring(i)); + break; + } + buffer.write(xml.substring(i, tagStart)); + final tag = xml.substring(tagStart, tagEnd + 2); + final idMatch = RegExp(r'numFmtId="(\d+)"').firstMatch(tag); + final id = idMatch != null ? int.tryParse(idMatch.group(1)!) : null; + if (id == null || id >= 164) { + buffer.write(tag); // 정상 범위의 커스텀 서식은 그대로 둔다. + } + i = tagEnd + 2; + } + return buffer.toString(); + } + String? _cellText(CellValue? value) { if (value == null) return null; String text; diff --git a/pubspec.lock b/pubspec.lock index 19c3143..c938a38 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -18,7 +18,7 @@ packages: source: hosted version: "1.0.3" archive: - dependency: transitive + dependency: "direct main" description: name: archive sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d diff --git a/pubspec.yaml b/pubspec.yaml index afe4139..5514dcd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: # 📳 무단 반출 감지 시 진동 (백그라운드 서비스에서도 동작하도록 HapticFeedback 대신 사용) vibration: ^3.2.0 excel: ^4.0.6 + archive: ^3.6.1 file_picker: ^12.1.2 desktop_drop: ^0.8.3 web: ^1.1.1