From 96223b1b74b639b969d6954aa16c7cb475c43e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:28:57 +0900 Subject: [PATCH] feat(coder): finish decode --- codegen/radical/main.go | 2 +- coder.go | 116 ++++- coder_test.go | 40 +- data.go | 86 +++- define.go | 56 ++- lookup.go | 149 ++++++- pinyin_test.go | 14 +- preprocess/main.go | 2 +- radical.go | 920 ++++++++++++++++++++-------------------- string.go | 7 +- 10 files changed, 883 insertions(+), 509 deletions(-) diff --git a/codegen/radical/main.go b/codegen/radical/main.go index 29f929f..3876063 100644 --- a/codegen/radical/main.go +++ b/codegen/radical/main.go @@ -15,7 +15,7 @@ package emozi // 部首后备 内嵌的部首到颜文字的映射, 是第二优先查询顺位. 第一顺位是数据库的部首表. 第三位是回落到 🈳️. var 部首后备 = map[rune]string{ {{range .}} - '{{.R}}': "🈳️",{{end}} + '{{.R}}': 空,{{end}} } ` diff --git a/coder.go b/coder.go index 7820f05..0a00058 100644 --- a/coder.go +++ b/coder.go @@ -13,15 +13,16 @@ import ( // Coder encoder/decoder type Coder struct { - mu sync.RWMutex - db sql.Sqlite - 字表缓存 map[rune][]字表 - 部首缓存 map[rune]string - isRandom bool + mu sync.RWMutex + db sql.Sqlite + 字表缓存 map[rune][]字表 + 逆字表缓存 map[int64][]rune + 部首缓存 map[rune]string + 逆部首缓存 map[string][]rune } // NewCoder israndom 随机挑选声母韵母的颜文字, 否则固定使用第一个 -func NewCoder(israndom bool, cachettl time.Duration) (c Coder, err error) { +func NewCoder(cachettl time.Duration) (c Coder, err error) { if _, err = os.Stat(EmoziDatabasePath); err != nil { err = os.WriteFile(EmoziDatabasePath, 字数据库, 0644) if err != nil { @@ -30,8 +31,9 @@ func NewCoder(israndom bool, cachettl time.Duration) (c Coder, err error) { } c.db.DBPath = EmoziDatabasePath c.字表缓存 = make(map[rune][]字表, 4096) + c.逆字表缓存 = make(map[int64][]rune, 4096) c.部首缓存 = make(map[rune]string, 4096) - c.isRandom = israndom + c.逆部首缓存 = make(map[string][]rune, 4096) err = c.db.Open(cachettl) if err != nil { return @@ -56,19 +58,23 @@ func NewCoder(israndom bool, cachettl time.Duration) (c Coder, err error) { func (c *Coder) Close() error { c.mu.Lock() defer c.mu.Unlock() + c.字表缓存 = nil + c.逆字表缓存 = nil + c.部首缓存 = nil + c.逆部首缓存 = nil return c.db.Close() } // Encode 从汉字序列生成 EmoziString 返回 EmoziString 多音字选择数列表 -func (c *Coder) Encode(s string, selections ...int) (EmoziString, []int, error) { +func (c *Coder) Encode(enableRandom bool, s string, selections ...int) (EmoziString, []int, error) { sb := strings.Builder{} lstbuf := make([]字表, 0, len(s)/2) var lst []字表 write := func(x *字表) { - sb.WriteString(c.查声母(x.S)) - sb.WriteString(c.查韵母(x.Y)) - sb.WriteString(c.查声调(x.T)) - sb.WriteString(c.查部首(x.R)) + sb.WriteString(c.声母(enableRandom, x.S)) + sb.WriteString(c.韵母(enableRandom, x.Y)) + sb.WriteString(c.声调(enableRandom, x.T)) + sb.WriteString(c.部首(x.R)) } 多音字计数 := 0 多音字数表 := []int{} @@ -104,6 +110,92 @@ func (c *Coder) Encode(s string, selections ...int) (EmoziString, []int, error) return WrapRawEmoziString(sb.String()), 多音字数表, nil } +// Decode 从 EmoziString 解码得到可能的文字序列 +func (c *Coder) Decode(es EmoziString, forcedecode bool) (string, error) { + if !es.IsValid() && !forcedecode { + return "", ErrInvalidEmoziString + } + s := "" + if forcedecode { + s = string(es) + } else { + s = es.String() + } + lstbuf := make([]字表, 0, len(s)/8) + read := func(s string) (string, int) { + sum := 0 + sm, n := c.逆声母(s) + if n == 0 { + return "", 0 + } + sum += n + ym, n := c.逆韵母(s[sum:]) + if n == 0 { + return "", 0 + } + sum += n + t, n := c.逆声调(s[sum:]) + if n == 0 { + return "", 0 + } + sum += n + rs, n := c.逆部首(s[sum:]) + if n == 0 { + return "", 0 + } + sum += n + var possibles []rune + var err error + if len(rs) == 0 { // 意符为空 + possibles, lstbuf, err = c.逆字(sm, ym, t, 0, lstbuf) + if err != nil { + return "[]", sum + } + } else { + var revr []rune + for i := 0; i < len(rs); i++ { + revr, lstbuf, err = c.逆字(sm, ym, t, rs[i], lstbuf) + if err != nil || len(revr) == 0 { + continue + } + if len(possibles) == 0 { + possibles = revr + } else { + possibles = append(possibles, revr...) + } + } + } + if len(possibles) == 0 { + return "[]", sum + } + if len(possibles) == 1 { + return string(possibles[0]), sum + } + sb := strings.Builder{} + sb.WriteString("[") + sb.WriteRune(possibles[0]) + for _, r := range possibles[1:] { + sb.WriteString("|") + sb.WriteRune(r) + } + sb.WriteString("]") + return sb.String(), sum + } + sb := strings.Builder{} + sum := 0 + for sum < len(s) { + ch, n := read(s[sum:]) + if n <= 0 { + sb.WriteByte(s[sum]) + sum++ + continue + } + sum += n + sb.WriteString(ch) + } + return sb.String(), nil +} + // AddChar 向主库添加一个新字 // // w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 diff --git a/coder_test.go b/coder_test.go index d6597e4..2a9a0ad 100644 --- a/coder_test.go +++ b/coder_test.go @@ -6,11 +6,11 @@ import ( ) func TestEncode(t *testing.T) { - c, err := NewCoder(false, time.Minute) + c, err := NewCoder(time.Minute) if err != nil { t.Fatal(err) } - es, lst, err := c.Encode("你好,世界!看看多音字:行。") + es, lst, err := c.Encode(false, "你好,世界!看看多音字:行。") if err != nil { t.Fatal(err) } @@ -21,7 +21,7 @@ func TestEncode(t *testing.T) { if len(lst) != 1 && lst[0] != 2 { t.Fail() } - es, lst, err = c.Encode("你好,世界!指定多音字:银行行。", 1, 0) + es, lst, err = c.Encode(false, "你好,世界!指定多音字:银行行。", 1, 0) if err != nil { t.Fatal(err) } @@ -33,3 +33,37 @@ func TestEncode(t *testing.T) { t.Fail() } } + +func TestDecode(t *testing.T) { + c, err := NewCoder(time.Minute) + if err != nil { + t.Fatal(err) + } + s := "你好,世界!看看多音字:行。" + es, lst, err := c.Encode(false, s) + if err != nil { + t.Fatal(err) + } + t.Log(es.String(), lst) + ds, err := c.Decode(es, false) + if err != nil { + t.Fatal(err) + } + t.Log(ds) + if ds != "[你|儗]好,世[界|畍]!看看多音字:[行|行]。" { + t.Fatal("got", ds) + } + es, lst, err = c.Encode(false, "你好,世界!指定多音字:银行行。", 1, 0) + if err != nil { + t.Fatal(err) + } + t.Log(es.String(), lst) + ds, err = c.Decode(es, false) + if err != nil { + t.Fatal(err) + } + t.Log(ds) + if ds != "[你|儗]好,世[界|畍]![指|抧|扺]定多音字:[銀|银]行行。" { + t.Fatal("got", ds) + } +} diff --git a/data.go b/data.go index 3ce8b29..a311fed 100644 --- a/data.go +++ b/data.go @@ -2,7 +2,9 @@ package emozi import ( _ "embed" + "errors" "strconv" + "strings" ) // 字数据库 数据来自 https://github.com/shuowenjiezi/shuowen @@ -11,7 +13,7 @@ import ( var 字数据库 []byte // DatabasePath 字数据库的路径 如找不到会向对应路径写入内嵌的字数据库 -var EmoziDatabasePath = "字a.db" +var EmoziDatabasePath = "字.db" const ( 主字表名 = "emozi" @@ -19,6 +21,10 @@ const ( 部首表名 = "radcl" ) +var ( + ErrNoSuchChar = errors.New("no such char") +) + // 字表 emozi表 定义 type 字表 struct { ID int64 // ID 高 32 位 W 的 rune, 低 32 位 保留8 S8 Y8 T8 @@ -35,33 +41,105 @@ func 字表ID(w rune, s 声母枚举, y 韵母枚举, t 声调枚举) int64 { return int64((uint64(w) << 32) | (uint64(s) << 16) | (uint64(y) << 8) | (uint64(t))) } +// 逆字ID 同声母 韵母 声调 部首的字的集合 +func 逆字ID(s 声母枚举, y 韵母枚举, t 声调枚举, r rune) int64 { + return int64((uint64(r) << 32) | (uint64(s) << 16) | (uint64(y) << 8) | (uint64(t))) +} + // 查字 返回 lst lstbuf error func (c *Coder) 查字(ch rune, lstbuf []字表) ([]字表, []字表, error) { c.mu.RLock() lst, ok := c.字表缓存[ch] c.mu.RUnlock() if ok { + if len(lst) == 0 { + return nil, lstbuf, ErrNoSuchChar + } return lst, lstbuf, nil } lstbuf = lstbuf[:0] x := 字表{} + q := "WHERE W=" + strconv.Itoa(int(ch)) c.mu.Lock() defer c.mu.Unlock() - err := c.db.FindFor(附字表名, &x, "WHERE W="+strconv.Itoa(int(ch)), func() error { + err := c.db.FindFor(附字表名, &x, q, func() error { lstbuf = append(lstbuf, x) return nil }) if err != nil { lstbuf = lstbuf[:0] - err = c.db.FindFor(主字表名, &x, "WHERE W="+strconv.Itoa(int(ch)), func() error { + err = c.db.FindFor(主字表名, &x, q, func() error { lstbuf = append(lstbuf, x) return nil }) } + if err != nil { + c.字表缓存[ch] = nil + return nil, lstbuf, err + } + if len(lstbuf) == 0 { + c.字表缓存[ch] = nil + return nil, lstbuf, ErrNoSuchChar + } lstsave := make([]字表, len(lstbuf)) copy(lstsave, lstbuf) c.字表缓存[ch] = lstsave - return lstbuf, lstbuf, err + return lstbuf, lstbuf, nil +} + +// 逆字 逆查匹配的字 +func (c *Coder) 逆字(s 声母枚举, y 韵母枚举, t 声调枚举, r rune, lstbuf []字表) ([]rune, []字表, error) { + id := 逆字ID(s, y, t, r) + c.mu.RLock() + matches, ok := c.逆字表缓存[id] + c.mu.RUnlock() + if ok { + if len(matches) == 0 { + return nil, lstbuf, ErrNoSuchChar + } + return matches, lstbuf, nil + } + lstbuf = lstbuf[:0] + x := 字表{} + sb := strings.Builder{} + sb.WriteString("WHERE S=") + sb.WriteString(strconv.Itoa(int(s))) + sb.WriteString(" AND Y=") + sb.WriteString(strconv.Itoa(int(y))) + sb.WriteString(" AND T=") + sb.WriteString(strconv.Itoa(int(t))) + if r != 0 { + sb.WriteString(" AND R=") + sb.WriteString(strconv.Itoa(int(r))) + } + q := sb.String() + c.mu.Lock() + defer c.mu.Unlock() + err := c.db.FindFor(附字表名, &x, q, func() error { + lstbuf = append(lstbuf, x) + return nil + }) + if err != nil { + lstbuf = lstbuf[:0] + err = c.db.FindFor(主字表名, &x, q, func() error { + lstbuf = append(lstbuf, x) + return nil + }) + } + if err != nil { + c.逆字表缓存[id] = nil + return nil, lstbuf, err + } + if len(lstbuf) == 0 { + c.逆字表缓存[id] = nil + return nil, lstbuf, ErrNoSuchChar + } + rs := make([]rune, len(lstbuf)) + for i, x := range lstbuf { + rs[i] = x.W + } + c.逆字表缓存[id] = rs + return rs, lstbuf, nil } // 从表 从部首表 diff --git a/define.go b/define.go index 83d012f..7b03f29 100644 --- a/define.go +++ b/define.go @@ -1,8 +1,6 @@ package emozi import ( - "strings" - base14 "github.com/fumiama/go-base16384" ) @@ -11,9 +9,9 @@ type 声母枚举 uint8 // String 国际音标 func (sm 声母枚举) String() string { a := sm * 3 - b := a + 3 + b := a + [...]声母枚举{1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 2, 3, 1, 2, 3, 1, 1, 2, 3, 1, 1, 1, 1, 1}[sm] s := []rune("p pʰ m f t tʰ n l k kʰ x tɕ tɕʰɕ ʈʂ ʈʂʰʂ ɻ ts tsʰs j w ɥ 0 ")[a:b] - return strings.TrimSpace(string(s)) + return string(s) } const ( @@ -88,6 +86,33 @@ var 声母 = [...][]string{ {"🐑", "🐐", "🦷", "💊", "🧂"}, {"🧦", "🌫️", "🕸️", "🥣"}, {"🐟", "🌧️", "🐠"}, {"🪑", "💺"}, } +func 低阶逆初始化(表 [][]string, 倍数 int) map[rune][]string { + m := make(map[rune][]string, len(表)*倍数) + for _, lst := range 表 { + for _, s := range lst { + k := []rune(s)[0] + m[k] = append(m[k], s) + } + } + return m +} + +// 低阶逆声母 rune声母到声母的逆映射 +var 低阶逆声母 = 低阶逆初始化(声母[:], 32) + +func 逆初始化[T ~uint8](表 [][]string, 倍数 int) map[string]T { + m := make(map[string]T, len(表)*倍数) + for i, lst := range 表 { + for _, s := range lst { + m[s] = T(i) + } + } + return m +} + +// 逆声母 声母到枚举的逆映射 +var 逆声母 = 逆初始化[声母枚举](声母[:], 32) + type 韵母枚举 uint8 // String 国际音标 @@ -150,6 +175,12 @@ var 韵母 = [...][]string{ {string([]rune("🕸️")[0]), "🕸️"}, {"💡", "🪔"}, {"🦅"}, {"🐝"}, {"🌈"}, {"😳"}, // jiong 囧 } +// 低阶逆韵母 rune韵母到韵母的逆映射 +var 低阶逆韵母 = 低阶逆初始化(韵母[:], 8) + +// 逆韵母 韵母到枚举的逆映射 +var 逆韵母 = 逆初始化[韵母枚举](韵母[:], 8) + type 声调枚举 uint8 // String 返回传统调名 @@ -169,6 +200,12 @@ const ( var 声调 = [...][]string{{"🍉"}, {"🧗", "🧗‍♀️", "🧗‍♂️", "🦎"}, {"🐴", "🐎"}, {"😨"}, {"😯"}} +// 低阶逆声调 rune声调到声调的逆映射 +var 低阶逆声调 = 低阶逆初始化(声调[:], 4) + +// 逆声调 声调到枚举的逆映射 +var 逆声调 = 逆初始化[声调枚举](声调[:], 4) + // 校验表 用 校验表长度 个unicode控制字符做校验和验证此序列是由本程序生成而非手写的 // // 具体做法是先对后面的文本做crc32然后取 校验表长度^校验字节数 的模 @@ -208,3 +245,14 @@ var 逆校验表 = func() map[rune]uint8 { } return m }() + +// 逆部首后备 部首后备逆查表 一对多 +var 逆部首后备 = func() map[string][]rune { + m := make(map[string][]rune, len(部首后备)*4) + for r, e := range 部首后备 { + if 无此字符(m[e], r) { + m[e] = append(m[e], r) + } + } + return m +}() diff --git a/lookup.go b/lookup.go index b3077b6..3207bbd 100644 --- a/lookup.go +++ b/lookup.go @@ -3,44 +3,35 @@ package emozi import ( "math/rand" "strconv" + "strings" ) var 空 = "🈳️" -func (c *Coder) 查声母(s 声母枚举) string { - lst := 声母[s] +func 随机正查(m [][]string, isRandom bool, i uint8) string { + lst := m[i] if len(lst) == 0 { return 空 } - if len(lst) == 1 || !c.isRandom { + if len(lst) == 1 || !isRandom { return lst[0] } return lst[rand.Intn(len(lst))] } -func (c *Coder) 查韵母(y 韵母枚举) string { - lst := 韵母[y] - if len(lst) == 0 { - return 空 - } - if len(lst) == 1 || !c.isRandom { - return lst[0] - } - return lst[rand.Intn(len(lst))] +func (c *Coder) 声母(isRandom bool, s 声母枚举) string { + return 随机正查(声母[:], isRandom, uint8(s)) } -func (c *Coder) 查声调(t 声调枚举) string { - lst := 声调[t] - if len(lst) == 0 { - return 空 - } - if len(lst) == 1 || !c.isRandom { - return lst[0] - } - return lst[rand.Intn(len(lst))] +func (c *Coder) 韵母(isRandom bool, y 韵母枚举) string { + return 随机正查(韵母[:], isRandom, uint8(y)) } -func (c *Coder) 查部首(r rune) string { +func (c *Coder) 声调(isRandom bool, t 声调枚举) string { + return 随机正查(声调[:], isRandom, uint8(t)) +} + +func (c *Coder) 部首(r rune) string { c.mu.RLock() e, ok := c.部首缓存[r] c.mu.RUnlock() @@ -62,3 +53,117 @@ func (c *Coder) 查部首(r rune) string { c.部首缓存[r] = 空 return 空 } + +func 二阶逆查[E ~uint8](lowm map[rune][]string, m map[string]E, s string) (enum E, n int) { + lowk := rune(0) + lows := s + if len(lows) > 12 { + lows = lows[:12] + } + r := []rune(lows) + if len(r) == 0 { + return + } + lowk = r[0] + ks := lowm[lowk] + if len(ks) == 0 { + return + } + // 寻找最长匹配 T + matchp := -1 + matchl := 0 + for i, k := range ks { + if strings.HasPrefix(s, k) { + if len(k) > matchl { + matchl = len(k) + matchp = i + } + } + } + if matchp < 0 { + return + } + enum, ok := m[ks[matchp]] + if !ok { + return + } + n = matchl + return +} + +func (c *Coder) 逆声母(s string) (声母枚举, int) { + return 二阶逆查[声母枚举](低阶逆声母, 逆声母, s) +} + +func (c *Coder) 逆韵母(s string) (韵母枚举, int) { + return 二阶逆查[韵母枚举](低阶逆韵母, 逆韵母, s) +} +func (c *Coder) 逆声调(s string) (声调枚举, int) { + return 二阶逆查[声调枚举](低阶逆声调, 逆声调, s) +} + +func (c *Coder) 逆部首(s string) (rs []rune, n int) { + lim := len(s) + if lim > 32 { + lim = 32 + } + c.mu.RLock() + for i := 1; i < lim; i++ { + l := c.逆部首缓存[s[:i]] + if len(l) > 0 { + rs = l + n = i + } + } + c.mu.RUnlock() + if n > 0 && len(rs) > 0 { + return + } + x := &部首表{} + sb := strings.Builder{} + sb.WriteString("WHERE ") + for i := 1; i < lim; i++ { + sb.WriteString("E='") + sb.WriteString(s[:i]) + sb.WriteString("' OR ") + } + q := sb.String()[:sb.Len()-4] + n = 0 + e := "" + c.mu.Lock() + defer c.mu.Unlock() + err := c.db.FindFor(部首表名, x, q, func() error { + if len(x.E) > n { + n = len(x.E) + rs = rs[:0] + e = x.E + } + if len(x.E) == n && 无此字符(rs, x.R) { + rs = append(rs, x.R) + } + return nil + }) + if err == nil && len(rs) > 0 && n > 0 { + c.逆部首缓存[e] = rs + return + } + for i := 1; i < lim; i++ { + k := s[:i] + innerrs, ok := 逆部首后备[k] + c.逆部首缓存[k] = innerrs + if ok && len(innerrs) > 0 { + n = i + rs = innerrs + } + } + return +} + +func 无此字符(runes []rune, ch rune) bool { + for _, r := range runes { + if ch == r { + return false + } + } + return true +} diff --git a/pinyin_test.go b/pinyin_test.go index 0f289d4..8470837 100644 --- a/pinyin_test.go +++ b/pinyin_test.go @@ -1,6 +1,18 @@ package emozi -import "testing" +import ( + "strings" + "testing" +) + +func TestShengmuString(t *testing.T) { + for i := 0; i < len(声母); i++ { + t.Log(声母枚举(i).String()) + if 声母枚举(i).String() != strings.TrimSpace(声母枚举(i).String()) { + t.Fatal("声母: '", 声母枚举(i), "'") + } + } +} func TestSplitPinyin(t *testing.T) { s, y, tone, err := SplitPinyin("yōng") diff --git a/preprocess/main.go b/preprocess/main.go index 07b1c6b..247a24b 100644 --- a/preprocess/main.go +++ b/preprocess/main.go @@ -29,7 +29,7 @@ func main() { panic(fmt.Sprintf("ERROR: creating gocc: %v", err)) } _ = os.RemoveAll(emozi.EmoziDatabasePath) - c, err := emozi.NewCoder(false, time.Minute) + c, err := emozi.NewCoder(time.Minute) if err != nil { panic(fmt.Sprintf("ERROR: creating emozi coder: %v", err)) } diff --git a/radical.go b/radical.go index 37de774..b19b1b1 100644 --- a/radical.go +++ b/radical.go @@ -40,508 +40,508 @@ var 部首后备 = map[rune]string{ '辵': "🚶", '彳': "🚦", '廴': "🚥", - '㢟': "🈳️", + '㢟': 空, '行': "⛕", - '齒': "🈳️", - '牙': "🈳️", - '足': "🈳️", - '疋': "🈳️", - '品': "🈳️", - '龠': "🈳️", - '冊': "🈳️", - '㗊': "🈳️", + '齒': 空, + '牙': 空, + '足': 空, + '疋': 空, + '品': 空, + '龠': 空, + '冊': 空, + '㗊': 空, '舌': "👅", - '干': "🈳️", - '𧮫': "🈳️", - '只': "🈳️", - '㕯': "🈳️", - '句': "🈳️", - '丩': "🈳️", + '干': 空, + '𧮫': 空, + '只': 空, + '㕯': 空, + '句': 空, + '丩': 空, '古': "🏺", '十': "🔟", '卅': "🌍", '言': "💬", '田': "👨‍🌾", - '誩': "🈳️", + '誩': 空, '音': "🎵", - '䇂': "🈳️", - '丵': "🈳️", - '菐': "🈳️", - '𠬞': "🈳️", - '𠬜': "🈳️", - '共': "🈳️", - '異': "🈳️", - '舁': "🈳️", - '𦥑': "🈳️", - '䢅': "🈳️", - '爨': "🈳️", - '革': "🈳️", - '鬲': "🈳️", - '䰜': "🈳️", + '䇂': 空, + '丵': 空, + '菐': 空, + '𠬞': 空, + '𠬜': 空, + '共': 空, + '異': 空, + '舁': 空, + '𦥑': 空, + '䢅': 空, + '爨': 空, + '革': 空, + '鬲': 空, + '䰜': 空, '爪': "🤏", - '丮': "🈳️", + '丮': 空, '鬥': "👊", - '又': "🈳️", - '𠂇': "🈳️", - '史': "🈳️", - '支': "🈳️", - '𦘒': "🈳️", + '又': 空, + '𠂇': 空, + '史': 空, + '支': 空, + '𦘒': 空, '聿': "✍", '畫': "🎨", - '隶': "🈳️", - '臤': "🈳️", - '臣': "🈳️", - '殳': "🈳️", + '隶': 空, + '臤': 空, + '臣': 空, + '殳': 空, '殺': "🪚", - '𠘧': "🈳️", - '寸': "🈳️", - '皮': "🈳️", - '㼱': "🈳️", - '攴': "🈳️", + '𠘧': 空, + '寸': 空, + '皮': 空, + '㼱': 空, + '攴': 空, '女': "👩", - '教': "🈳️", + '教': 空, '卜': "🔮", - '用': "🈳️", - '爻': "🈳️", - '㸚': "🈳️", - '𡕥': "🈳️", + '用': 空, + '爻': 空, + '㸚': 空, + '𡕥': 空, '目': "👁️", '䀠': "👀", '眉': "🤨", - '盾': "🈳️", + '盾': 空, '自': "👃", - '𪞶': "🈳️", + '𪞶': 空, '鼻': "👃", - '皕': "🈳️", - '習': "🈳️", + '皕': 空, + '習': 空, '羽': "🪶", '隹': "🐦", - '奞': "🈳️", - '雈': "🈳️", - '𦫳': "🈳️", - '𥄕': "🈳️", + '奞': 空, + '雈': 空, + '𦫳': 空, + '𥄕': 空, '羊': "🐑", - '羴': "🈳️", - '瞿': "🈳️", - '雔': "🈳️", - '雥': "🈳️", + '羴': 空, + '瞿': 空, + '雔': 空, + '雥': 空, '鳥': "🦢", '烏': "🐦‍⬛", - '𠦒': "🈳", - '冓': "🈳️", - '幺': "🈳️", - '𢆶': "🈳️", - '叀': "🈳️", - '玄': "🈳️", - '予': "🈳️", - '放': "🈳️", - '𠬪': "🈳️", - '𣦼': "🈳️", - '歺': "🈳️", - '死': "🈳️", - '冎': "🈳️", - '骨': "🈳️", - '肉': "🈳️", - '筋': "🈳️", + '𠦒': 空, + '冓': 空, + '幺': 空, + '𢆶': 空, + '叀': 空, + '玄': 空, + '予': 空, + '放': 空, + '𠬪': 空, + '𣦼': 空, + '歺': 空, + '死': 空, + '冎': 空, + '骨': 空, + '肉': 空, + '筋': 空, '刀': "🔪", '刃': "🔪", - '㓞': "🈳️", - '丯': "🈳️", - '耒': "🈳️", + '㓞': 空, + '丯': 空, + '耒': 空, '角': "🥐", '竹': "🎋", - '箕': "🈳️", - '丌': "🈳️", + '箕': 空, + '丌': 空, '左': "👈", - '工': "🈳️", - '㠭': "🈳️", - '巫': "🈳️", - '甘': "🈳️", - '曰': "🈳️", - '乃': "🈳️", - '丂': "🈳️", - '可': "🈳️", - '兮': "🈳️", - '号': "🈳️", - '亏': "🈳️", - '旨': "🈳️", - '喜': "🈳️", - '壴': "🈳️", - '鼓': "🈳️", - '豈': "🈳️", - '豆': "🈳️", - '豊': "🈳️", - '豐': "🈳️", - '䖒': "🈳️", - '虍': "🈳️", - '虎': "🈳️", - '虤': "🈳️", - '皿': "🈳️", - '𠙴': "🈳️", - '去': "🈳️", - '血': "🈳️", - '丶': "🈳️", - '丹': "🈳️", - '青': "🈳️", - '井': "🈳️", - '皀': "🈳️", - '鬯': "🈳️", - '食': "🈳️", - '亼': "🈳️", - '會': "🈳️", - '倉': "🈳️", - '入': "🈳️", - '缶': "🈳️", - '矢': "🈳️", - '高': "🈳️", - '冂': "🈳️", - '𩫖': "🈳️", - '京': "🈳️", - '亯': "🈳️", - '㫗': "🈳️", - '畗': "🈳️", - '㐭': "🈳️", - '嗇': "🈳️", - '來': "🈳️", - '麥': "🈳️", - '夊': "🈳️", - '舛': "🈳️", - '舜': "🈳️", - '韋': "🈳️", - '弟': "🈳️", - '夂': "🈳️", - '久': "🈳️", - '桀': "🈳️", - '木': "🈳️", - '東': "🈳️", - '林': "🈳️", - '才': "🈳️", - '叒': "🈳️", - '之': "🈳️", - '帀': "🈳️", - '出': "🈳️", - '𣎵': "🈳️", - '生': "🈳️", - '乇': "🈳️", - '𠂹': "🈳️", - '𠌶': "🈳️", - '華': "🈳️", - '𥝌': "🈳️", - '稽': "🈳️", - '巢': "🈳️", - '桼': "🈳️", - '束': "🈳️", - '㯻': "🈳️", - '囗': "🈳️", - '員': "🈳️", - '貝': "🈳️", - '邑': "🈳️", - '𨛜': "🈳️", - '日': "🈳️", - '旦': "🈳️", - '倝': "🈳️", - '㫃': "🈳️", - '冥': "🈳️", - '晶': "🈳️", - '月': "🈳️", - '有': "🈳️", - '朙': "🈳️", - '囧': "🈳️", + '工': 空, + '㠭': 空, + '巫': 空, + '甘': 空, + '曰': 空, + '乃': 空, + '丂': 空, + '可': 空, + '兮': 空, + '号': 空, + '亏': 空, + '旨': 空, + '喜': 空, + '壴': 空, + '鼓': 空, + '豈': 空, + '豆': 空, + '豊': 空, + '豐': 空, + '䖒': 空, + '虍': 空, + '虎': 空, + '虤': 空, + '皿': 空, + '𠙴': 空, + '去': 空, + '血': 空, + '丶': 空, + '丹': 空, + '青': 空, + '井': 空, + '皀': 空, + '鬯': 空, + '食': 空, + '亼': 空, + '會': 空, + '倉': 空, + '入': 空, + '缶': 空, + '矢': 空, + '高': 空, + '冂': 空, + '𩫖': 空, + '京': 空, + '亯': 空, + '㫗': 空, + '畗': 空, + '㐭': 空, + '嗇': 空, + '來': 空, + '麥': 空, + '夊': 空, + '舛': 空, + '舜': 空, + '韋': 空, + '弟': 空, + '夂': 空, + '久': 空, + '桀': 空, + '木': 空, + '東': 空, + '林': 空, + '才': 空, + '叒': 空, + '之': 空, + '帀': 空, + '出': 空, + '𣎵': 空, + '生': 空, + '乇': 空, + '𠂹': 空, + '𠌶': 空, + '華': 空, + '𥝌': 空, + '稽': 空, + '巢': 空, + '桼': 空, + '束': 空, + '㯻': 空, + '囗': 空, + '員': 空, + '貝': 空, + '邑': 空, + '𨛜': 空, + '日': 空, + '旦': 空, + '倝': 空, + '㫃': 空, + '冥': 空, + '晶': 空, + '月': 空, + '有': 空, + '朙': 空, + '囧': 空, '夕': "🌇", '多': "🪩", - '毌': "🈳️", - '𢎘': "🈳️", - '𣐺': "🈳️", - '𠧪': "🈳️", - '齊': "🈳️", - '朿': "🈳️", - '片': "🈳️", - '鼎': "🈳️", - '克': "🈳️", - '彔': "🈳️", - '禾': "🈳️", - '秝': "🈳️", - '黍': "🈳️", - '香': "🈳️", - '米': "🈳️", - '毇': "🈳️", - '臼': "🈳️", - '凶': "🈳️", - '朩': "🈳️", - '𣏟': "🈳️", - '麻': "🈳️", - '尗': "🈳️", - '耑': "🈳️", - '韭': "🈳️", - '瓜': "🈳️", - '瓠': "🈳️", + '毌': 空, + '𢎘': 空, + '𣐺': 空, + '𠧪': 空, + '齊': 空, + '朿': 空, + '片': 空, + '鼎': 空, + '克': 空, + '彔': 空, + '禾': 空, + '秝': 空, + '黍': 空, + '香': 空, + '米': 空, + '毇': 空, + '臼': 空, + '凶': 空, + '朩': 空, + '𣏟': 空, + '麻': 空, + '尗': 空, + '耑': 空, + '韭': 空, + '瓜': 空, + '瓠': 空, '宀': "🏠", '宮': "🏛", '呂': "🩻", '穴': "🕳️", - '㝱': "🈳️", + '㝱': 空, '疒': "😷", '冖': "🗃", - '𠔼': "🈳️", - '冃': "🈳️", - '㒳': "🈳️", - '网': "🈳️", - '襾': "🈳️", + '𠔼': 空, + '冃': 空, + '㒳': 空, + '网': 空, + '襾': 空, '巾': "🧣", - '巿': "🈳️", - '帛': "🈳️", + '巿': 空, + '帛': 空, '白': "⚪", - '㡀': "🈳️", - '黹': "🈳️", - '𠤎': "🈳️", - '匕': "🈳️", - '从': "🈳️", - '比': "🈳️", - '北': "🈳️", - '丘': "🈳️", - '㐺': "🈳️", - '𡈼': "🈳️", - '重': "🈳️", - '臥': "🈳️", - '身': "🈳️", - '㐆': "🈳️", - '衣': "🈳️", - '裘': "🈳️", - '老': "🈳️", - '毛': "🈳️", - '毳': "🈳️", - '尸': "🈳️", - '尺': "🈳️", - '尾': "🈳️", - '履': "🈳️", + '㡀': 空, + '黹': 空, + '𠤎': 空, + '匕': 空, + '从': 空, + '比': 空, + '北': 空, + '丘': 空, + '㐺': 空, + '𡈼': 空, + '重': 空, + '臥': 空, + '身': 空, + '㐆': 空, + '衣': 空, + '裘': 空, + '老': 空, + '毛': 空, + '毳': 空, + '尸': 空, + '尺': 空, + '尾': 空, + '履': 空, '舟': "🛶", - '方': "🈳️", - '儿': "🈳️", - '兄': "🈳️", - '兂': "🈳️", - '皃': "🈳️", - '𠑹': "🈳️", - '先': "🈳️", - '禿': "🈳️", - '見': "🈳️", - '覞': "🈳️", - '欠': "🈳️", - '㱃': "🈳️", - '㳄': "🈳️", - '旡': "🈳️", - '頁': "🈳️", - '𦣻': "🈳️", - '面': "🈳️", - '丏': "🈳️", - '首': "🈳️", - '𥄉': "🈳️", - '須': "🈳️", - '彡': "🈳️", - '彣': "🈳️", - '文': "🈳️", - '髟': "🈳️", - '后': "🈳️", - '司': "🈳️", - '卮': "🈳️", - '卩': "🈳️", - '印': "🈳️", - '色': "🈳️", - '𠨍': "🈳️", - '辟': "🈳️", - '勹': "🈳️", - '包': "🈳️", - '茍': "🈳️", - '鬼': "🈳️", - '甶': "🈳️", - '厶': "🈳️", - '嵬': "🈳️", - '山': "🈳️", - '屾': "🈳️", - '屵': "🈳️", - '广': "🈳️", - '厂': "🈳️", - '丸': "🈳️", - '危': "🈳️", - '石': "🈳️", - '長': "🈳️", - '勿': "🈳️", - '冄': "🈳️", - '而': "🈳️", - '豕': "🈳️", - '㣇': "🈳️", - '彑': "🈳️", - '豚': "🈳️", - '豸': "🈳️", - '𤉡': "🈳️", - '易': "🈳️", - '象': "🈳️", - '馬': "🈳️", - '𢊁': "🈳️", - '鹿': "🈳️", - '麤': "🈳️", - '㲋': "🈳️", - '兔': "🈳️", - '萈': "🈳️", - '犬': "🈳️", - '㹜': "🈳️", - '鼠': "🈳️", - '能': "🈳️", - '熊': "🈳️", - '火': "🈳️", - '炎': "🈳️", - '黑': "🈳️", - '囪': "🈳️", - '焱': "🈳️", - '炙': "🈳️", - '赤': "🈳️", - '大': "🈳️", - '亦': "🈳️", - '夨': "🈳️", - '夭': "🈳️", - '交': "🈳️", - '尣': "🈳️", - '壺': "🈳️", - '壹': "🈳️", - '㚔': "🈳️", - '奢': "🈳️", - '亢': "🈳️", - '夲': "🈳️", - '夰': "🈳️", - '亣': "🈳️", - '夫': "🈳️", - '立': "🈳️", - '竝': "🈳️", - '囟': "🈳️", - '思': "🈳️", - '心': "🈳️", - '惢': "🈳️", - '水': "🈳️", - '沝': "🈳️", - '瀕': "🈳️", - '𡿨': "🈳️", - '巜': "🈳️", - '川': "🈳️", - '泉': "🈳️", - '灥': "🈳️", - '永': "🈳️", - '𠂢': "🈳️", - '谷': "🈳️", - '仌': "🈳️", - '雨': "🈳️", - '雲': "🈳️", - '魚': "🈳️", - '𩺰': "🈳️", - '燕': "🈳️", - '龍': "🈳️", - '飛': "🈳️", - '非': "🈳️", - '卂': "🈳️", - '𠃉': "🈳️", - '不': "🈳️", - '至': "🈳️", - '西': "🈳️", - '鹵': "🈳️", - '鹽': "🈳️", - '戶': "🈳️", - '門': "🈳️", - '耳': "🈳️", - '𦣞': "🈳️", + '方': 空, + '儿': 空, + '兄': 空, + '兂': 空, + '皃': 空, + '𠑹': 空, + '先': 空, + '禿': 空, + '見': 空, + '覞': 空, + '欠': 空, + '㱃': 空, + '㳄': 空, + '旡': 空, + '頁': 空, + '𦣻': 空, + '面': 空, + '丏': 空, + '首': 空, + '𥄉': 空, + '須': 空, + '彡': 空, + '彣': 空, + '文': 空, + '髟': 空, + '后': 空, + '司': 空, + '卮': 空, + '卩': 空, + '印': 空, + '色': 空, + '𠨍': 空, + '辟': 空, + '勹': 空, + '包': 空, + '茍': 空, + '鬼': 空, + '甶': 空, + '厶': 空, + '嵬': 空, + '山': 空, + '屾': 空, + '屵': 空, + '广': 空, + '厂': 空, + '丸': 空, + '危': 空, + '石': 空, + '長': 空, + '勿': 空, + '冄': 空, + '而': 空, + '豕': 空, + '㣇': 空, + '彑': 空, + '豚': 空, + '豸': 空, + '𤉡': 空, + '易': 空, + '象': 空, + '馬': 空, + '𢊁': 空, + '鹿': 空, + '麤': 空, + '㲋': 空, + '兔': 空, + '萈': 空, + '犬': 空, + '㹜': 空, + '鼠': 空, + '能': 空, + '熊': 空, + '火': 空, + '炎': 空, + '黑': 空, + '囪': 空, + '焱': 空, + '炙': 空, + '赤': 空, + '大': 空, + '亦': 空, + '夨': 空, + '夭': 空, + '交': 空, + '尣': 空, + '壺': 空, + '壹': 空, + '㚔': 空, + '奢': 空, + '亢': 空, + '夲': 空, + '夰': 空, + '亣': 空, + '夫': 空, + '立': 空, + '竝': 空, + '囟': 空, + '思': 空, + '心': 空, + '惢': 空, + '水': 空, + '沝': 空, + '瀕': 空, + '𡿨': 空, + '巜': 空, + '川': 空, + '泉': 空, + '灥': 空, + '永': 空, + '𠂢': 空, + '谷': 空, + '仌': 空, + '雨': 空, + '雲': 空, + '魚': 空, + '𩺰': 空, + '燕': 空, + '龍': 空, + '飛': 空, + '非': 空, + '卂': 空, + '𠃉': 空, + '不': 空, + '至': 空, + '西': 空, + '鹵': 空, + '鹽': 空, + '戶': 空, + '門': 空, + '耳': 空, + '𦣞': 空, '手': "✋", - '𠦬': "🈳️", - '毋': "🈳️", - '民': "🈳️", - '丿': "🈳️", - '𠂆': "🈳️", - '乁': "🈳️", - '氏': "🈳️", - '氐': "🈳️", - '戈': "🈳️", - '戉': "🈳️", - '我': "🈳️", - '亅': "🈳️", - '珡': "🈳️", - '𠃊': "🈳️", - '亾': "🈳️", - '匸': "🈳️", - '匚': "🈳️", - '曲': "🈳️", - '甾': "🈳️", - '瓦': "🈳️", - '弓': "🈳️", - '弜': "🈳️", - '弦': "🈳️", - '系': "🈳️", - '糸': "🈳️", - '素': "🈳️", - '絲': "🈳️", - '率': "🈳️", - '虫': "🈳️", - '䖵': "🈳️", - '蟲': "🈳️", - '風': "🈳️", - '它': "🈳️", - '龜': "🈳️", - '黽': "🈳️", - '卵': "🈳️", - '二': "🈳️", - '土': "🈳️", - '垚': "🈳️", - '堇': "🈳️", - '里': "🈳️", - '畕': "🈳️", - '黃': "🈳️", - '男': "🈳️", - '力': "🈳️", - '劦': "🈳️", + '𠦬': 空, + '毋': 空, + '民': 空, + '丿': 空, + '𠂆': 空, + '乁': 空, + '氏': 空, + '氐': 空, + '戈': 空, + '戉': 空, + '我': 空, + '亅': 空, + '珡': 空, + '𠃊': 空, + '亾': 空, + '匸': 空, + '匚': 空, + '曲': 空, + '甾': 空, + '瓦': 空, + '弓': 空, + '弜': 空, + '弦': 空, + '系': 空, + '糸': 空, + '素': 空, + '絲': 空, + '率': 空, + '虫': 空, + '䖵': 空, + '蟲': 空, + '風': 空, + '它': 空, + '龜': 空, + '黽': 空, + '卵': 空, + '二': 空, + '土': 空, + '垚': 空, + '堇': 空, + '里': 空, + '畕': 空, + '黃': 空, + '男': 空, + '力': 空, + '劦': 空, '金': "💰", - '幵': "🈳️", - '勺': "🈳️", - '几': "🈳️", - '且': "🈳️", - '斤': "🈳️", - '斗': "🈳️", - '矛': "🈳️", - '車': "🈳️", - '𠂤': "🈳️", - '𨸏': "🈳️", - '𨺅': "🈳️", - '厽': "🈳️", - '四': "🈳️", - '宁': "🈳️", - '叕': "🈳️", - '亞': "🈳️", - '五': "🈳️", - '六': "🈳️", - '七': "🈳️", - '九': "🈳️", - '禸': "🈳️", - '嘼': "🈳️", - '甲': "🈳️", - '乙': "🈳️", - '丙': "🈳️", - '丁': "🈳️", - '戊': "🈳️", - '己': "🈳️", - '巴': "🈳️", - '庚': "🈳️", - '辛': "🈳️", - '辡': "🈳️", - '壬': "🈳️", - '癸': "🈳️", + '幵': 空, + '勺': 空, + '几': 空, + '且': 空, + '斤': 空, + '斗': 空, + '矛': 空, + '車': 空, + '𠂤': 空, + '𨸏': 空, + '𨺅': 空, + '厽': 空, + '四': 空, + '宁': 空, + '叕': 空, + '亞': 空, + '五': 空, + '六': 空, + '七': 空, + '九': 空, + '禸': 空, + '嘼': 空, + '甲': 空, + '乙': 空, + '丙': 空, + '丁': 空, + '戊': 空, + '己': 空, + '巴': 空, + '庚': 空, + '辛': 空, + '辡': 空, + '壬': 空, + '癸': 空, '子': "🚼", - '了': "🈳️", - '孨': "🈳️", - '𠫓': "🈳️", - '丑': "🈳️", - '寅': "🈳️", - '戼': "🈳️", - '辰': "🈳️", - '巳': "🈳️", - '午': "🈳️", - '未': "🈳️", - '申': "🈳️", - '酉': "🈳️", - '酋': "🈳️", - '戌': "🈳️", - '亥': "🈳️", + '了': 空, + '孨': 空, + '𠫓': 空, + '丑': 空, + '寅': 空, + '戼': 空, + '辰': 空, + '巳': 空, + '午': 空, + '未': 空, + '申': 空, + '酉': 空, + '酋': 空, + '戌': 空, + '亥': 空, } diff --git a/string.go b/string.go index fa49c86..3c711d9 100644 --- a/string.go +++ b/string.go @@ -1,12 +1,17 @@ package emozi import ( + "errors" "hash/crc32" "strings" base14 "github.com/fumiama/go-base16384" ) +var ( + ErrInvalidEmoziString = errors.New("invalid EmoziString") +) + // EmoziString 一个颜文字汉字转写串, 包含串头 校验字节数*2 字节校验和 type EmoziString string @@ -35,7 +40,7 @@ func WrapRawEmoziString(s string) EmoziString { // String 输出不包含串头的转写串 func (es EmoziString) String() string { if !es.IsValid() { - return "ERROR: invalid EmoziString" + return ErrInvalidEmoziString.Error() } rs := []rune(es) sb := strings.Builder{}