Files
vyatsu-schedule/app/src/main/java/agzam/vyatsu/data/remote/TeacherScheduleParser.kt
T

128 lines
5.2 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String>, val days: List<Day>)
fun parse(html: String): ParsedTeacherSchedule {
val doc = Jsoup.parse(html)
var headerFound = false
var teachers: List<String> = emptyList()
val days = mutableListOf<Day>()
var currentDate: LocalDate? = null
var currentIntervals: MutableList<TimeSlot>? = 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<Element>,
intervalMatch: MatchResult,
startCol: Int,
teachers: List<String>,
intervals: MutableList<TimeSlot>
) {
val start = LocalTime.parse(intervalMatch.groupValues[1])
val end = LocalTime.parse(intervalMatch.groupValues[2])
val lessons = mutableListOf<Lesson>()
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<Lesson> {
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()
}