diff --git a/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfScheduleParser.kt b/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfScheduleParser.kt index f7ca53c..e7bd1cc 100644 --- a/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfScheduleParser.kt +++ b/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfScheduleParser.kt @@ -2,6 +2,7 @@ package agzam.vyatsu.data.remote.pdf 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 android.util.Log import com.tom_roush.pdfbox.pdmodel.PDDocument @@ -25,6 +26,10 @@ object PdfScheduleParser { private fun timeFrom(timeString: String?): LocalTime? { return runCatching { LocalTime.parse(timeString, TIME_FORMAT) }.getOrNull() } private fun dateFrom(dateString: String?): LocalDate? { return runCatching { LocalDate.parse(dateString, DATE_FORMAT) }.getOrNull() } + private val ROOM_REGEX = Regex("""(?<=^|\s)\d{1,2}-\d{3}[_a-zA-Zа-яА-Я]?(?=\s|$)""") + private val LESSON_TYPE_REGEX = Regex(LessonType.entries.joinToString(separator = "|") { Regex.escape(it.label) }.let { """(?<=^|\s)($it)(?=\s|$)""" } ) + private val SUBGROUP_REGEX = Regex("""\d{2}\sподгруппа""", RegexOption.IGNORE_CASE) + fun parse(pdfBytes: ByteArray): List { Log.d(TAG, "Parsing ${pdfBytes.size} bytes") val doc = try { @@ -35,6 +40,8 @@ object PdfScheduleParser { } val days = mutableListOf() + var group: String? = null + try { for (pageIndex in 0 until doc.numberOfPages) { Log.v(TAG, "============ Page $pageIndex ============") @@ -100,9 +107,13 @@ object PdfScheduleParser { } } - for (dayCell in daysColumn.cells) { val day = content(dayCell).split(' ') + if(day.size == 1 && day.first() == "День") { + val groupCell = select(lessonsColumn, dayCell).firstOrNull() ?: continue + group = content(groupCell) + continue + } if(day.size != 2) continue // XXX: добавить защиту от заголовка и выбрасывать исключения вместо пропуска? val date = dateFrom(day[1]) ?: continue @@ -124,7 +135,55 @@ object PdfScheduleParser { val content = content(lessonCell) Log.v(TAG, "- $content") // TODO: парсинг контента лекции - lessons.add(Lesson(raw=content)) + + // Поиск кабинета в строке, ибо он последний, если их несколько - то скорее всего разделение на подгруппы + var found = 0 + var currentStart = 0 + for (roomMatch in ROOM_REGEX.findAll(content)) { + found++ + + val raw = content.substring(currentStart, roomMatch.range.last + 1) + currentStart = roomMatch.range.last + 1 + + // Предполагаемый формат: [группа], [XX подгруппа] <предмет> <тип> [id + тип] <ФИО> <кабинет> + + val room = roomMatch.value + + val typeMatch = LESSON_TYPE_REGEX.findAll(raw).toList() + if(typeMatch.isEmpty()) { + lessons.add(Lesson( + raw=raw, + room=room, + )) + continue + } + var subject = raw.substring(0, typeMatch.first().range.first).trim() // TODO: сейчас [группа], [XX подгруппа] <предмет> - можно достать подгруппу + val type = LessonType.fromRaw(typeMatch.first().value.trim()) + val teacher = raw.substring(typeMatch.last().range.last+1, raw.length - room.length).trim() + var subgroup: String? = null + Log.d(TAG, subject + " " + group) + if(group != null && subject.startsWith("$group,")) { + subject = subject.substring(group.length + 1).trim() + val subgroupMatch = SUBGROUP_REGEX.find(subject) + if(subgroupMatch != null) { + subgroup = subgroupMatch.value.trim() + subject = subject.removeRange(subgroupMatch.range).trim() + } + } + + + lessons.add(Lesson( + raw=raw, + subject=subject, + room=room, + type=type, + teacher=teacher, + subgroup=subgroup + )) + } + + if(found == 0) lessons.add(Lesson(raw=content)) + } intervals.add(TimeSlot(start = start, end = end, lessons = lessons)) } diff --git a/app/src/main/java/agzam/vyatsu/ui/schedule/ScheduleScreen.kt b/app/src/main/java/agzam/vyatsu/ui/schedule/ScheduleScreen.kt index 649b6aa..7e972f1 100644 --- a/app/src/main/java/agzam/vyatsu/ui/schedule/ScheduleScreen.kt +++ b/app/src/main/java/agzam/vyatsu/ui/schedule/ScheduleScreen.kt @@ -285,13 +285,18 @@ private fun LessonSlot(slotIndex: Int, slot: TimeSlot) { } else { slot.lessons.forEach { lesson -> Column(Modifier.fillMaxWidth().padding(vertical = 2.dp)) { + Text( + lesson.type.label, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( lesson.subject?.takeIf { it.isNotBlank() } ?: lesson.raw, style = MaterialTheme.typography.bodyLarge ) Text( listOfNotNull( - lesson.type.label, lesson.subgroup, lesson.teacher, lesson.room