refactor: introduce service layer and migrate gRPC handlers to use it
All checks were successful
Build / build (push) Successful in 2m17s
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.
This commit is contained in:
@@ -2,82 +2,15 @@ package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"schedule_converter/client"
|
||||
"schedule_converter/config"
|
||||
"schedule_converter/models"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type EmptyClassroomEntry struct {
|
||||
Classroom string `json:"classroom"`
|
||||
Weekday string `json:"weekday"`
|
||||
Periods string `json:"periods"`
|
||||
Term string `json:"term"`
|
||||
Weeks string `json:"weeks"`
|
||||
}
|
||||
|
||||
type buildingInfo struct {
|
||||
DM string `json:"dm"`
|
||||
MC string `json:"mc"`
|
||||
}
|
||||
|
||||
type venueInfo struct {
|
||||
DM string `json:"dm"`
|
||||
MC string `json:"mc"`
|
||||
}
|
||||
|
||||
func FetchEmptyClassrooms(httpClient *http.Client, semester, weekStart, weekEnd, campusCode string) ([]EmptyClassroomEntry, error) {
|
||||
url := config.Get().BaseURL + "/kjscx/queryKjs"
|
||||
log := slog.With("semester", semester, "week_start", weekStart, "week_end", weekEnd)
|
||||
log.Info("获取空教室数据")
|
||||
|
||||
// First GET to establish session
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("访问空教室页面失败: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// POST with query parameters
|
||||
data := fmt.Sprintf("pageXnxq=%s&pageZc1=%s&pageZc2=%s&pageXiaoqu=%s",
|
||||
semester, weekStart, weekEnd, campusCode)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", url)
|
||||
|
||||
resp, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询空教室失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("查询空教室失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
html, err := decodeResponseBody(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entries, err := parseEmptyClassroomHTML(html, semester, weekStart, weekEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("空教室数据获取完成", "count", len(entries))
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func parseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]EmptyClassroomEntry, error) {
|
||||
// ParseEmptyClassroomHTML 解析空教室查询结果 HTML,返回空教室条目列表。
|
||||
func ParseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]models.EmptyClassroomEntry, error) {
|
||||
if html == "" {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -94,7 +27,7 @@ func parseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]Empty
|
||||
dayNames := [7]string{"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"}
|
||||
periodLabels := [6]string{"1,2", "3,4", "5,6", "7,8", "9,10", "11,12"}
|
||||
|
||||
var classrooms []EmptyClassroomEntry
|
||||
var classrooms []models.EmptyClassroomEntry
|
||||
|
||||
// Find the main data table with class="dataTable"
|
||||
doc.Find("table.dataTable").Each(func(_ int, table *goquery.Selection) {
|
||||
@@ -201,7 +134,7 @@ func parseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]Empty
|
||||
if weekStart != "" && weekEnd != "" {
|
||||
weekStr = fmt.Sprintf("%s-%s周", weekStart, weekEnd)
|
||||
}
|
||||
entry := EmptyClassroomEntry{
|
||||
entry := models.EmptyClassroomEntry{
|
||||
Classroom: classroomName,
|
||||
Weekday: strings.Join(weekdays, ", "),
|
||||
Periods: strings.Join(slots, "\n"),
|
||||
@@ -216,69 +149,7 @@ func parseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]Empty
|
||||
return classrooms, nil
|
||||
}
|
||||
|
||||
func fetchBuildings(httpClient *http.Client, campusCode string) ([]buildingInfo, error) {
|
||||
url := config.Get().BaseURL + "/kjscx/queryJxlListBySjid"
|
||||
slog.Info("获取楼号列表", "campus", campusCode)
|
||||
|
||||
data := fmt.Sprintf("id=%s", campusCode)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", config.Get().BaseURL+"/kjscx/queryKjs")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取楼号列表失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("获取楼号列表失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var buildings []buildingInfo
|
||||
if err := decodeJSONResponse(resp, &buildings); err != nil {
|
||||
return nil, fmt.Errorf("解析楼号列表失败: %w", err)
|
||||
}
|
||||
|
||||
return buildings, nil
|
||||
}
|
||||
|
||||
func fetchVenues(httpClient *http.Client, buildingCode string) ([]venueInfo, error) {
|
||||
url := config.Get().BaseURL + "/kjscx/queryJxcdListBySjid"
|
||||
slog.Info("获取场地列表", "building", buildingCode)
|
||||
|
||||
data := fmt.Sprintf("id=%s", buildingCode)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", config.Get().BaseURL+"/kjscx/queryKjs")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取场地列表失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("获取场地列表失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var venues []venueInfo
|
||||
if err := decodeJSONResponse(resp, &venues); err != nil {
|
||||
return nil, fmt.Errorf("解析场地列表失败: %w", err)
|
||||
}
|
||||
|
||||
return venues, nil
|
||||
}
|
||||
|
||||
// TestParseEmptyClassroomHTML is a test helper function that exports the parser for testing
|
||||
func TestParseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]EmptyClassroomEntry, error) {
|
||||
return parseEmptyClassroomHTML(html, semester, weekStart, weekEnd)
|
||||
func TestParseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]models.EmptyClassroomEntry, error) {
|
||||
return ParseEmptyClassroomHTML(html, semester, weekStart, weekEnd)
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
func decodeResponseBody(resp *http.Response) (string, error) {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return decodeBody(body, resp.Header.Get("Content-Type")), nil
|
||||
}
|
||||
|
||||
func decodeBody(body []byte, contentType string) string {
|
||||
lowerCT := strings.ToLower(contentType)
|
||||
if strings.Contains(lowerCT, "charset=gb") || strings.Contains(lowerCT, "charset=gb2312") || strings.Contains(lowerCT, "charset=gbk") {
|
||||
decoded, err := decodeGBK(body)
|
||||
if err == nil {
|
||||
return decoded
|
||||
}
|
||||
}
|
||||
|
||||
if isValidUTF8(body) {
|
||||
return string(body)
|
||||
}
|
||||
|
||||
decoded, err := decodeGBK(body)
|
||||
if err == nil {
|
||||
return decoded
|
||||
}
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func decodeGBK(data []byte) (string, error) {
|
||||
reader := transform.NewReader(bytes.NewReader(data), simplifiedchinese.GBK.NewDecoder())
|
||||
decoded, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(decoded), nil
|
||||
}
|
||||
|
||||
func isValidUTF8(data []byte) bool {
|
||||
for i := 0; i < len(data); i++ {
|
||||
if data[i]&0x80 != 0 {
|
||||
if data[i]&0xE0 == 0xC0 {
|
||||
if i+1 >= len(data) || data[i+1]&0xC0 != 0x80 {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
} else if data[i]&0xF0 == 0xE0 {
|
||||
if i+2 >= len(data) || data[i+1]&0xC0 != 0x80 || data[i+2]&0xC0 != 0x80 {
|
||||
return false
|
||||
}
|
||||
i += 2
|
||||
} else if data[i]&0xF8 == 0xF0 {
|
||||
if i+3 >= len(data) || data[i+1]&0xC0 != 0x80 || data[i+2]&0xC0 != 0x80 || data[i+3]&0xC0 != 0x80 {
|
||||
return false
|
||||
}
|
||||
i += 3
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func decodeJSONResponse(resp *http.Response, v any) error {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewDecoder(bytes.NewReader(body)).Decode(v)
|
||||
}
|
||||
@@ -1,107 +1,38 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"schedule_converter/client"
|
||||
"schedule_converter/config"
|
||||
"schedule_converter/models"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type ExamType string
|
||||
|
||||
const (
|
||||
ExamTypeFinal ExamType = "final"
|
||||
ExamTypeMidterm ExamType = "midterm"
|
||||
ExamTypeMakeup ExamType = "makeup"
|
||||
)
|
||||
|
||||
type ExamEntry struct {
|
||||
Course string
|
||||
Date string
|
||||
Time string
|
||||
Week string
|
||||
Weekday string
|
||||
Type ExamType
|
||||
}
|
||||
|
||||
func examTypeToCode(t ExamType) string {
|
||||
// examTypeToCode 将考试类型映射为教务系统的请求码。
|
||||
func examTypeToCode(t models.ExamType) string {
|
||||
switch t {
|
||||
case ExamTypeMidterm:
|
||||
case models.ExamTypeMidterm:
|
||||
return "02"
|
||||
case ExamTypeMakeup:
|
||||
case models.ExamTypeMakeup:
|
||||
return "03"
|
||||
default:
|
||||
return "01"
|
||||
}
|
||||
}
|
||||
|
||||
func FetchExams(httpClient *http.Client, examType ExamType) ([]ExamEntry, error) {
|
||||
examURL := config.Get().BaseURL + "/kscx/queryKsForXs"
|
||||
log := slog.With("exam_type", string(examType))
|
||||
log.Info("获取考试时间数据")
|
||||
|
||||
data := fmt.Sprintf("xnxq=&kssjd=%s", examTypeToCode(examType))
|
||||
req, err := http.NewRequest("POST", examURL, strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("获取考试数据失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
html, err := decodeResponseBody(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exams, err := parseExamTimeHTML(html, examType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("考试数据获取完成", "exam_count", len(exams))
|
||||
return exams, nil
|
||||
// ExamTypeCode 返回考试类型对应的请求码(供 client 层构造请求使用)。
|
||||
func ExamTypeCode(t models.ExamType) string {
|
||||
return examTypeToCode(t)
|
||||
}
|
||||
|
||||
func FetchAllExams(httpClient *http.Client) ([]ExamEntry, error) {
|
||||
var allExams []ExamEntry
|
||||
var lastErr error
|
||||
for _, et := range []ExamType{ExamTypeFinal, ExamTypeMidterm, ExamTypeMakeup} {
|
||||
exams, err := FetchExams(httpClient, et)
|
||||
if err != nil {
|
||||
slog.Warn("获取考试数据失败", "exam_type", string(et), "error", err)
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
allExams = append(allExams, exams...)
|
||||
}
|
||||
if len(allExams) == 0 && lastErr != nil {
|
||||
return nil, fmt.Errorf("所有考试类型获取均失败: %w", lastErr)
|
||||
}
|
||||
return allExams, nil
|
||||
}
|
||||
|
||||
func parseExamTimeHTML(html string, examType ExamType) ([]ExamEntry, error) {
|
||||
// ParseExamHTML 解析考试安排页面 HTML,返回考试条目列表。
|
||||
func ParseExamHTML(html string, examType models.ExamType) ([]models.ExamEntry, error) {
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var exams []ExamEntry
|
||||
var exams []models.ExamEntry
|
||||
|
||||
doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) {
|
||||
rows := table.Find("tr")
|
||||
@@ -120,7 +51,7 @@ func parseExamTimeHTML(html string, examType ExamType) ([]ExamEntry, error) {
|
||||
|
||||
texts := getCellTexts(cells)
|
||||
|
||||
exam := ExamEntry{
|
||||
exam := models.ExamEntry{
|
||||
Course: safeGet(texts, 1),
|
||||
Date: safeGet(texts, 2),
|
||||
Time: safeGet(texts, 3),
|
||||
@@ -136,4 +67,4 @@ func parseExamTimeHTML(html string, examType ExamType) ([]ExamEntry, error) {
|
||||
})
|
||||
|
||||
return exams, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"schedule_converter/client"
|
||||
"schedule_converter/config"
|
||||
"schedule_converter/models"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
@@ -23,32 +18,6 @@ type weekRoomEntry struct {
|
||||
room string
|
||||
}
|
||||
|
||||
// 获取课表
|
||||
// FetchScheduleWithClient 使用指定的 HTTP 客户端和学期获取课表
|
||||
func FetchScheduleWithClient(httpClient *http.Client, semester string) ([]models.Course, error) {
|
||||
// POST请求获取课表
|
||||
data := fmt.Sprintf("xnxq=%s", semester)
|
||||
req, err := http.NewRequest("POST", config.Get().BaseURL+"/kbcx/queryGrkb", strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseScheduleHTML(string(body))
|
||||
}
|
||||
|
||||
// 解析课表HTML
|
||||
func ParseScheduleHTML(html string) ([]models.Course, error) {
|
||||
// 创建一个带Reader的Document
|
||||
|
||||
126
parser/score.go
126
parser/score.go
@@ -1,128 +1,21 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"schedule_converter/client"
|
||||
"schedule_converter/config"
|
||||
"schedule_converter/models"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type ScoreType string
|
||||
|
||||
const (
|
||||
ScoreTypeFinal ScoreType = "final"
|
||||
ScoreTypeMidterm ScoreType = "midterm"
|
||||
)
|
||||
|
||||
type GradeEntry struct {
|
||||
Term string
|
||||
Code string
|
||||
Name string
|
||||
Nature string
|
||||
Category string
|
||||
Credit string
|
||||
Score string
|
||||
GradePoint string
|
||||
IsExam string
|
||||
}
|
||||
|
||||
type GPASummary struct {
|
||||
AverageGPA string
|
||||
Rank string
|
||||
ExamTotalGPA string
|
||||
ExamTotalCredits string
|
||||
FailCredits string
|
||||
Semesters string
|
||||
}
|
||||
|
||||
func FetchGrades(httpClient *http.Client, examType ScoreType) ([]GradeEntry, error) {
|
||||
var gradeURL string
|
||||
switch examType {
|
||||
case ScoreTypeMidterm:
|
||||
gradeURL = config.Get().BaseURL + "/cjcx/queryQzcj"
|
||||
default:
|
||||
gradeURL = config.Get().BaseURL + "/cjcx/queryQmcj"
|
||||
}
|
||||
|
||||
log := slog.With("exam_type", string(examType))
|
||||
log.Info("获取成绩数据")
|
||||
|
||||
req, err := http.NewRequest("GET", gradeURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = client.GetHeaders()
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("获取成绩失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
html, err := decodeResponseBody(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grades, err := parseGradesHTML(html)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("成绩获取完成", "grade_count", len(grades))
|
||||
return grades, nil
|
||||
}
|
||||
|
||||
func FetchGPA(httpClient *http.Client) (*GPASummary, []GradeEntry, error) {
|
||||
gpaURL := config.Get().BaseURL + "/xfj/queryListXfj"
|
||||
slog.Info("获取学分绩数据")
|
||||
|
||||
req, err := http.NewRequest("GET", gpaURL, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header = client.GetHeaders()
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf("获取学分绩失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
html, err := decodeResponseBody(resp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
summary, grades, err := parseGPAHTML(html)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
slog.Info("学分绩获取完成", "grade_count", len(grades))
|
||||
return summary, grades, nil
|
||||
}
|
||||
|
||||
func parseGradesHTML(html string) ([]GradeEntry, error) {
|
||||
// ParseGradesHTML 解析成绩页面 HTML,返回成绩条目列表。
|
||||
func ParseGradesHTML(html string) ([]models.GradeEntry, error) {
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var grades []GradeEntry
|
||||
var grades []models.GradeEntry
|
||||
|
||||
doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) {
|
||||
rows := table.Find("tr")
|
||||
@@ -146,7 +39,7 @@ func parseGradesHTML(html string) ([]GradeEntry, error) {
|
||||
|
||||
isMidterm := len(texts) == 10
|
||||
|
||||
entry := GradeEntry{
|
||||
entry := models.GradeEntry{
|
||||
Term: safeGet(texts, 1),
|
||||
Code: safeGet(texts, 3),
|
||||
Name: safeGet(texts, 4),
|
||||
@@ -183,13 +76,14 @@ func parseGradesHTML(html string) ([]GradeEntry, error) {
|
||||
return grades, nil
|
||||
}
|
||||
|
||||
func parseGPAHTML(html string) (*GPASummary, []GradeEntry, error) {
|
||||
// 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 := &GPASummary{}
|
||||
summary := &models.GPASummary{}
|
||||
|
||||
doc.Find("span").Each(func(_ int, span *goquery.Selection) {
|
||||
id, _ := span.Attr("id")
|
||||
@@ -210,7 +104,7 @@ func parseGPAHTML(html string) (*GPASummary, []GradeEntry, error) {
|
||||
}
|
||||
})
|
||||
|
||||
var grades []GradeEntry
|
||||
var grades []models.GradeEntry
|
||||
|
||||
doc.Find("table.bot_line").Each(func(_ int, table *goquery.Selection) {
|
||||
rows := table.Find("tr")
|
||||
@@ -229,7 +123,7 @@ func parseGPAHTML(html string) (*GPASummary, []GradeEntry, error) {
|
||||
|
||||
texts := getCellTexts(cells)
|
||||
|
||||
entry := GradeEntry{
|
||||
entry := models.GradeEntry{
|
||||
Term: safeGet(texts, 0),
|
||||
Code: safeGet(texts, 1),
|
||||
Name: safeGet(texts, 2),
|
||||
|
||||
Reference in New Issue
Block a user