import 'dart:convert'; import 'package:html/parser.dart' as html; import '../../domain/exam_schedule.dart'; class SchoolExamAdapter { static ExamSchedule extract(String content) { try { if (utf8.encode(content).length > 2 * 1024 * 1024) { throw const FormatException(); } final document = html.parse(content); final selected = document.querySelector('#xqbh option[selected]'); final term = selected?.attributes['value']; final table = document.querySelector('#sample-table-1'); const headers = [ 'id', '序号', '学号', '姓名', '课程代码', '课程名称', '考核方式', '考生编号', '考场顺序号', '考场', '座位号', '考试时间', ]; if (selected == null || term == null || table == null || !RegExp(r'^\d+$').hasMatch(term)) { throw const FormatException(); } final rows = table.querySelectorAll('tr'); if (rows.isEmpty || rows.first.children.length != headers.length) { throw const FormatException(); } for (var i = 0; i < headers.length; i++) { if (rows.first.children[i].text.trim() != headers[i]) { throw const FormatException(); } } final records = []; var explicitlyEmpty = false; for (final row in rows.skip(1)) { // DataTables can insert a single explicit no-data placeholder. if (row.children.length == 1 && row.children.first.attributes['colspan'] == '12' && const [ '没有数据', '暂无数据', '无数据', 'No data available in table', ].contains(row.text.trim())) { explicitlyEmpty = true; continue; } if (row.children.length != 12 || row.querySelector('script,style') != null) { throw const FormatException(); } final cells = row.children.map((c) => c.text.trim()).toList(); records.add( ExamRecord( code: cells[4], name: cells[5], assessment: cells[6], candidate: cells[7], sequence: cells[8], room: cells[9], seat: cells[10], time: cells[11], ), ); } if (records.isEmpty && !explicitlyEmpty) throw const FormatException(); return ExamSchedule.fromJson( ExamSchedule( semesterLabel: selected.text.trim(), termId: term, records: records, ).toJson(), ); } catch (_) { throw const FormatException('未识别到完整的考试查询表,已有考试信息保持不变。'); } } }