All checks were successful
Build / build (push) Successful in 2m17s
Extract business logic from gRPC handlers into a dedicated service package. Add context support throughout for cancellation and timeouts. Move models to their own package, remove hardcoded credentials from config, and simplify parsers to only handle HTML parsing.
162 lines
3.4 KiB
Go
162 lines
3.4 KiB
Go
package parser
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"schedule_converter/models"
|
||
|
||
"github.com/PuerkitoBio/goquery"
|
||
)
|
||
|
||
// ParseGradesHTML 解析成绩页面 HTML,返回成绩条目列表。
|
||
func ParseGradesHTML(html string) ([]models.GradeEntry, error) {
|
||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var grades []models.GradeEntry
|
||
|
||
doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) {
|
||
rows := table.Find("tr")
|
||
if rows.Length() < 2 {
|
||
return
|
||
}
|
||
|
||
rows.Each(func(idx int, row *goquery.Selection) {
|
||
if idx == 0 {
|
||
return
|
||
}
|
||
cells := row.Find("td")
|
||
if cells.Length() < 8 {
|
||
return
|
||
}
|
||
|
||
texts := getCellTexts(cells)
|
||
if len(texts) < 9 {
|
||
return
|
||
}
|
||
|
||
isMidterm := len(texts) == 10
|
||
|
||
entry := models.GradeEntry{
|
||
Term: safeGet(texts, 1),
|
||
Code: safeGet(texts, 3),
|
||
Name: safeGet(texts, 4),
|
||
Credit: safeGet(texts, 7),
|
||
}
|
||
|
||
if isMidterm {
|
||
entry.Score = safeGet(texts, 8)
|
||
if len(texts) > 9 {
|
||
entry.GradePoint = safeGet(texts, 9)
|
||
}
|
||
} else {
|
||
if len(texts) >= 13 {
|
||
entry.Nature = safeGet(texts, 3)
|
||
entry.Category = safeGet(texts, 4)
|
||
entry.Score = safeGet(texts, 12)
|
||
entry.GradePoint = safeGet(texts, 9)
|
||
entry.IsExam = safeGet(texts, 6)
|
||
} else if len(texts) >= 10 {
|
||
entry.Score = safeGet(texts, 8)
|
||
entry.GradePoint = safeGet(texts, 9)
|
||
}
|
||
}
|
||
|
||
if entry.Name != "" && len(entry.Name) >= 2 {
|
||
if entry.Score == "" {
|
||
entry.Score = "-"
|
||
}
|
||
grades = append(grades, entry)
|
||
}
|
||
})
|
||
})
|
||
|
||
return grades, nil
|
||
}
|
||
|
||
// ParseGPAHTML 解析学分绩页面 HTML,返回 GPA 概要与成绩条目列表。
|
||
func ParseGPAHTML(html string) (*models.GPASummary, []models.GradeEntry, error) {
|
||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
|
||
summary := &models.GPASummary{}
|
||
|
||
doc.Find("span").Each(func(_ int, span *goquery.Selection) {
|
||
id, _ := span.Attr("id")
|
||
text := strings.TrimSpace(span.Text())
|
||
switch id {
|
||
case "pjxfj":
|
||
summary.AverageGPA = text
|
||
case "zrs":
|
||
summary.Rank = text
|
||
case "kskxfj":
|
||
summary.ExamTotalGPA = text
|
||
case "kskxfs":
|
||
summary.ExamTotalCredits = text
|
||
case "kckxfj":
|
||
summary.FailCredits = text
|
||
case "xqs":
|
||
summary.Semesters = text
|
||
}
|
||
})
|
||
|
||
var grades []models.GradeEntry
|
||
|
||
doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) {
|
||
rows := table.Find("tr")
|
||
if rows.Length() < 2 {
|
||
return
|
||
}
|
||
|
||
rows.Each(func(idx int, row *goquery.Selection) {
|
||
if idx == 0 {
|
||
return
|
||
}
|
||
cells := row.Find("td")
|
||
if cells.Length() < 10 {
|
||
return
|
||
}
|
||
|
||
texts := getCellTexts(cells)
|
||
|
||
entry := models.GradeEntry{
|
||
Term: safeGet(texts, 0),
|
||
Code: safeGet(texts, 1),
|
||
Name: safeGet(texts, 2),
|
||
Nature: safeGet(texts, 3),
|
||
Category: safeGet(texts, 4),
|
||
IsExam: safeGet(texts, 6),
|
||
Credit: safeGet(texts, 7),
|
||
Score: safeGet(texts, 8),
|
||
GradePoint: safeGet(texts, 9),
|
||
}
|
||
|
||
if entry.Name != "" && len(entry.Name) >= 2 {
|
||
grades = append(grades, entry)
|
||
}
|
||
})
|
||
})
|
||
|
||
return summary, grades, nil
|
||
}
|
||
|
||
func getCellTexts(cells *goquery.Selection) []string {
|
||
var texts []string
|
||
cells.Each(func(_ int, cell *goquery.Selection) {
|
||
text := strings.TrimSpace(cell.Text())
|
||
texts = append(texts, text)
|
||
})
|
||
return texts
|
||
}
|
||
|
||
func safeGet(arr []string, idx int) string {
|
||
if idx < len(arr) {
|
||
return arr[idx]
|
||
}
|
||
return ""
|
||
}
|