getSyllabus method

  1. @override
Future<SyllabusDto?> getSyllabus({
  1. required String courseNumber,
  2. required String teacherId,
})
override

Fetches the detailed syllabus for a course offering.

Returns syllabus information including course objectives, textbooks, grading policy, and weekly plan, or null when the authoring teacher hasn't submitted one yet (the page shows 尚未登錄).

The courseNumber should be a course offering number (e.g., "346774"), and teacherId is the authoring teacher's ID (one of the teachers IDs of a ScheduleDto).

Throws an Exception if the syllabus tables are not found.

Implementation

@override
Future<SyllabusDto?> getSyllabus({
  required String courseNumber,
  required String teacherId,
}) async {
  final response = await _courseDio.get(
    'tw/ShowSyllabus.jsp',
    queryParameters: {'snum': courseNumber, 'code': teacherId},
  );

  final document = parse(response.data);

  // When the teacher hasn't submitted a syllabus, the page shows a red
  // "尚未登錄" marker in place of the 教學大綱與進度 content.
  final notSubmitted = document
      .querySelectorAll('font')
      .any((f) => f.text.trim() == '尚未登錄');
  if (notSubmitted) return null;

  final tables = document.querySelectorAll('table');
  if (tables.length < 2) {
    throw Exception('Syllabus tables not found.');
  }

  // Table 0: Header table (課程基本資料)
  // Row 1 contains: semester, number, name, phase, credits, hours, type,
  // instructor, classes, enrolled, withdrawn, remarks
  final headerRow = tables[0].querySelectorAll('tr')[1];
  final headerCells = headerRow.querySelectorAll('td');

  final typeSymbol = _parseCellText(headerCells[6]);
  final type = CourseType.values.firstWhereOrNull(
    (t) => t.symbol == typeSymbol,
  );
  final enrolled = int.tryParse(headerCells[9].text.trim());
  final withdrawn = int.tryParse(headerCells[10].text.trim());

  // Table 1: Syllabus table (教學大綱與進度)
  // Rows 0-2: Label and value both in th elements
  // Rows 3+: Label in th, value in td (some with textarea)
  final syllabusRows = tables[1].querySelectorAll('tr');

  final email = _parseCellText(syllabusRows[1].querySelectorAll('th')[1]);
  final lastUpdatedText = _parseCellText(
    syllabusRows[2].querySelectorAll('th')[1],
  );
  final lastUpdated = DateTime.tryParse(lastUpdatedText ?? '');

  // Rows 3-5 have textarea elements for long content
  final objective = _parseTextareaValue(syllabusRows[3]);
  final weeklyPlan = _parseTextareaValue(syllabusRows[4]);
  final evaluation = _parseTextareaValue(syllabusRows[5]);
  final materials = _parseTextareaValue(syllabusRows[6]);

  final remarksTd = syllabusRows[10].querySelector('td');
  final remarks = remarksTd != null ? _parseCellText(remarksTd) : null;

  return (
    type: type,
    enrolled: enrolled,
    withdrawn: withdrawn,
    email: email,
    lastUpdated: lastUpdated,
    objective: objective,
    weeklyPlan: weeklyPlan,
    evaluation: evaluation,
    materials: materials,
    remarks: remarks,
  );
}