rime-ice/others/script/rime/check.go
2024-01-26 04:30:02 +08:00

317 lines
8.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rime
import (
"bufio"
"fmt"
mapset "github.com/deckarep/golang-set/v2"
"log"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
)
var (
specialWords = mapset.NewSet[string]() // 特殊词汇列表,不进行任何检查
polyphoneWords = mapset.NewSet[string]() // 需要注音的字词
wrongWords = mapset.NewSet[string]() // 异形词、错别字
wrongWordsFilter = mapset.NewSet[string]() // 过滤一部分,包含错别字但不是错别字,如「作爱」是错的,但「叫作爱」是对的
hanPinyin = make(map[string][]string) // 汉字拼音映射,用于检查注音是否正确
hanPinyinFilter = mapset.NewSet[string]() // 过滤一部分比如「深厉浅揭qi」只在这个词中念qi并不是错误。
)
// 初始化特殊词汇列表、需要注音列表、错别字列表、拼音列表
func init() {
// 特殊词汇列表,不进行任何检查
specialWords.Add("狄尔斯–阿尔德反应")
specialWords.Add("特里斯坦–达库尼亚")
specialWords.Add("特里斯坦–达库尼亚群岛")
specialWords.Add("茱莉亚·路易斯-德瑞弗斯")
specialWords.Add("科科斯(基林)群岛")
specialWords.Add("刚果(金)")
specialWords.Add("刚果(布)")
specialWords.Add("赛博朋克:边缘行者")
specialWords.Add("赛博朋克:边缘跑手")
specialWords.Add("赛博朋克:命运之轮")
specialWords.Add("哈勃–勒梅特定律")
// 需要注音的列表
file1, err := os.Open(需要注音TXT)
if err != nil {
log.Fatalln(err)
}
defer file1.Close()
sc := bufio.NewScanner(file1)
for sc.Scan() {
line := sc.Text()
if strings.HasPrefix(line, "#") || line == "" {
continue
}
polyphoneWords.Add(line)
}
// 错别字的两个列表: wrongWords wrongWordsFilter
file2, err := os.Open(错别字TXT)
if err != nil {
log.Fatalln(err)
}
defer file2.Close()
sc = bufio.NewScanner(file2)
isMark := false
for sc.Scan() {
line := sc.Text()
if strings.HasPrefix(line, "# -_-") {
isMark = true
continue
}
if strings.HasPrefix(line, "#") || line == "" {
continue
}
if !isMark {
wrongWords.Add(line)
} else {
wrongWordsFilter.Add(line)
}
}
// 汉字拼音映射 hanPinyin hanPinyinFilter
// 将所有读音读入 hanPinyin
file3, err := os.Open(HanziPath)
if err != nil {
log.Fatalln(err)
}
defer file3.Close()
isMark = false
sc = bufio.NewScanner(file3)
for sc.Scan() {
line := sc.Text()
if !isMark {
if strings.Contains(line, mark) {
isMark = true
}
continue
}
parts := strings.Split(line, "\t")
text, code := parts[0], parts[1]
hanPinyin[text] = append(hanPinyin[text], code)
}
// 给 hanPinyin 补充不再字表的读音,和过滤列表 hanPinyinFilter
file4, err := os.Open(汉字拼音映射TXT)
if err != nil {
log.Fatalln(err)
}
defer file4.Close()
sc = bufio.NewScanner(file4)
isMark = false
for sc.Scan() {
line := sc.Text()
if strings.HasPrefix(line, "#") && !strings.HasPrefix(line, "# -_-") || line == "" {
continue
}
if strings.HasPrefix(line, "# -_-") {
isMark = true
continue
}
if !isMark {
parts := strings.Split(line, " ")
key := parts[0]
values := parts[1:]
hanPinyin[key] = append(hanPinyin[key], values...)
} else {
hanPinyinFilter.Add(line)
}
}
}
// Check 对传入的词库文件进行检查
// dictPath: 词库文件路径
// _type: 词库类型 1 只有汉字 2 汉字+注音 3 汉字+注音+权重 4 汉字+权重
func Check(dictPath string, _type int) {
// 控制台输出
defer printlnTimeCost("检查 "+path.Base(dictPath), time.Now())
// 打开文件
file, err := os.Open(dictPath)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
// 开始检查!
lineNumber := 0
isMark := false
sc := bufio.NewScanner(file)
var wg sync.WaitGroup
for sc.Scan() {
lineNumber++
line := sc.Text()
if !isMark {
if strings.HasPrefix(line, mark) {
isMark = true
}
continue
}
wg.Add(1)
go func(line string, lineNumber int) {
defer wg.Done()
checkLine(dictPath, _type, line, lineNumber)
}(line, lineNumber)
}
wg.Wait()
if err := sc.Err(); err != nil {
log.Fatalln(err)
}
}
// 检查一行
func checkLine(dictPath string, _type int, line string, lineNumber int) {
// 忽略注释base 中有很多被注释了的词汇,暂时没有删除
if strings.HasPrefix(line, "#") {
// 注释以 '#' 开头,但不是以 '# '开头(强迫症晚期)
if !strings.HasPrefix(line, "# ") {
fmt.Println("has # but not #␣", line)
}
return
}
// 有空行
if strings.TrimSpace(line) == "" {
fmt.Println("empty line", line)
}
// +---------------------------------------------------------------
// | 开始检查分割后的内容,分割为: 词汇text 编码code 权重weight
// +---------------------------------------------------------------
parts := strings.Split(line, "\t")
var text, code, weight string
switch {
case _type == 1 && len(parts) == 1: // 一列,【汉字】
text = parts[0]
case _type == 2 && len(parts) == 2: // 两列,【汉字+注音】
text, code = parts[0], parts[1]
case _type == 3 && len(parts) == 3: // 三列,【汉字+注音+权重】
text, code, weight = parts[0], parts[1], parts[2]
case _type == 4 && len(parts) == 2: // 两列,【汉字+权重】
text, weight = parts[0], parts[1]
default:
log.Fatalln("❌ 分割错误:", lineNumber, line)
}
// weight 应该是纯数字
if weight != "" {
_, err := strconv.Atoi(weight)
if err != nil {
fmt.Println("❌ weight 非数字:", line)
}
}
// text 和 weight 不应该含有空格
if strings.Contains(text, " ") || strings.Contains(weight, " ") {
fmt.Println("❌ text 和 weight 含有空格:", line)
}
// code 前后不应该有空格
if strings.HasPrefix(code, " ") || strings.HasSuffix(code, " ") {
fmt.Println("❌ code 前后有空格:", line)
}
// code 不应该有非小写字母
for _, r := range code {
if string(r) != " " && !unicode.IsLower(r) {
fmt.Println("❌ code 含有非小写字母:", line)
break
}
}
// 过滤特殊词条
if specialWords.Contains(text) {
return
}
// text 不应该有非汉字内容,除了间隔号 ·
for _, c := range text {
if string(c) != "·" && !unicode.Is(unicode.Han, c) {
fmt.Println("❌ text 含有非汉字内容:", line)
break
}
}
// 除了字表,其他词库不应该含有单个的汉字
if dictPath != HanziPath && utf8.RuneCountInString(text) == 1 {
fmt.Println("❌ 意外的单个汉字:", line)
}
// 除了 base其他词库不应该含有两个字的词汇
if dictPath != BasePath && utf8.RuneCountInString(text) == 2 {
fmt.Println("❌ 意外的两字词:", line)
}
// 汉字个数应该和拼音个数相等
if code != "" {
codeCount := len(strings.Split(code, " "))
textCount := utf8.RuneCountInString(text)
if strings.Contains(text, "·") {
textCount -= strings.Count(text, "·")
}
if strings.HasPrefix(text, "# ") {
textCount -= 2
}
if textCount != codeCount {
fmt.Println("❌ 汉字个数 != 拼音个数:", line)
}
}
// +---------------------------------------------------------------
// | 其他检查
// +---------------------------------------------------------------
// 需要注音但没有注音的字
if dictPath == TencentPath {
for _, word := range polyphoneWords.ToSlice() {
if strings.Contains(text, word) {
fmt.Println("❌ 需要注音:", line)
}
}
}
// 检查拼写错误如「赞zan」写成了zna顺便检查是否存在字表中没有注音的字
if dictPath != HanziPath && (_type == 2 || _type == 3) && !hanPinyinFilter.Contains(text) {
// 把汉字和拼音弄成一一对应关系,「拼音:pin yin」→「拼:pin」「音:yin」
textWithoutDian := strings.ReplaceAll(text, "·", "") // 去掉间隔号
pinyins := strings.Split(code, " ")
i := 0
for _, zi := range textWithoutDian {
if !contains(hanPinyin[string(zi)], pinyins[i]) {
fmt.Printf("❌ 注音错误 or 字表未包含的汉字及注音: %s - %s.+%s\n", line, string(zi), pinyins[i])
}
i++
}
}
// 错别字检查,最耗时的检查
if dictPath != HanziPath && !wrongWordsFilter.Contains(text) {
wrongWords.Each(func(wrongWord string) bool {
if strings.HasPrefix(wrongWord, "=") {
wrongWord = strings.TrimLeft(wrongWord, "= ")
if text == wrongWord {
fmt.Printf("❌ 错别字: %s = %s\n", text, wrongWord)
return true
}
} else if strings.Contains(text, wrongWord) {
fmt.Printf("❌ 错别字: %s - %s\n", text, wrongWord)
return true
}
return false
})
}
}