Files
TPSchedule/mobile/lib/features/school/undergraduate/activity_data.dart
T

145 lines
4.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class UndergraduateActivity {
UndergraduateActivity(
this.teacher,
this.name,
this.courseCode,
this.room,
this.indices,
this.weeks,
);
final String teacher, name, courseCode, room;
final Set<int> indices, weeks;
bool get cancelled => room == '停课';
}
List<UndergraduateActivity> parseActivityData(
String script, {
required int unitCount,
}) {
if (unitCount != 12) throw const FormatException('本科节次结构无效');
final matches = RegExp(
r'new\s+TaskActivity\s*\((.*?)\)\s*;',
dotAll: true,
).allMatches(script).toList();
final result = <UndergraduateActivity>[];
for (var i = 0; i < matches.length; i++) {
final m = matches[i], args = _strings(m[1]!);
if (args.length != 7) throw const FormatException('课程活动字段不完整');
final suffix = RegExp(r'\(([A-Za-z0-9_.-]+)\)$').firstMatch(args[3]);
if (suffix == null || args[1].trim().isEmpty || args[5].trim().isEmpty) {
throw const FormatException('教师、教室或课程序号缺失');
}
final bitmap = args[6];
if (!RegExp(r'^0[01]+$').hasMatch(bitmap) ||
bitmap.length > 64 ||
(bitmap.length > 31 && bitmap.substring(31).contains('1'))) {
throw const FormatException('上课周次超出1–30周');
}
final weeks = {
for (var w = 1; w < bitmap.length && w <= 30; w++)
if (bitmap[w] == '1') w,
};
if (weeks.isEmpty) throw const FormatException('课程周次为空');
var end = i + 1 < matches.length ? matches[i + 1].start : script.length;
final finish = script.indexOf('table0.marshalTable', m.end);
if (finish >= 0 && finish < end) end = finish;
final body = script.substring(m.end, end);
final indices = <int>{};
final assignments = RegExp(r'\bindex\s*=\s*([^;]+);')
.allMatches(body)
.toList();
final appends = RegExp(
r'table\d+\.activities\[index\]\s*\[[^;]+?\]\s*=\s*activity\s*;',
).allMatches(body).length;
if (assignments.isEmpty || assignments.length != appends) {
throw const FormatException('课程节次赋值不完整');
}
for (final assignment in assignments) {
final expression = RegExp(r'^\s*(\d+)\s*\*\s*unitCount\s*\+\s*(\d+)\s*$')
.firstMatch(assignment[1]!);
if (expression == null) throw const FormatException('不支持的节次表达式');
final day = int.parse(expression[1]!), period = int.parse(expression[2]!);
if (day > 6 || period >= unitCount) {
throw const FormatException('课程星期或节次无效');
}
indices.add(day * unitCount + period);
}
result.add(
UndergraduateActivity(
args[1].trim(),
args[3].substring(0, suffix.start).trim(),
suffix[1]!,
args[5].trim(),
indices,
weeks,
),
);
}
return result;
}
/// Reads only quoted JS literals; expressions and calls are never evaluated.
List<String> _strings(String input) {
final out = <String>[];
var p = 0;
void spaces() {
while (p < input.length && RegExp(r'\s').hasMatch(input[p])) {
p++;
}
}
while (true) {
spaces();
if (p >= input.length) break;
final quote = input[p++];
if (quote != '"' && quote != "'") throw const FormatException('活动参数必须为字符串');
final b = StringBuffer();
var closed = false;
while (p < input.length) {
var c = input[p++];
if (c == quote) {
closed = true;
break;
}
if (c == '\\') {
if (p >= input.length) throw const FormatException('字符串转义不完整');
c = input[p++];
const escapes = {
'n': '\n',
'r': '\r',
't': '\t',
'b': '\b',
'f': '\f',
'v': '\u000b',
'0': '\u0000',
};
if (c == 'u' || c == 'x') {
final count = c == 'u' ? 4 : 2;
if (p + count > input.length) throw const FormatException('字符串转义无效');
final hex = input.substring(p, p + count);
if (!RegExp(r'^[0-9a-fA-F]+$').hasMatch(hex)) {
throw const FormatException('字符串转义无效');
}
b.writeCharCode(int.parse(hex, radix: 16));
p += count;
} else if (escapes.containsKey(c)) {
b.write(escapes[c]);
} else if (const ['"', "'", '\\', '/'].contains(c)) {
b.write(c);
} else {
throw const FormatException('不支持的字符串转义');
}
} else {
if (c == '\n' || c == '\r') throw const FormatException('字符串未闭合');
b.write(c);
}
}
if (!closed) throw const FormatException('字符串未闭合');
out.add(b.toString());
spaces();
if (p == input.length) break;
if (input[p++] != ',') throw const FormatException('活动字段分隔无效');
}
return out;
}