import '../domain/course.dart'; import '../domain/school_term.dart'; import '../domain/school_identity.dart'; import 'dart:convert'; class ImportDraft { const ImportDraft({ required this.courses, required this.explicitlyEmpty, required this.source, required this.parserVersion, this.semesterLabel, this.schoolTerm, }); final List courses; final bool explicitlyEmpty; final CourseSource source; final String parserVersion; final String? semesterLabel; final SchoolTerm? schoolTerm; } class JsonScheduleImport { static ImportDraft parse(String text) { try { if (utf8.encode(text).length > 2 * 1024 * 1024) { throw const FormatException('文件不能超过 2 MB'); } final json = jsonDecode(text); if (json is! Map || json['schemaVersion'] != 1 || json['courses'] is! List || (json['isDemo'] != null && json['isDemo'] is! bool) || (json['explicitlyEmpty'] != null && json['explicitlyEmpty'] is! bool)) { throw const FormatException('请选择版本 1 的课表 JSON 文件'); } final source = json['isDemo'] == true ? CourseSource.demo : CourseSource.file; final courses = (json['courses'] as List) .map( (c) => Course.fromJson( Map.from(c as Map), source: source, ), ) .toList(); if (courses.map((c) => c.id).toSet().length != courses.length) { throw const FormatException('课程标识重复'); } final termIds = courses .map((c) => RegExp(r'^uestc-(\d+)-').firstMatch(c.id)?.group(1)) .whereType() .toSet(); final term = json['schoolTerm'] != null ? SchoolTerm.fromJson( Map.from(json['schoolTerm'] as Map), ) : termIds.length == 1 ? SchoolTerm(int.parse(termIds.single)) : null; if (termIds.length > 1 || (term != null && termIds.any( (id) => id != term.id.toString() || term.school != SupportedSchool.uestcGraduate, ))) { throw const FormatException('文件中包含不同学期的课程'); } final undergraduateIds = courses .map( (c) => RegExp(r'^uestc-undergraduate-(\d+)-') .firstMatch(c.id) ?.group(1), ) .whereType() .toSet(); if (undergraduateIds.isNotEmpty && (term?.school != SupportedSchool.uestcUndergraduate || undergraduateIds.any((id) => id != term!.id.toString())) || courses.any((c) => c.endPeriod > 11) && (source == CourseSource.demo || term?.school != SupportedSchool.uestcUndergraduate)) { throw const FormatException('本科课表来源或学期无效'); } return ImportDraft( courses: List.unmodifiable(courses), explicitlyEmpty: json['explicitlyEmpty'] == true, source: source, parserVersion: 'json-v1', schoolTerm: source == CourseSource.demo ? null : term, semesterLabel: term?.label, ); } on FormatException { rethrow; } catch (_) { throw const FormatException('课表文件格式无效'); } } }