getTeacher method

Future<TeacherDto> getTeacher({
  1. required String teacherId,
  2. required SemesterDto semester,
})

Fetches detailed information about a specific teacher.

Returns teacher profile information including department, title, and office hours for the given teacherId in a specific semester.

The teacherId should be a teacher code obtained from the teacher.id field of a ScheduleDto.

Implementation

Future<TeacherDto> getTeacher({
  required String teacherId,
  required SemesterDto semester,
}) async {
  final queryParams = {
    'year': semester.year,
    'sem': semester.term,
    'code': teacherId,
  };

  final (profileResponse, officeHoursResponse) = await (
    _courseDio.get(
      'Teach.jsp',
      queryParameters: {'format': '-3', ...queryParams},
    ),
    _courseDio.get(
      'Teach.jsp',
      queryParameters: {'format': '-6', ...queryParams},
    ),
  ).wait;

  // Parse format=-3: profile header
  // Structure: <th colspan="24"><a>dept</a> title name hours <a>office hours link</a></th>
  final profileDoc = parse(profileResponse.data);
  final headerTh = profileDoc.querySelector('table tr:first-child th');

  ReferenceDto? department;
  String? title;
  String? nameZh;
  double? teachingHours;

  if (headerTh != null) {
    final anchors = headerTh.querySelectorAll('a');
    if (anchors.isNotEmpty) {
      final deptAnchor = anchors.first;
      final deptHref = deptAnchor.attributes['href'];
      final deptCode = deptHref != null
          ? Uri.parse(deptHref).queryParameters['code']
          : null;
      department = (id: deptCode, name: deptAnchor.text.trim());
    }

    // Parse text segments: "dept  title  name  XX.XX 小時  office hours link"
    final fullText = headerTh.text.trim();
    final segments = fullText
        .split(RegExp(r'\s{2,}'))
        .where((s) => s.isNotEmpty)
        .toList();

    if (segments.length >= 4) {
      title = segments[1];
      nameZh = segments[2];
      final hoursMatch = RegExp(r'([\d.]+)\s*小時').firstMatch(segments[3]);
      teachingHours = hoursMatch != null
          ? double.tryParse(hoursMatch.group(1)!)
          : null;
    }
  }

  // Parse format=-6: office hours
  // Structure: plain text with <br> separators
  final officeDoc = parse(officeHoursResponse.data);
  final bodyText = officeDoc.body?.text ?? '';
  final lines = bodyText.split(RegExp(r'\n')).map((l) => l.trim()).toList();

  String? nameEn;
  final officeHours = <OfficeHourDto>[];
  String? officeHoursNote;

  for (final line in lines) {
    // Parse instructor line: "教師姓名(Instructor) 陸元平(Luh Yuan-Ping)"
    if (line.contains('Instructor')) {
      final nameMatch = RegExp(r'\(([A-Za-z\s\-]+)\)$').firstMatch(line);
      nameEn = nameMatch?.group(1);
    }

    // Parse office hours: "星期三(Wed) 10:00 ~ 13:00"
    final hourMatch = RegExp(
      r'星期[一二三四五六日]\((\w+)\)\s*(\d{1,2}:\d{2})\s*~\s*(\d{1,2}:\d{2})',
    ).firstMatch(line);
    if (hourMatch != null) {
      final dayCode = hourMatch.group(1)!;
      final day = _parseDayOfWeek(dayCode);
      final start = _parseTime(hourMatch.group(2)!);
      final end = _parseTime(hourMatch.group(3)!);
      if (day != null && start != null && end != null) {
        officeHours.add((day: day, startTime: start, endTime: end));
      }
    }

    // Parse note: "備 註(Note) ..."
    if (line.contains('Note)')) {
      final noteMatch = RegExp(r'Note\)\s*(.+)$').firstMatch(line);
      officeHoursNote = noteMatch?.group(1);
    }
  }

  return (
    department: department,
    title: title,
    nameZh: nameZh,
    nameEn: nameEn,
    teachingHours: teachingHours,
    officeHours: officeHours.isNotEmpty ? officeHours : null,
    officeHoursNote: officeHoursNote,
  );
}