feat(remote): add streaming TeachersParser

This commit is contained in:
2026-09-19 14:47:46 +03:00
parent 1a4bff73d0
commit 458e4c4e69
7 changed files with 18086 additions and 29 deletions
@@ -14,30 +14,11 @@ object GroupsParser {
private val groupPattern = Regex("""/reports/schedule/Group/(\d{4,})_([12])_(\d{8})_(\d{8})\.pdf""")
private val grpIdPattern = Regex("""data-grp_period_id="(\d+)"""")
private val parenthesized = Regex("""\(.*?\)\s?""")
private const val CHUNK_SIZE = 8192
private const val MAX_SLICE = 1 shl 16 // 64 KiB — защита от аномально длинных единиц
private const val MAX_PENDING = 1 shl 20 // 1 MiB — жёсткий потолок буфера
private val shorthands = mapOf(
"Институт биологии и биотехнологии (факультет)(ОРУ)" to "ИББТ",
"Институт химии и экологии (факультет) (ОРУ)" to "ИнХимЭк",
"Факультет автоматики и вычислительной техники (ОРУ)" to "ФАВТ",
"Факультет истории, политических наук и культурологии (ОРУ)" to "ФИПНК",
"Факультет компьютерных и физико-математических наук (ОРУ)" to "ФКФМН",
"Факультет лингвистики (ОРУ)" to "ФЛ",
"Факультет менеджмента и сервиса (ОРУ)" to "ФМиС",
"Факультет педагогики и психологии (ОРУ)" to "ФПП",
"Факультет строительства и архитектуры (ОРУ)" to "ФСА",
"Факультет технологий, инжиниринга и дизайна (ОРУ)" to "ФТИД",
"Факультет физической культуры и спорта (ОРУ)" to "ФФКС",
"Факультет филологии и медиакоммуникаций (ОРУ)" to "ФФМ",
"Факультет экономики и финансов (ОРУ)" to "ФЭиФ",
"Электротехнический факультет (ОРУ)" to "ЭТФ",
"Юридический институт (факультет) (ОРУ)" to "ЮИ"
)
/**
* Стриминговый парсер страницы групп: читает тело ответа по чанкам и
* разбирает его на лету, не материализуя весь HTML в память.
@@ -80,8 +61,7 @@ object GroupsParser {
else -> minOf(divAt, aAt)
}
if (tagStart < 0) {
// хвостовой текст без div-тегов — обычный текст страницы
pos = pending.length
pos = keepPartialTagTail(pending, pos)
break
}
if (tagStart > pos) {
@@ -101,13 +81,13 @@ object GroupsParser {
val content = pending.substring(tagEnd + 1, closeDiv)
if (isFakName) {
val name = content.trim()
if (name.isNotEmpty()) faculty = shortname(name)
if (name.isNotEmpty()) faculty = FacultyNames.shortname(name)
} else {
val m = grpIdPattern.find(tagText)
if (m != null) {
groups += Group(
id = m.groupValues[1].dropLast(1),
name = removeParenthesized(content),
name = FacultyNames.removeParenthesized(content),
faculty = faculty
)
}
@@ -153,13 +133,22 @@ object GroupsParser {
return -1
}
private fun shortname(name: String): String {
val stripped = removeParenthesized(name)
val sh = shorthands[name]
return if (sh != null) "$sh ($stripped)" else stripped
/**
* Хвост буфера может содержать неполное начало тега (<div... разорванный границей чанка).
* Сдвиг перемотки назад, чтобы оставить такой префикс в pending до прихода следующего чанка.
*/
private fun keepPartialTagTail(pending: CharSequence, from: Int): Int {
var i = from
while (i < pending.length) {
val c = pending[i]
if (c == '<') {
val tail = pending.substring(i, pending.length)
if (tail == "<" || tail == "<d" || tail == "<di" || tail == "<a") return i
}
i++
}
return pending.length
}
private fun removeParenthesized(name: String): String = name.replace(parenthesized, "").trim()
}
data class ParsedGroups(