Files
TPSchedule/mobile/lib/data/json_schedule_import.dart
T

105 lines
3.4 KiB
Dart

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<Course> 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<String, dynamic>.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<String>()
.toSet();
final term = json['schoolTerm'] != null
? SchoolTerm.fromJson(
Map<String, dynamic>.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<String>()
.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('课表文件格式无效');
}
}
}