package agzam.vyatsu.data.remote import agzam.vyatsu.data.remote.pdf.TextPatterns import agzam.vyatsu.domain.model.Day import agzam.vyatsu.domain.model.Lesson import agzam.vyatsu.domain.model.LessonType import agzam.vyatsu.domain.model.TimeSlot import org.jsoup.Jsoup import org.jsoup.nodes.Element import java.time.LocalDate import java.time.LocalTime import java.time.format.DateTimeFormatter object TeacherScheduleParser { private val DAY_REGEX = Regex("""^(понедельник|вторник|среда|четверг|пятница|суббота|воскресенье)\s+(\d{2}\.\d{2}\.\d{2})$""") private val INTERVAL_REGEX = Regex("""^(\d{2}:\d{2})-(\d{2}:\d{2})$""") private val GROUP_TOKEN = Regex("""(?U)\b[А-ЯЁа-яё]{1,4}-\d{2,4}-\d{2}-\d{2}\b""") private val SPLIT_BY_GROUP = Regex("""(?U)(?=\b[А-ЯЁа-яё]{1,4}-\d{2,4}-\d{2}-\d{2}\b)""") private val DATE_FMT = DateTimeFormatter.ofPattern("dd.MM.yy") data class ParsedTeacherSchedule(val teachers: List, val days: List) fun parse(html: String): ParsedTeacherSchedule { val doc = Jsoup.parse(html) var headerFound = false var teachers: List = emptyList() val days = mutableListOf() var currentDate: LocalDate? = null var currentIntervals: MutableList? = null fun flushDay() { val date = currentDate ?: return val intervals = currentIntervals ?: return if (intervals.isNotEmpty()) days.add(Day(date, intervals.toList())) } for (tr in doc.select("tr")) { val tds = tr.children().filter { it.tagName() == "td" } if (tds.size < 2) continue val first = cellText(tds[0]) val second = cellText(tds[1]) if (!headerFound && first == "День" && second == "Интервал") { teachers = tds.drop(2) .map { cellText(it.selectFirst("span") ?: it) } .filter { it.isNotEmpty() } .distinct() headerFound = true continue } val dayMatch = DAY_REGEX.matchEntire(first) val intervalMatch = INTERVAL_REGEX.matchEntire(if (dayMatch != null) second else first) if (dayMatch != null) { flushDay() currentDate = LocalDate.parse(dayMatch.groupValues[2], DATE_FMT) currentIntervals = mutableListOf() } if (intervalMatch != null && currentIntervals != null) { parseIntervalRow(tds, intervalMatch, if (dayMatch != null) 2 else 1, teachers, currentIntervals!!) } } flushDay() return ParsedTeacherSchedule(teachers, days) } private fun parseIntervalRow( tds: List, intervalMatch: MatchResult, startCol: Int, teachers: List, intervals: MutableList ) { val start = LocalTime.parse(intervalMatch.groupValues[1]) val end = LocalTime.parse(intervalMatch.groupValues[2]) val lessons = mutableListOf() for (i in teachers.indices) { val cell = tds.getOrNull(startCol + i) ?: continue lessons += parseCell(cell, teachers[i]) } if (lessons.isNotEmpty()) intervals.add(TimeSlot(start, end, lessons)) } private fun parseCell(cell: Element, teacher: String): List { val raw = cellText(cell) if (raw.isEmpty()) return emptyList() val room = TextPatterns.ROOM_REGEX.find(raw)?.value val typeMatch = TextPatterns.LESSON_TYPE_REGEX.find(raw) val type = LessonType.fromRaw(typeMatch?.value?.trim() ?: "") val segments = raw.split(SPLIT_BY_GROUP).map { it.trim() }.filter { it.isNotEmpty() } val groupSegments = segments.filter { GROUP_TOKEN.containsMatchIn(it) } if (groupSegments.isEmpty()) { return listOf( Lesson( raw = raw, room = room, subject = subjectOf(raw, room, typeMatch), type = type, teacher = teacher ) ) } val subject = subjectOf(raw, room, typeMatch) return groupSegments.flatMap { segment -> val groups = GROUP_TOKEN.findAll(segment).map { it.value }.distinct().toList() val subgroup = TextPatterns.SUBGROUP_REGEX.find(segment)?.value groups.map { group -> Lesson( raw = segment, room = room, group = group, subgroup = subgroup, subject = subject, type = type, teacher = teacher ) } } } private fun subjectOf(raw: String, room: String?, typeMatch: MatchResult?): String? { val end = typeMatch?.range?.first ?: GROUP_TOKEN.find(raw)?.range?.first ?: raw.length val prefix = raw.substring(0, end).trim() val stripped = room?.let { prefix.removePrefix(it).trim() } ?: prefix return stripped.ifEmpty { null } } private fun cellText(el: Element): String = el.text().replace('\u00A0', ' ').trim() }