diff --git a/README.md b/README.md index c8471a9..057c31e 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,19 @@ 固定附加库中的字到主库 ``` 下面是一些用例。 -### 查询一个字的国际音标、部首、全局ID +### 计算一个字的国际音标、部首、全局ID +> 该计算不查数据库, 完全与数据库无关. ```bash go run cmd/main.go -i -a 哦 -p o 文字: 哦 拼音IPA: 0 ɔ 轻声 ID: 93346820784388 ``` +### 查询库中记录的字的信息 +```bash +go run cmd/main.go -a 行 +查询到汉字 行 的记录: +0) #149859999752449 行 [ɕiŋ阳平] 从行 xing xínɡ +1) #149859999554817 行 [xɑŋ阳平] 从行 hang háng +``` ### 编码 > 注意: 可以指定`-nr`参数从而使编解码结果唯一。 ```bash diff --git a/cmd/main.go b/cmd/main.go index bd90e65..8321e1a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -132,4 +132,20 @@ func main() { } fmt.Println("已删除汉字ID:", *deloverlay) } + if *addoverlay != "" { + r := []rune(*addoverlay) + if len(r) != 1 { + fmt.Println("ERROR: 非法汉字参数-a:", *addoverlay) + return + } + lst, err := coder.Lookup(r[0]) + if err != nil { + fmt.Println("ERROR: coder.Lookup:", err) + return + } + fmt.Println("查询到汉字", *addoverlay, "的记录:") + for i, x := range lst { + fmt.Printf("%d)\t%s\n", i, x) + } + } } diff --git a/coder.go b/coder.go index 328234f..b2c245c 100644 --- a/coder.go +++ b/coder.go @@ -1,9 +1,7 @@ package emozi import ( - "errors" "os" - "strconv" "strings" "sync" "time" @@ -214,139 +212,3 @@ func (c *Coder) Decode(es EmoziString, forcedecode bool) (string, error) { } return sb.String(), nil } - -// AddChar 向主库添加一个新字 -// -// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 -func (c *Coder) AddChar(w, r, p, f string) error { - if p == "" { - p = 去调(f) - } - s, y, t, rw, rr, err := 拆音识字(w, r, p, f) - if err != nil { - return err - } - c.mu.Lock() - err = c.db.InsertUnique(主字表名, &字表{ - ID: 字表ID(rw, s, y, t), - W: rw, S: s, Y: y, T: t, - R: rr, P: p, F: f, - }) - c.mu.Unlock() - if err != nil { - return errors.New("已有同音同形的字 '" + w + "'") - } - return nil -} - -// AddCharOverlay 向附加库添加一个新字, 覆盖在主库之上 -// -// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 -// 返回: 字表ID, 文字描述, error -func (c *Coder) AddCharOverlay(w, r, p, f string) (int64, string, error) { - if p == "" { - p = 去调(f) - } - s, y, t, rw, rr, err := 拆音识字(w, r, p, f) - if err != nil { - return 0, "", err - } - return c.addcharoverlay(w, p, f, s, y, t, rw, rr) -} - -func (c *Coder) addcharoverlay(w, p, f string, s 声母枚举, y 韵母枚举, t 声调枚举, rw rune, rr rune) (int64, string, error) { - id := 字表ID(rw, s, y, t) - c.mu.Lock() - err := c.db.InsertUnique(附字表名, &字表{ - ID: id, - W: rw, S: s, Y: y, T: t, - R: rr, P: p, F: f, - }) - c.mu.Unlock() - if err != nil { - return 0, "", errors.New("已有同音同形的字 '" + w + "'") - } - sb := strings.Builder{} - sb.WriteString(s.String()) - sb.WriteString(", ") - sb.WriteString(y.String()) - sb.WriteString(", ") - sb.WriteString(t.String()) - return id, sb.String(), nil -} - -// ChangeCharOverlay 更改附加库的一项 -func (c *Coder) ChangeCharOverlay(oldw, oldr, oldf, neww, newr, newf string) (int64, string, error) { - s, y, t, rw, rr, err := 拆音识字(oldw, oldr, 去调(oldf), oldf) - if err != nil { - return 0, "", err - } - newp := 去调(newf) - ns, ny, nt, nrw, nrr, err := 拆音识字(neww, newr, newp, newf) - if err != nil { - return 0, "", err - } - q := "WHERE ID=" + strconv.FormatInt(字表ID(rw, s, y, t), 10) - x := 字表{} - c.mu.RLock() - err = c.db.Find(附字表名, &x, q) - c.mu.RUnlock() - if err != nil { - return 0, "", err - } - if x.R != rr { - return 0, "", errors.New("提供的旧部首 '" + string(rr) + "' 与记载的 '" + string(x.R) + "' 不符") - } - c.mu.Lock() - err = c.db.Del(附字表名, q) - c.mu.Unlock() - if err != nil { - return 0, "", err - } - return c.addcharoverlay(neww, newp, newf, ns, ny, nt, nrw, nrr) -} - -// StabilizeCharFromOverlay 将附加库中的一项固定到主库 -func (c *Coder) StabilizeCharFromOverlay(id int64) (string, error) { - x := 字表{} - q := "WHERE ID=" + strconv.FormatInt(id, 10) - c.mu.Lock() - defer c.mu.Unlock() - err := c.db.Find(附字表名, &x, q) - if err != nil { - return "", err - } - err = c.db.Insert(主字表名, &x) - if err != nil { - return x.String(), err - } - return x.String(), c.db.Del(附字表名, q) -} - -// DelChar 删除主库的一个字 -func (c *Coder) DelChar(id int64) error { - c.mu.Lock() - defer c.mu.Unlock() - return c.db.Del(主字表名, "WHERE ID="+strconv.FormatInt(id, 10)) -} - -// DelCharOverlay 删除附加库的一个字 -func (c *Coder) DelCharOverlay(id int64) error { - c.mu.Lock() - defer c.mu.Unlock() - return c.db.Del(附字表名, "WHERE ID="+strconv.FormatInt(id, 10)) -} - -// AddRadical 添加一个部首 -func (c *Coder) AddRadical(r rune, e string) error { - c.mu.Lock() - defer c.mu.Unlock() - return c.db.InsertUnique(部首表名, &部首表{R: r, E: e}) -} - -// DelRadical 删除一个部首 -func (c *Coder) DelRadical(r rune) error { - c.mu.Lock() - defer c.mu.Unlock() - return c.db.Del(部首表名, "WHERE R="+strconv.Itoa(int(r))) -} diff --git a/data.go b/data.go index 2820de8..8a4e3f8 100644 --- a/data.go +++ b/data.go @@ -25,6 +25,12 @@ var ( ErrNoSuchChar = errors.New("no such char") ) +// 从表 从部首表 +type 部首表 struct { + R rune // R 该部首 + E string `db:"E,UNIQUE"` // E 该部首对应的颜文字 +} + // 字表 emozi表 定义 type 字表 struct { ID int64 // ID 高 32 位 W 的 rune, 低 32 位 保留8 S8 Y8 T8 @@ -98,11 +104,11 @@ func (c *Coder) 查字(ch rune, lstbuf []字表) ([]字表, []字表, error) { }) if err != nil { lstbuf = lstbuf[:0] - err = c.db.FindFor(主字表名, &x, q, func() error { - lstbuf = append(lstbuf, x) - return nil - }) } + err = c.db.FindFor(主字表名, &x, q, func() error { + lstbuf = append(lstbuf, x) + return nil + }) if err != nil { c.字表缓存[ch] = nil return nil, lstbuf, err @@ -172,8 +178,138 @@ func (c *Coder) 逆字(s 声母枚举, y 韵母枚举, t 声调枚举, r rune, l return rs, lstbuf, nil } -// 从表 从部首表 -type 部首表 struct { - R rune // R 该部首 - E string `db:"E,UNIQUE"` // E 该部首对应的颜文字 +// AddChar 向主库添加一个新字 +// +// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 +func (c *Coder) AddChar(w, r, p, f string) error { + if p == "" { + p = 去调(f) + } + s, y, t, rw, rr, err := 拆音识字(w, r, p, f) + if err != nil { + return err + } + c.mu.Lock() + err = c.db.InsertUnique(主字表名, &字表{ + ID: 字表ID(rw, s, y, t), + W: rw, S: s, Y: y, T: t, + R: rr, P: p, F: f, + }) + c.mu.Unlock() + if err != nil { + return errors.New("已有同音同形的字 '" + w + "'") + } + return nil +} + +// AddCharOverlay 向附加库添加一个新字, 覆盖在主库之上 +// +// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 +// 返回: 字表ID, 文字描述, error +func (c *Coder) AddCharOverlay(w, r, p, f string) (int64, string, error) { + if p == "" { + p = 去调(f) + } + s, y, t, rw, rr, err := 拆音识字(w, r, p, f) + if err != nil { + return 0, "", err + } + return c.addcharoverlay(w, p, f, s, y, t, rw, rr) +} + +func (c *Coder) addcharoverlay(w, p, f string, s 声母枚举, y 韵母枚举, t 声调枚举, rw rune, rr rune) (int64, string, error) { + id := 字表ID(rw, s, y, t) + c.mu.Lock() + err := c.db.InsertUnique(附字表名, &字表{ + ID: id, + W: rw, S: s, Y: y, T: t, + R: rr, P: p, F: f, + }) + c.mu.Unlock() + if err != nil { + return 0, "", errors.New("已有同音同形的字 '" + w + "'") + } + sb := strings.Builder{} + sb.WriteString(s.String()) + sb.WriteString(", ") + sb.WriteString(y.String()) + sb.WriteString(", ") + sb.WriteString(t.String()) + return id, sb.String(), nil +} + +// ChangeCharOverlay 更改附加库的一项 +func (c *Coder) ChangeCharOverlay(oldw, oldr, oldf, neww, newr, newf string) (int64, string, error) { + s, y, t, rw, rr, err := 拆音识字(oldw, oldr, 去调(oldf), oldf) + if err != nil { + return 0, "", err + } + newp := 去调(newf) + ns, ny, nt, nrw, nrr, err := 拆音识字(neww, newr, newp, newf) + if err != nil { + return 0, "", err + } + q := "WHERE ID=" + strconv.FormatInt(字表ID(rw, s, y, t), 10) + x := 字表{} + c.mu.RLock() + err = c.db.Find(附字表名, &x, q) + c.mu.RUnlock() + if err != nil { + return 0, "", err + } + if x.R != rr { + return 0, "", errors.New("提供的旧部首 '" + string(rr) + "' 与记载的 '" + string(x.R) + "' 不符") + } + c.mu.Lock() + err = c.db.Del(附字表名, q) + c.mu.Unlock() + if err != nil { + return 0, "", err + } + return c.addcharoverlay(neww, newp, newf, ns, ny, nt, nrw, nrr) +} + +// StabilizeCharFromOverlay 将附加库中的一项固定到主库 +func (c *Coder) StabilizeCharFromOverlay(id int64) (string, error) { + x := 字表{} + q := "WHERE ID=" + strconv.FormatInt(id, 10) + c.mu.Lock() + defer c.mu.Unlock() + err := c.db.Find(附字表名, &x, q) + if err != nil { + return "", err + } + err = c.db.Insert(主字表名, &x) + if err != nil { + return x.String(), err + } + return x.String(), c.db.Del(附字表名, q) +} + +// DelChar 删除主库的一个字 +func (c *Coder) DelChar(id int64) error { + c.mu.Lock() + defer c.mu.Unlock() + return c.db.Del(主字表名, "WHERE ID="+strconv.FormatInt(id, 10)) +} + +// DelCharOverlay 删除附加库的一个字 +func (c *Coder) DelCharOverlay(id int64) error { + c.mu.Lock() + defer c.mu.Unlock() + return c.db.Del(附字表名, "WHERE ID="+strconv.FormatInt(id, 10)) +} + +// AddRadical 添加一个部首 +func (c *Coder) AddRadical(r rune, e string) error { + c.mu.Lock() + defer c.mu.Unlock() + return c.db.InsertUnique(部首表名, &部首表{R: r, E: e}) +} + +// DelRadical 删除一个部首 +func (c *Coder) DelRadical(r rune) error { + c.mu.Lock() + defer c.mu.Unlock() + return c.db.Del(部首表名, "WHERE R="+strconv.Itoa(int(r))) } diff --git a/define.go b/define.go index 7b03f29..faba199 100644 --- a/define.go +++ b/define.go @@ -76,8 +76,8 @@ var 声母 = [...][]string{ {"🐽", "🐷", "🪡", "💉"}, // ch: 茶茶车车车车床床除虫 {"🍵", "☕️", "🚗", "🚘", "🚙", "🛻", "🛏️", "🛌", "➗", "🐛"}, - // sh: 水水书书书书书书上上上石山山山树树树蛇鼠鼠鼠珊 - {"💦", "💧", "📖", "📕", "📗", "📘", "📙", "📚", "⬆️", "☝️", "👆", "🪨", "⛰️", "🏔️", "🗻", "🌲", "🌳", "🌴", "🐍", "🐭", "🐀", "🐁", "🪸"}, + // sh: 水水书书书书书书上上上石山山山树树树蛇鼠鼠鼠珊18 + {"💦", "💧", "📖", "📕", "📗", "📘", "📙", "📚", "⬆️", "☝️", "👆", "🪨", "⛰️", "🏔️", "🗻", "🌲", "🌳", "🌴", "🐍", "🐭", "🐀", "🐁", "🪸", "🔞"}, // r: 肉日日日 {"🥩", "🌞", "☀️", "🌅"}, // z: 左左左 c: 错错错错 s: 锁伞伞伞伞蒜 @@ -167,12 +167,12 @@ const ( // 韵母 仅由一种意象表示 var 韵母 = [...][]string{ {"😧", "🤔️"}, {"😲"}, {"😋"}, {"👔", "👗", "👕", "👚"}, {"🌞", "☀️", "🌅"}, - {string([]rune("🌫️")[0]), "🌫️"}, {"🐟", "🌧️"}, {"👂"}, - {string([]rune("❤️")[0]), "❤️", "💗", "💓", "🫶", "💕", "💖", "💘", "💙", "💚", "💛", "💜", "💝", "💞", "🧡"}, {"🥢"}, {"😩"}, {"🐢"}, + {"🈚", "🌫️"}, {"🐟", "🌧️"}, {"👂"}, + {"💟", "❤️", "💗", "💓", "🫶", "💕", "💖", "💘", "💙", "💚", "💛", "💜", "💝", "💞", "🧡"}, {"🥢"}, {"😩"}, {"🐢"}, {"🐱", "🐈", "🐈‍⬛", "😺"}, {"💊"}, {"🤮", "🤢"}, {"👉", "➡️"}, - {"🦷"}, {"🧦"}, {"🌀"}, {string([]rune("✌️")[0]), "✌️"}, {"🌙", "🌕", "🌛", "🌝"}, {"🔐"}, {"🚬"}, {"🥣", "🍚"}, - {string([]rune("⭕️")[0]), "⭕️"}, {"😐"}, {"😘", "💋"}, {"🎵"}, {string([]rune("☁️")[0]), "☁️"}, {"👍"}, {"🐑"}, - {string([]rune("🕸️")[0]), "🕸️"}, {"💡", "🪔"}, {"🦅"}, {"🐝"}, {"🌈"}, {"😳"}, // jiong 囧 + {"🦷"}, {"🧦"}, {"🌀"}, {"👴", "✌️"}, {"🌙", "🌕", "🌛", "🌝"}, {"🔐"}, {"🚬"}, {"🥣", "🍚"}, + {"🔘", "⭕️"}, {"😐"}, {"😘", "💋"}, {"🎵"}, {"☁"}, {"👍"}, {"🐑"}, + {"🥅", "🕸️"}, {"💡", "🪔"}, {"🦅"}, {"🐝"}, {"🌈"}, {"😳"}, // jiong 囧 } // 低阶逆韵母 rune韵母到韵母的逆映射 diff --git a/define_test.go b/define_test.go index 920ffc3..29c3959 100644 --- a/define_test.go +++ b/define_test.go @@ -8,7 +8,6 @@ func TestFirstEmojiSingle(t *testing.T) { t.Fatal("声母", i, "长度", len([]rune(lst[0])), "字", lst[0]) } } - t.Log(string([]rune("🌫️")[0]), string([]rune("❤️")[0]), string([]rune("✌️")[0]), string([]rune("⭕️")[0]), string([]rune("☁️")[0]), string([]rune("🕸️")[0])) for i, lst := range 韵母 { if len([]rune(lst[0])) != 1 { t.Fatal("韵母", i, "长度", len([]rune(lst[0])), "字", lst[0]) diff --git a/lookup.go b/lookup.go index b9f3542..e73eef4 100644 --- a/lookup.go +++ b/lookup.go @@ -8,6 +8,19 @@ import ( const 空 = '🈳' +// Lookup 查一个汉字 (可能是多音字) +func (c *Coder) Lookup(ch rune) (explains []string, err error) { + lst, _, err := c.查字(ch, make([]字表, 0, 8)) + if err != nil || len(lst) == 0 { + return + } + explains = make([]string, len(lst)) + for i, x := range lst { + explains[i] = x.String() + } + return +} + func 随机正查(m [][]string, isRandom bool, i uint8) string { lst := m[i] if len(lst) == 0 { diff --git a/radical.go b/radical.go index 210a57a..ea77a82 100644 --- a/radical.go +++ b/radical.go @@ -329,8 +329,8 @@ var 部首后备 = map[rune]string{ '㱃': string(空), '㳄': string(空), '旡': string(空), - '頁': string(空), - '𦣻': string(空), + '頁': "👱‍♂️", + '𦣻': "👱", '面': string(空), '丏': string(空), '首': string(空), @@ -338,7 +338,7 @@ var 部首后备 = map[rune]string{ '須': string(空), '彡': string(空), '彣': string(空), - '文': string(空), + '文': "💮", '髟': string(空), '后': string(空), '司': string(空), @@ -374,25 +374,25 @@ var 部首后备 = map[rune]string{ '豸': string(空), '𤉡': string(空), '易': string(空), - '象': string(空), - '馬': string(空), + '象': "🐘", + '馬': "🐎", '𢊁': string(空), - '鹿': string(空), + '鹿': "🦌", '麤': string(空), '㲋': string(空), - '兔': string(空), + '兔': "🐰", '萈': string(空), - '犬': string(空), + '犬': "🐶", '㹜': string(空), - '鼠': string(空), + '鼠': "🐭", '能': string(空), - '熊': string(空), - '火': string(空), - '炎': string(空), + '熊': "🐻", + '火': "🔥", + '炎': "🔥", '黑': string(空), '囪': string(空), '焱': string(空), - '炙': string(空), + '炙': "🍖", '赤': string(空), '大': string(空), '亦': string(空), @@ -415,12 +415,12 @@ var 部首后备 = map[rune]string{ '思': string(空), '心': string(空), '惢': string(空), - '水': string(空), - '沝': string(空), + '水': "💧", + '沝': "💦", '瀕': string(空), - '𡿨': string(空), - '巜': string(空), - '川': string(空), + '𡿨': "🫗", + '巜': "⛜", + '川': "💦", '泉': string(空), '灥': string(空), '永': string(空), @@ -501,7 +501,7 @@ var 部首后备 = map[rune]string{ '斤': string(空), '斗': string(空), '矛': string(空), - '車': string(空), + '車': "🚲", '𠂤': string(空), '𨸏': string(空), '𨺅': string(空),