//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format. //Copyright (C) 2024 HTWKalender support@htwkalender.de //This program is free software: you can redistribute it and/or modify //it under the terms of the GNU Affero General Public License as published by //the Free Software Foundation, either version 3 of the License, or //(at your option) any later version. //This program is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied warranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //GNU Affero General Public License for more details. //You should have received a copy of the GNU Affero General Public License //along with this program. If not, see . package v2 import ( "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/net/html" "htwkalender/model" "htwkalender/service/date" "regexp" "strconv" "strings" "time" ) // Find the first element in the HTML document func findFirstTable(node *html.Node) *html.Node { if node.Type == html.ElementNode && node.Data == "table" { return node } // Traverse child nodes recursively for child := node.FirstChild; child != nil; child = child.NextSibling { found := findFirstTable(child) if found != nil { return found } } return nil } // Find the first element with the specified class attribute value func findFirstSpanWithClass(node *html.Node, classValue string) *html.Node { // Check if the current node is a element with the specified class attribute value if node.Type == html.ElementNode && node.Data == "span" { if hasClassAttribute(node, classValue) { return node } } // Traverse child nodes recursively for child := node.FirstChild; child != nil; child = child.NextSibling { found := findFirstSpanWithClass(child, classValue) if found != nil { return found } } return nil } // Check if the specified element has the specified class attribute value func hasClassAttribute(node *html.Node, classValue string) bool { for _, attr := range node.Attr { if attr.Key == "class" && strings.Contains(attr.Val, classValue) { return true } } return false } // Get Tables with days func getEventTables(node *html.Node) [][]*html.Node { var eventTables [][]*html.Node tables := findTables(node) // get all tables with events for events := range tables { rows := findTableRows(tables[events]) // check that a first row exists if len(rows) > 0 { rows = rows[1:] eventTables = append(eventTables, rows) } } return eventTables } // Get Tables with days func getAllDayLabels(node *html.Node) []string { paragraphs := findParagraphs(node) var dayArray []string for _, p := range paragraphs { label := getDayLabel(p) if label != "" { dayArray = append(dayArray, label) } } return dayArray } // Find all

elements in the HTML document func findParagraphs(node *html.Node) []*html.Node { var paragraphs []*html.Node if node.Type == html.ElementNode && node.Data == "p" { paragraphs = append(paragraphs, node) } for child := node.FirstChild; child != nil; child = child.NextSibling { paragraphs = append(paragraphs, findParagraphs(child)...) } return paragraphs } // Find all

elements in , excluding the first one func findTableRows(node *html.Node) []*html.Node { var tableRows []*html.Node if node.Type == html.ElementNode && node.Data == "tbody" { child := node.FirstChild for child != nil { if child.Type == html.ElementNode && child.Data == "tr" { tableRows = append(tableRows, child) } child = child.NextSibling } } // Traverse child nodes recursively for child := node.FirstChild; child != nil; child = child.NextSibling { var tableRowElement = findTableRows(child) if tableRowElement != nil { tableRows = append(tableRows, tableRowElement...) } } // check if tableRows is nil if tableRows == nil { return []*html.Node{} } else { return tableRows } } // Find all

elements in the HTML document func findTables(node *html.Node) []*html.Node { var tables []*html.Node if node.Type == html.ElementNode && node.Data == "table" { tables = append(tables, node) } for child := node.FirstChild; child != nil; child = child.NextSibling { tables = append(tables, findDayTables(child)...) } return tables } // Find all

elements in the HTML document func findDayTables(node *html.Node) []*html.Node { var tables []*html.Node for child := node.FirstChild; child != nil; child = child.NextSibling { tables = append(tables, findDayTables(child)...) } if node.Type == html.ElementNode && node.Data == "table" && hasClassAttribute(node, "spreadsheet") { tables = append(tables, node) } return tables } // Get the text content of the specified node and its descendants func getDayLabel(node *html.Node) string { child := node.FirstChild if child != nil { if child.Type == html.ElementNode && child.Data == "span" { if child.FirstChild != nil { return child.FirstChild.Data } } } return "" } // Find all

func findTableData(node *html.Node) []*html.Node { var tableData []*html.Node if node.Type == html.ElementNode && node.Data == "tr" { child := node.FirstChild for child != nil { if child.Type == html.ElementNode && child.Data == "td" { tableData = append(tableData, child) } child = child.NextSibling } } return tableData } // Get the text content of the specified node and its descendants func getTextContent(node *html.Node) string { var textContent string if node.Type == html.TextNode { textContent = node.Data } for child := node.FirstChild; child != nil; child = child.NextSibling { textContent += getTextContent(child) } return textContent } func splitEventsByWeek(events []model.Event) []model.Event { var newEvents []model.Event for _, event := range events { weeks := strings.Split(event.Week, ",") for _, week := range weeks { newEvent := event newEvent.Week = strings.TrimSpace(week) newEvents = append(newEvents, newEvent) } } return newEvents } func splitEventsBySingleWeek(events []model.Event) []model.Event { var newEvents []model.Event for _, event := range events { if strings.Contains(event.Week, "-") { weeks := splitWeekRange(event.Week) for _, week := range weeks { newEvent := event newEvent.Week = week newEvents = append(newEvents, newEvent) } } else { newEvents = append(newEvents, event) } } return newEvents } func splitWeekRange(weekRange string) []string { parts := strings.Split(weekRange, "-") if len(parts) != 2 { return nil // Invalid format } start, errStart := strconv.Atoi(strings.TrimSpace(parts[0])) end, errEnd := strconv.Atoi(strings.TrimSpace(parts[1])) if errStart != nil || errEnd != nil { return nil // Error converting to integers } var weeks []string for i := start; i <= end; i++ { weeks = append(weeks, strconv.Itoa(i)) } return weeks } func extractSemesterAndYear(semesterString string) (string, string) { winterPattern := "Wintersemester" summerPattern := "Sommersemester" winterMatch := strings.Contains(semesterString, winterPattern) summerMatch := strings.Contains(semesterString, summerPattern) semester := "" semesterShortcut := "" if winterMatch { semester = "Wintersemester" semesterShortcut = "ws" } else if summerMatch { semester = "Sommersemester" semesterShortcut = "ss" } else { return "", "" } yearPattern := `\d{4}` combinedPattern := semester + `\s` + yearPattern re := regexp.MustCompile(combinedPattern) match := re.FindString(semesterString) year := "" if match != "" { reYear := regexp.MustCompile(yearPattern) year = reYear.FindString(match) } return semesterShortcut, year } func convertWeeksToDates(events []model.Event, semester string, year string) []model.Event { var newEvents []model.Event eventYear, _ := strconv.Atoi(year) // for each event we need to calculate the start and end date based on the week and the year for _, event := range events { eventWeek, _ := strconv.Atoi(event.Week) eventDay, _ := date.GetDateFromWeekNumber(eventYear, eventWeek, event.Day) start := replaceTimeForDate(eventDay, event.Start.Time()) end := replaceTimeForDate(eventDay, event.End.Time()) //Check if end is before start if end.Before(start) { end = end.AddDate(0, 0, 1) } newEvent := event newEvent.Start, _ = types.ParseDateTime(start.In(time.UTC)) newEvent.End, _ = types.ParseDateTime(end.In(time.UTC)) newEvent.Semester = semester newEvents = append(newEvents, newEvent) } return newEvents } // replaceTimeForDate replaces hour, minute, second, nsec for the selected date func replaceTimeForDate(date time.Time, replacementTime time.Time) time.Time { return time.Date(date.Year(), date.Month(), date.Day(), replacementTime.Hour(), replacementTime.Minute(), replacementTime.Second(), replacementTime.Nanosecond(), date.Location()) }
elements in the current