Files

185 lines
4.7 KiB
Go
Raw Permalink Normal View History

2026-06-17 22:41:52 +08:00
package parser
import (
"fmt"
"regexp"
"strings"
"github.com/PuerkitoBio/goquery"
)
// CETScore 四六级成绩条目。
type CETScore struct {
Level string // 等级:英语四级/英语六级
ExamTime string // 考试时间:如 2025年06月
TicketNum string // 准考证号
Score string // 成绩
}
// CETStudentInfo 四六级查询中的学生信息。
type CETStudentInfo struct {
StudentID string // 学号
Name string // 姓名
Gender string // 性别
IDCard string // 身份证号
Duration string // 学制
Grade string // 年级
College string // 学院
Major string // 专业
}
// CETResult 四六级查询结果。
type CETResult struct {
StudentInfo CETStudentInfo
Scores []CETScore
}
// cetScoreRegex 匹配四六级成绩行。
// 示例:英语六级 时间 2025年06月 准考证号 372220251213105 成绩 445
var cetScoreRegex = regexp.MustCompile(`(英语[四六]级)\s+时间\s*([0-9]{4}年[0-9]{1,2}月)\s+准考证号\s*([0-9A-Za-z]+)\s+成绩\s*([0-9]+|缺考|合格|不合格|优秀|良好|通过|未通过)`)
// FetchCETHTML 解析四六级成绩 HTML 页面。
func ParseCETHTML(html string) (*CETResult, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
return nil, err
}
result := &CETResult{}
table := findCETTable(doc)
if table.Length() == 0 {
return nil, fmt.Errorf("未找到四六级成绩表格")
}
parseCETStudentInfo(table, &result.StudentInfo)
result.Scores = parseCETScores(table)
if len(result.Scores) == 0 {
result.Scores = parseCETScoresFromText(doc.Find(".jdcx_area").Text())
}
if len(result.Scores) == 0 {
result.Scores = parseCETScoresFromText(doc.Find(".Contentbox").Text())
}
if len(result.Scores) == 0 {
result.Scores = parseCETScoresFromText(doc.Text())
}
return result, nil
}
func findCETTable(doc *goquery.Document) *goquery.Selection {
selectors := []string{
"table.addlist",
".jdcx_area table",
"form#ff table",
"table:contains('已有成绩')",
}
for _, selector := range selectors {
table := doc.Find(selector).First()
if table.Length() > 0 {
return table
}
}
return doc.Find("__not_found__")
}
func parseCETStudentInfo(table *goquery.Selection, info *CETStudentInfo) {
table.Find("tr").Each(func(_ int, row *goquery.Selection) {
var label string
row.Find("th, td").Each(func(_ int, cell *goquery.Selection) {
text := normalizeCETText(cell.Text())
if text == "" {
return
}
if cell.Is("th") {
label = normalizeCETLabel(text)
return
}
assignCETStudentField(info, label, text)
label = ""
})
})
}
func assignCETStudentField(info *CETStudentInfo, label, value string) {
switch label {
case "学号":
info.StudentID = value
case "姓名":
info.Name = value
case "性别":
info.Gender = value
case "身份证号":
info.IDCard = value
case "学制":
info.Duration = value
case "年级":
info.Grade = value
case "学院":
info.College = value
case "专业":
info.Major = value
}
}
func parseCETScores(table *goquery.Selection) []CETScore {
var scores []CETScore
table.Find("tr").Each(func(_ int, row *goquery.Selection) {
rowText := normalizeCETText(row.Text())
if !strings.Contains(rowText, "英语四级") && !strings.Contains(rowText, "英语六级") {
return
}
scores = append(scores, parseCETScoresFromText(rowText)...)
})
return deduplicateCETScores(scores)
}
func parseCETScoresFromText(text string) []CETScore {
text = normalizeCETText(text)
matches := cetScoreRegex.FindAllStringSubmatch(text, -1)
scores := make([]CETScore, 0, len(matches))
for _, match := range matches {
if len(match) < 5 {
continue
}
scores = append(scores, CETScore{
Level: strings.TrimSpace(match[1]),
ExamTime: strings.TrimSpace(match[2]),
TicketNum: strings.TrimSpace(match[3]),
Score: strings.TrimSpace(match[4]),
})
}
return deduplicateCETScores(scores)
}
func deduplicateCETScores(scores []CETScore) []CETScore {
if len(scores) <= 1 {
return scores
}
seen := make(map[string]struct{}, len(scores))
result := make([]CETScore, 0, len(scores))
for _, score := range scores {
key := strings.Join([]string{score.Level, score.ExamTime, score.TicketNum, score.Score}, "|")
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
result = append(result, score)
}
return result
}
func normalizeCETLabel(label string) string {
label = normalizeCETText(label)
label = strings.ReplaceAll(label, " ", "")
label = strings.ReplaceAll(label, "", "")
label = strings.ReplaceAll(label, ":", "")
return label
}
func normalizeCETText(text string) string {
text = strings.ReplaceAll(text, "\u00a0", " ")
return strings.Join(strings.Fields(strings.TrimSpace(text)), " ")
}