百度百科 BaikeLemmaCardApi 摘要以 ... 硬截断,出现半截话。 新增 trimTruncatedTail:去掉结尾省略号后,若末句不完整则 回退到最后一个完整句标点(。!?),避免「唐宋时期,冬至...」 这类戛然而止的结尾。仅依赖百度百科,不引入维基,线上可用。
152 lines
5.5 KiB
Go
152 lines
5.5 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// 取自百度百科 BaikeLemmaCardApi 「立春」的真实返回片段
|
|
const sampleLemmaJSON = `{
|
|
"id":25702,"key":"立春","desc":"二十四节气之一","title":"立春",
|
|
"image":"https://bkimg.cdn.bcebos.com/pic/d62a6059252dd42a2834edd463674cb5c9ea15ce1205",
|
|
"abstract":"立春(Beginning of Spring),又名正月节、岁节、改岁、岁旦、打春等,为二十四节气之首。当太阳到达黄经315°时为立春,于每年公历2月3-5日交节。",
|
|
"card":[
|
|
{"key":"m25_nameC","name":"中文名","value":["立春"]},
|
|
{"key":"m25_nameE","name":"外文名","value":["Beginning of Spring<sup>3</sup><a name=\"ref_3\"></a>","Lichun"]},
|
|
{"key":"m25_alternateName","name":"别名","value":["岁首、岁节、改岁、立春节、正月节"]},
|
|
{"key":"m25_meaning","name":"涵义","value":["阳气上升,万物更生,新岁开启"]},
|
|
{"key":"m25_solarDate","name":"公历时间","value":["公历2月3日-2月5日交节"]},
|
|
{"key":"m25_ecliptic","name":"黄道位置","value":["太阳到达黄经315度"]},
|
|
{"key":"m25_phenology","name":"物候现象","value":["东风解冻、蛰虫始振、鱼陟负冰"]},
|
|
{"key":"m25_farming","name":"农事活动","value":["备耕春播"]},
|
|
{"key":"m25_customs","name":"传统习俗","value":["<a target=_blank href=\"/item/x\">演春</a>、迎春、打春"]},
|
|
{"key":"m25_health","name":"起居养生","value":["防躁动,避免过度劳累,保证睡眠情绪豁达。"]},
|
|
{"key":"m25_flower","name":"花信","value":["迎春、樱桃、望春<sup>35</sup><a name=\"ref_35\"></a>"]}
|
|
]
|
|
}`
|
|
|
|
func TestParseLemmaCard(t *testing.T) {
|
|
var lemma baikeLemmaCard
|
|
if err := json.Unmarshal([]byte(sampleLemmaJSON), &lemma); err != nil {
|
|
t.Fatalf("JSON 解析失败: %v", err)
|
|
}
|
|
if lemma.Key != "立春" {
|
|
t.Errorf("key = %q, want 立春", lemma.Key)
|
|
}
|
|
if lemma.Image == "" {
|
|
t.Error("image 不应为空")
|
|
}
|
|
if !strings.Contains(lemma.Abstract, "二十四节气之首") {
|
|
t.Errorf("abstract 应含「二十四节气之首」, got %q", lemma.Abstract)
|
|
}
|
|
if len(lemma.Card) == 0 {
|
|
t.Fatal("card 不应为空")
|
|
}
|
|
}
|
|
|
|
func TestLemmaCardMapping(t *testing.T) {
|
|
var lemma baikeLemmaCard
|
|
json.Unmarshal([]byte(sampleLemmaJSON), &lemma)
|
|
|
|
extra := map[string]string{}
|
|
for _, c := range lemma.Card {
|
|
text := cleanTermHTML(firstValue(c.Value))
|
|
if text == "" {
|
|
continue
|
|
}
|
|
switch c.Name {
|
|
case "别名":
|
|
extra["alias"] = text
|
|
case "外文名":
|
|
extra["english"] = text
|
|
case "涵义":
|
|
extra["meaning"] = text
|
|
case "物候现象":
|
|
extra["phenology"] = text
|
|
case "传统习俗":
|
|
extra["customs"] = text
|
|
case "起居养生":
|
|
extra["health"] = text
|
|
}
|
|
}
|
|
|
|
if extra["alias"] != "岁首、岁节、改岁、立春节、正月节" {
|
|
t.Errorf("alias = %q", extra["alias"])
|
|
}
|
|
if extra["meaning"] != "阳气上升,万物更生,新岁开启" {
|
|
t.Errorf("meaning = %q", extra["meaning"])
|
|
}
|
|
if extra["phenology"] != "东风解冻、蛰虫始振、鱼陟负冰" {
|
|
t.Errorf("phenology = %q", extra["phenology"])
|
|
}
|
|
// 传统习俗含 HTML 链接,应被清洗
|
|
if strings.Contains(extra["customs"], "<a") || strings.Contains(extra["customs"], "href") {
|
|
t.Errorf("customs 应清洗 HTML, got %q", extra["customs"])
|
|
}
|
|
if !strings.Contains(extra["customs"], "演春") {
|
|
t.Errorf("customs 应保留文字「演春」, got %q", extra["customs"])
|
|
}
|
|
// 外文名含 <sup> 应清洗
|
|
if strings.Contains(extra["english"], "<sup>") {
|
|
t.Errorf("english 应清洗 sup 标签, got %q", extra["english"])
|
|
}
|
|
}
|
|
|
|
func TestCleanTermHTML(t *testing.T) {
|
|
cases := map[string]string{
|
|
`<a target=_blank href="/item/x">演春</a>、迎春`: "演春、迎春",
|
|
`Beginning of Spring<sup>3</sup><a name="ref_3"></a>`: "Beginning of Spring",
|
|
` 多余 空白 `: "多余 空白",
|
|
}
|
|
for in, want := range cases {
|
|
if got := cleanTermHTML(in); got != want {
|
|
t.Errorf("cleanTermHTML(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFirstSentence(t *testing.T) {
|
|
s := "立春为二十四节气之首。当太阳到达黄经315°时为立春。"
|
|
if got := firstSentence(s); got != "立春为二十四节气之首。" {
|
|
t.Errorf("firstSentence = %q", got)
|
|
}
|
|
// 无句号时返回原文
|
|
if got := firstSentence("无句号文本"); got != "无句号文本" {
|
|
t.Errorf("firstSentence 无句号 = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestTermSort(t *testing.T) {
|
|
if termSort("立春") != 1 {
|
|
t.Errorf("termSort(立春) = %d, want 1", termSort("立春"))
|
|
}
|
|
if termSort("大寒") != 24 {
|
|
t.Errorf("termSort(大寒) = %d, want 24", termSort("大寒"))
|
|
}
|
|
if termSort("不存在") != 0 {
|
|
t.Errorf("termSort(不存在) = %d, want 0", termSort("不存在"))
|
|
}
|
|
}
|
|
|
|
func TestTrimTruncatedTail(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
}{
|
|
{"完整句号结尾不动", "夏至后人们普遍食用清补凉汤来避暑。", "夏至后人们普遍食用清补凉汤来避暑。"},
|
|
{"英文省略号回退", "表达感谢的心情。唐宋时期,冬至...", "表达感谢的心情。"},
|
|
{"中文省略号回退", "表达感谢的心情。唐宋时期,冬至…", "表达感谢的心情。"},
|
|
{"无句号半截话保留", "可明显感觉到早春的气...", "可明显感觉到早春的气"},
|
|
{"多点省略号", "农事谚语....", "农事谚语"},
|
|
{"结尾空白加省略号", "冬至大如年。 ...", "冬至大如年。"},
|
|
{"空字符串", "", ""},
|
|
}
|
|
for _, c := range cases {
|
|
if got := trimTruncatedTail(c.in); got != c.want {
|
|
t.Errorf("%s: trimTruncatedTail(%q) = %q, want %q", c.name, c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|