getCourseTable method
- required String username,
- required SemesterDto semester,
Fetches the course schedule table for a specific student and semester.
Returns a list of course offerings enrolled by the student, including:
- Course details (name, credits, hours)
- Schedule information (days, periods, classroom)
- Teacher and class information
- Enrollment status and remarks
The username should be a student ID, and semester should be obtained
from getCourseSemesterList.
Throws an Exception if no courses are found for the given semester.
Implementation
Future<List<ScheduleDto>> getCourseTable({
required String username,
required SemesterDto semester,
}) async {
final response = await _courseDio.get(
'Select.jsp',
queryParameters: {
'format': '-2',
'code': username,
'year': semester.year,
'sem': semester.term,
},
);
final document = parse(response.data);
final tables = document.querySelectorAll('table');
if (tables.length < 2) {
throw Exception('Expected timetable grid and course list tables.');
}
// Parse the timetable grid (table[0]) for per-timeslot schedule+classroom
// Structure: header row has day labels (一–日), data rows have period
// labels in column 0 and course cells with <a> links for the rest.
final timetableGrid = tables[0];
final timetableRows = timetableGrid.querySelectorAll('tr');
if (timetableRows.length < 3) {
throw Exception('Timetable grid has no data rows.');
}
// Build column -> DayOfWeek map from header row
const dayCharToEnum = {
'一': DayOfWeek.monday,
'二': DayOfWeek.tuesday,
'三': DayOfWeek.wednesday,
'四': DayOfWeek.thursday,
'五': DayOfWeek.friday,
'六': DayOfWeek.saturday,
'日': DayOfWeek.sunday,
};
final headerCells = timetableRows[1].children;
final colToDayMap = <int, DayOfWeek>{};
for (var i = 1; i < headerCells.length; i++) {
final text = headerCells[i].text.trim();
final day = dayCharToEnum.entries
.firstWhereOrNull((e) => text.contains(e.key))
?.value;
if (day != null) colToDayMap[i] = day;
}
// Build schedule map keyed by course ID from the grid
final periodRegex = RegExp(r'第 (\S) 節');
final scheduleMap = <String, List<(DayOfWeek, Period, ReferenceDto?)>>{};
for (var rowIndex = 2; rowIndex < timetableRows.length; rowIndex++) {
final cells = timetableRows[rowIndex].children;
if (cells.isEmpty) continue;
final periodMatch = periodRegex.firstMatch(cells[0].text);
if (periodMatch == null) continue;
final period = Period.values.firstWhereOrNull(
(p) => p.code == periodMatch.group(1),
);
if (period == null) continue;
for (var colIndex = 1; colIndex < cells.length; colIndex++) {
final day = colToDayMap[colIndex];
if (day == null) continue;
final anchors = cells[colIndex].querySelectorAll('a');
if (anchors.isEmpty) continue;
final courseRef = _parseAnchorRef(anchors[0]);
final courseId = courseRef.id;
if (courseId == null) continue;
final classroomRef = anchors.length >= 3
? _parseAnchorRef(anchors[2])
: null;
scheduleMap.putIfAbsent(courseId, () => []);
scheduleMap[courseId]!.add((day, period, classroomRef));
}
}
// Parse the course list (table[1]) for metadata
final courseListTable = tables[1];
final tableRows = courseListTable.querySelectorAll('tr');
final trimmedTableRows = tableRows.sublist(2, tableRows.length - 1);
if (trimmedTableRows.isEmpty) {
throw Exception('No courses found in the selection table.');
}
return trimmedTableRows.map((row) {
final cells = row.children;
final number = _parseCellText(cells[0]);
final course = _parseCellRef(cells[1]);
final phase = int.tryParse(cells[2].text.trim());
final credits = double.tryParse(cells[3].text.trim());
final hours = int.tryParse(cells[4].text.trim());
final type = _parseCellText(cells[5]);
final teacher = _parseCellRef(cells[6]);
final classes = _parseCellRefs(cells[7]);
// Look up schedule+classroom from the timetable grid by course ID
final schedule = course.id != null ? scheduleMap[course.id!] : null;
final status = _parseCellText(cells[16]);
final language = _parseCellText(cells[17]);
final syllabusId = _parseCellRef(cells[18]).id;
final remarks = _parseCellText(cells[19]);
return (
number: number,
course: course,
phase: phase,
credits: credits,
hours: hours,
type: type,
teacher: teacher,
classes: classes,
schedule: schedule,
status: status,
language: language,
syllabusId: syllabusId,
remarks: remarks,
);
}).toList();
}