1
0
mirror of https://github.com/fumiama/emozi.git synced 2026-06-05 00:32:48 +08:00

feat(coder): finish decode

This commit is contained in:
源文雨
2024-02-15 15:28:57 +09:00
parent f7cc2d15d0
commit 96223b1b74
10 changed files with 883 additions and 509 deletions

View File

@@ -15,7 +15,7 @@ package emozi
// 部首后备 内嵌的部首到颜文字的映射, 是第二优先查询顺位. 第一顺位是数据库的部首表. 第三位是回落到 🈳️. // 部首后备 内嵌的部首到颜文字的映射, 是第二优先查询顺位. 第一顺位是数据库的部首表. 第三位是回落到 🈳️.
var 部首后备 = map[rune]string{ {{range .}} var 部首后备 = map[rune]string{ {{range .}}
'{{.R}}': "🈳️",{{end}} '{{.R}}': ,{{end}}
} }
` `

116
coder.go
View File

@@ -13,15 +13,16 @@ import (
// Coder encoder/decoder // Coder encoder/decoder
type Coder struct { type Coder struct {
mu sync.RWMutex mu sync.RWMutex
db sql.Sqlite db sql.Sqlite
字表缓存 map[rune][]字表 字表缓存 map[rune][]字表
部首缓存 map[rune]string 逆字表缓存 map[int64][]rune
isRandom bool 部首缓存 map[rune]string
逆部首缓存 map[string][]rune
} }
// NewCoder israndom 随机挑选声母韵母的颜文字, 否则固定使用第一个 // 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 { if _, err = os.Stat(EmoziDatabasePath); err != nil {
err = os.WriteFile(EmoziDatabasePath, 字数据库, 0644) err = os.WriteFile(EmoziDatabasePath, 字数据库, 0644)
if err != nil { if err != nil {
@@ -30,8 +31,9 @@ func NewCoder(israndom bool, cachettl time.Duration) (c Coder, err error) {
} }
c.db.DBPath = EmoziDatabasePath c.db.DBPath = EmoziDatabasePath
c.字表缓存 = make(map[rune][]字表, 4096) c.字表缓存 = make(map[rune][]字表, 4096)
c.逆字表缓存 = make(map[int64][]rune, 4096)
c.部首缓存 = make(map[rune]string, 4096) c.部首缓存 = make(map[rune]string, 4096)
c.isRandom = israndom c.逆部首缓存 = make(map[string][]rune, 4096)
err = c.db.Open(cachettl) err = c.db.Open(cachettl)
if err != nil { if err != nil {
return return
@@ -56,19 +58,23 @@ func NewCoder(israndom bool, cachettl time.Duration) (c Coder, err error) {
func (c *Coder) Close() error { func (c *Coder) Close() error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
c.字表缓存 = nil
c.逆字表缓存 = nil
c.部首缓存 = nil
c.逆部首缓存 = nil
return c.db.Close() return c.db.Close()
} }
// Encode 从汉字序列生成 EmoziString 返回 EmoziString 多音字选择数列表 // 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{} sb := strings.Builder{}
lstbuf := make([]字表, 0, len(s)/2) lstbuf := make([]字表, 0, len(s)/2)
var lst []字表 var lst []字表
write := func(x *字表) { write := func(x *字表) {
sb.WriteString(c.声母(x.S)) sb.WriteString(c.声母(enableRandom, x.S))
sb.WriteString(c.韵母(x.Y)) sb.WriteString(c.韵母(enableRandom, x.Y))
sb.WriteString(c.声调(x.T)) sb.WriteString(c.声调(enableRandom, x.T))
sb.WriteString(c.部首(x.R)) sb.WriteString(c.部首(x.R))
} }
多音字计数 := 0 多音字计数 := 0
多音字数表 := []int{} 多音字数表 := []int{}
@@ -104,6 +110,92 @@ func (c *Coder) Encode(s string, selections ...int) (EmoziString, []int, error)
return WrapRawEmoziString(sb.String()), 多音字数表, nil 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 向主库添加一个新字 // AddChar 向主库添加一个新字
// //
// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音 // w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音

View File

@@ -6,11 +6,11 @@ import (
) )
func TestEncode(t *testing.T) { func TestEncode(t *testing.T) {
c, err := NewCoder(false, time.Minute) c, err := NewCoder(time.Minute)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
es, lst, err := c.Encode("你好,世界!看看多音字:行。") es, lst, err := c.Encode(false, "你好,世界!看看多音字:行。")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -21,7 +21,7 @@ func TestEncode(t *testing.T) {
if len(lst) != 1 && lst[0] != 2 { if len(lst) != 1 && lst[0] != 2 {
t.Fail() t.Fail()
} }
es, lst, err = c.Encode("你好,世界!指定多音字:银行行。", 1, 0) es, lst, err = c.Encode(false, "你好,世界!指定多音字:银行行。", 1, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -33,3 +33,37 @@ func TestEncode(t *testing.T) {
t.Fail() 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)
}
}

86
data.go
View File

@@ -2,7 +2,9 @@ package emozi
import ( import (
_ "embed" _ "embed"
"errors"
"strconv" "strconv"
"strings"
) )
// 字数据库 数据来自 https://github.com/shuowenjiezi/shuowen // 字数据库 数据来自 https://github.com/shuowenjiezi/shuowen
@@ -11,7 +13,7 @@ import (
var 字数据库 []byte var 字数据库 []byte
// DatabasePath 字数据库的路径 如找不到会向对应路径写入内嵌的字数据库 // DatabasePath 字数据库的路径 如找不到会向对应路径写入内嵌的字数据库
var EmoziDatabasePath = "字a.db" var EmoziDatabasePath = "字.db"
const ( const (
主字表名 = "emozi" 主字表名 = "emozi"
@@ -19,6 +21,10 @@ const (
部首表名 = "radcl" 部首表名 = "radcl"
) )
var (
ErrNoSuchChar = errors.New("no such char")
)
// 字表 emozi表 定义 // 字表 emozi表 定义
type 字表 struct { type 字表 struct {
ID int64 // ID 高 32 位 W 的 rune, 低 32 位 保留8 S8 Y8 T8 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))) 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 // 查字 返回 lst lstbuf error
func (c *Coder) 查字(ch rune, lstbuf []字表) ([]字表, []字表, error) { func (c *Coder) 查字(ch rune, lstbuf []字表) ([]字表, []字表, error) {
c.mu.RLock() c.mu.RLock()
lst, ok := c.字表缓存[ch] lst, ok := c.字表缓存[ch]
c.mu.RUnlock() c.mu.RUnlock()
if ok { if ok {
if len(lst) == 0 {
return nil, lstbuf, ErrNoSuchChar
}
return lst, lstbuf, nil return lst, lstbuf, nil
} }
lstbuf = lstbuf[:0] lstbuf = lstbuf[:0]
x := 字表{} x := 字表{}
q := "WHERE W=" + strconv.Itoa(int(ch))
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() 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) lstbuf = append(lstbuf, x)
return nil return nil
}) })
if err != nil { if err != nil {
lstbuf = lstbuf[:0] 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) lstbuf = append(lstbuf, x)
return nil 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)) lstsave := make([]字表, len(lstbuf))
copy(lstsave, lstbuf) copy(lstsave, lstbuf)
c.字表缓存[ch] = lstsave 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
} }
// 从表 从部首表 // 从表 从部首表

View File

@@ -1,8 +1,6 @@
package emozi package emozi
import ( import (
"strings"
base14 "github.com/fumiama/go-base16384" base14 "github.com/fumiama/go-base16384"
) )
@@ -11,9 +9,9 @@ type 声母枚举 uint8
// String 国际音标 // String 国际音标
func (sm 声母枚举) String() string { func (sm 声母枚举) String() string {
a := sm * 3 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] 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 ( 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 type 韵母枚举 uint8
// String 国际音标 // String 国际音标
@@ -150,6 +175,12 @@ var 韵母 = [...][]string{
{string([]rune("🕸️")[0]), "🕸️"}, {"💡", "🪔"}, {"🦅"}, {"🐝"}, {"🌈"}, {"😳"}, // jiong 囧 {string([]rune("🕸️")[0]), "🕸️"}, {"💡", "🪔"}, {"🦅"}, {"🐝"}, {"🌈"}, {"😳"}, // jiong 囧
} }
// 低阶逆韵母 rune韵母到韵母的逆映射
var 低阶逆韵母 = 低阶逆初始化(韵母[:], 8)
// 逆韵母 韵母到枚举的逆映射
var 逆韵母 = 逆初始化[韵母枚举](韵母[:], 8)
type 声调枚举 uint8 type 声调枚举 uint8
// String 返回传统调名 // String 返回传统调名
@@ -169,6 +200,12 @@ const (
var 声调 = [...][]string{{"🍉"}, {"🧗", "🧗‍♀️", "🧗‍♂️", "🦎"}, {"🐴", "🐎"}, {"😨"}, {"😯"}} var 声调 = [...][]string{{"🍉"}, {"🧗", "🧗‍♀️", "🧗‍♂️", "🦎"}, {"🐴", "🐎"}, {"😨"}, {"😯"}}
// 低阶逆声调 rune声调到声调的逆映射
var 低阶逆声调 = 低阶逆初始化(声调[:], 4)
// 逆声调 声调到枚举的逆映射
var 逆声调 = 逆初始化[声调枚举](声调[:], 4)
// 校验表 用 校验表长度 个unicode控制字符做校验和验证此序列是由本程序生成而非手写的 // 校验表 用 校验表长度 个unicode控制字符做校验和验证此序列是由本程序生成而非手写的
// //
// 具体做法是先对后面的文本做crc32然后取 校验表长度^校验字节数 的模 // 具体做法是先对后面的文本做crc32然后取 校验表长度^校验字节数 的模
@@ -208,3 +245,14 @@ var 逆校验表 = func() map[rune]uint8 {
} }
return m 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
}()

149
lookup.go
View File

@@ -3,44 +3,35 @@ package emozi
import ( import (
"math/rand" "math/rand"
"strconv" "strconv"
"strings"
) )
var = "🈳️" var = "🈳️"
func (c *Coder) 查声母(s 声母枚举) string { func 随机正查(m [][]string, isRandom bool, i uint8) string {
lst := 声母[s] lst := m[i]
if len(lst) == 0 { if len(lst) == 0 {
return return
} }
if len(lst) == 1 || !c.isRandom { if len(lst) == 1 || !isRandom {
return lst[0] return lst[0]
} }
return lst[rand.Intn(len(lst))] return lst[rand.Intn(len(lst))]
} }
func (c *Coder) 查韵(y 母枚举) string { func (c *Coder) (isRandom bool, s 母枚举) string {
lst := [y] return 随机正查([:], isRandom, uint8(s))
if len(lst) == 0 {
return
}
if len(lst) == 1 || !c.isRandom {
return lst[0]
}
return lst[rand.Intn(len(lst))]
} }
func (c *Coder) 查声调(t 声调枚举) string { func (c *Coder) 韵母(isRandom bool, y 韵母枚举) string {
lst := 声调[t] return 随机正查(韵母[:], isRandom, uint8(y))
if len(lst) == 0 {
return
}
if len(lst) == 1 || !c.isRandom {
return lst[0]
}
return lst[rand.Intn(len(lst))]
} }
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() c.mu.RLock()
e, ok := c.部首缓存[r] e, ok := c.部首缓存[r]
c.mu.RUnlock() c.mu.RUnlock()
@@ -62,3 +53,117 @@ func (c *Coder) 查部首(r rune) string {
c.部首缓存[r] = c.部首缓存[r] =
return 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
}

View File

@@ -1,6 +1,18 @@
package emozi 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) { func TestSplitPinyin(t *testing.T) {
s, y, tone, err := SplitPinyin("yōng") s, y, tone, err := SplitPinyin("yōng")

View File

@@ -29,7 +29,7 @@ func main() {
panic(fmt.Sprintf("ERROR: creating gocc: %v", err)) panic(fmt.Sprintf("ERROR: creating gocc: %v", err))
} }
_ = os.RemoveAll(emozi.EmoziDatabasePath) _ = os.RemoveAll(emozi.EmoziDatabasePath)
c, err := emozi.NewCoder(false, time.Minute) c, err := emozi.NewCoder(time.Minute)
if err != nil { if err != nil {
panic(fmt.Sprintf("ERROR: creating emozi coder: %v", err)) panic(fmt.Sprintf("ERROR: creating emozi coder: %v", err))
} }

View File

@@ -40,508 +40,508 @@ var 部首后备 = map[rune]string{
'辵': "🚶", '辵': "🚶",
'彳': "🚦", '彳': "🚦",
'廴': "🚥", '廴': "🚥",
'㢟': "🈳️", '㢟': ,
'行': "⛕", '行': "⛕",
'齒': "🈳️", '齒': ,
'牙': "🈳️", '牙': ,
'足': "🈳️", '足': ,
'疋': "🈳️", '疋': ,
'品': "🈳️", '品': ,
'龠': "🈳️", '龠': ,
'冊': "🈳️", '冊': ,
'㗊': "🈳️", '㗊': ,
'舌': "👅", '舌': "👅",
'干': "🈳️", '干': ,
'𧮫': "🈳️", '𧮫': ,
'只': "🈳️", '只': ,
'㕯': "🈳️", '㕯': ,
'句': "🈳️", '句': ,
'丩': "🈳️", '丩': ,
'古': "🏺", '古': "🏺",
'十': "🔟", '十': "🔟",
'卅': "🌍", '卅': "🌍",
'言': "💬", '言': "💬",
'田': "👨‍🌾", '田': "👨‍🌾",
'誩': "🈳️", '誩': ,
'音': "🎵", '音': "🎵",
'䇂': "🈳️", '䇂': ,
'丵': "🈳️", '丵': ,
'菐': "🈳️", '菐': ,
'𠬞': "🈳️", '𠬞': ,
'𠬜': "🈳️", '𠬜': ,
'共': "🈳️", '共': ,
'異': "🈳️", '異': ,
'舁': "🈳️", '舁': ,
'𦥑': "🈳️", '𦥑': ,
'䢅': "🈳️", '䢅': ,
'爨': "🈳️", '爨': ,
'革': "🈳️", '革': ,
'鬲': "🈳️", '鬲': ,
'䰜': "🈳️", '䰜': ,
'爪': "🤏", '爪': "🤏",
'丮': "🈳️", '丮': ,
'鬥': "👊", '鬥': "👊",
'又': "🈳️", '又': ,
'𠂇': "🈳️", '𠂇': ,
'史': "🈳️", '史': ,
'支': "🈳️", '支': ,
'𦘒': "🈳️", '𦘒': ,
'聿': "✍", '聿': "✍",
'畫': "🎨", '畫': "🎨",
'隶': "🈳️", '隶': ,
'臤': "🈳️", '臤': ,
'臣': "🈳️", '臣': ,
'殳': "🈳️", '殳': ,
'殺': "🪚", '殺': "🪚",
'𠘧': "🈳️", '𠘧': ,
'寸': "🈳️", '寸': ,
'皮': "🈳️", '皮': ,
'㼱': "🈳️", '㼱': ,
'攴': "🈳️", '攴': ,
'女': "👩", '女': "👩",
'教': "🈳️", '教': ,
'卜': "🔮", '卜': "🔮",
'用': "🈳️", '用': ,
'爻': "🈳️", '爻': ,
'㸚': "🈳️", '㸚': ,
'𡕥': "🈳️", '𡕥': ,
'目': "👁️", '目': "👁️",
'䀠': "👀", '䀠': "👀",
'眉': "🤨", '眉': "🤨",
'盾': "🈳️", '盾': ,
'自': "👃", '自': "👃",
'𪞶': "🈳️", '𪞶': ,
'鼻': "👃", '鼻': "👃",
'皕': "🈳️", '皕': ,
'習': "🈳️", '習': ,
'羽': "🪶", '羽': "🪶",
'隹': "🐦", '隹': "🐦",
'奞': "🈳️", '奞': ,
'雈': "🈳️", '雈': ,
'𦫳': "🈳️", '𦫳': ,
'𥄕': "🈳️", '𥄕': ,
'羊': "🐑", '羊': "🐑",
'羴': "🈳️", '羴': ,
'瞿': "🈳️", '瞿': ,
'雔': "🈳️", '雔': ,
'雥': "🈳️", '雥': ,
'鳥': "🦢", '鳥': "🦢",
'烏': "🐦‍⬛", '烏': "🐦‍⬛",
'𠦒': "🈳", '𠦒': ,
'冓': "🈳️", '冓': ,
'幺': "🈳️", '幺': ,
'𢆶': "🈳️", '𢆶': ,
'叀': "🈳️", '叀': ,
'玄': "🈳️", '玄': ,
'予': "🈳️", '予': ,
'放': "🈳️", '放': ,
'𠬪': "🈳️", '𠬪': ,
'𣦼': "🈳️", '𣦼': ,
'歺': "🈳️", '歺': ,
'死': "🈳️", '死': ,
'冎': "🈳️", '冎': ,
'骨': "🈳️", '骨': ,
'肉': "🈳️", '肉': ,
'筋': "🈳️", '筋': ,
'刀': "🔪", '刀': "🔪",
'刃': "🔪", '刃': "🔪",
'㓞': "🈳️", '㓞': ,
'丯': "🈳️", '丯': ,
'耒': "🈳️", '耒': ,
'角': "🥐", '角': "🥐",
'竹': "🎋", '竹': "🎋",
'箕': "🈳️", '箕': ,
'丌': "🈳️", '丌': ,
'左': "👈", '左': "👈",
'工': "🈳️", '工': ,
'㠭': "🈳️", '㠭': ,
'巫': "🈳️", '巫': ,
'甘': "🈳️", '甘': ,
'曰': "🈳️", '曰': ,
'乃': "🈳️", '乃': ,
'丂': "🈳️", '丂': ,
'可': "🈳️", '可': ,
'兮': "🈳️", '兮': ,
'号': "🈳️", '号': ,
'亏': "🈳️", '亏': ,
'旨': "🈳️", '旨': ,
'喜': "🈳️", '喜': ,
'壴': "🈳️", '壴': ,
'鼓': "🈳️", '鼓': ,
'豈': "🈳️", '豈': ,
'豆': "🈳️", '豆': ,
'豊': "🈳️", '豊': ,
'豐': "🈳️", '豐': ,
'䖒': "🈳️", '䖒': ,
'虍': "🈳️", '虍': ,
'虎': "🈳️", '虎': ,
'虤': "🈳️", '虤': ,
'皿': "🈳️", '皿': ,
'𠙴': "🈳️", '𠙴': ,
'去': "🈳️", '去': ,
'血': "🈳️", '血': ,
'': "🈳️", '': ,
'丹': "🈳️", '丹': ,
'青': "🈳️", '青': ,
'井': "🈳️", '井': ,
'皀': "🈳️", '皀': ,
'鬯': "🈳️", '鬯': ,
'食': "🈳️", '食': ,
'亼': "🈳️", '亼': ,
'會': "🈳️", '會': ,
'倉': "🈳️", '倉': ,
'入': "🈳️", '入': ,
'缶': "🈳️", '缶': ,
'矢': "🈳️", '矢': ,
'高': "🈳️", '高': ,
'冂': "🈳️", '冂': ,
'𩫖': "🈳️", '𩫖': ,
'京': "🈳️", '京': ,
'亯': "🈳️", '亯': ,
'㫗': "🈳️", '㫗': ,
'畗': "🈳️", '畗': ,
'㐭': "🈳️", '㐭': ,
'嗇': "🈳️", '嗇': ,
'來': "🈳️", '來': ,
'麥': "🈳️", '麥': ,
'夊': "🈳️", '夊': ,
'舛': "🈳️", '舛': ,
'舜': "🈳️", '舜': ,
'韋': "🈳️", '韋': ,
'弟': "🈳️", '弟': ,
'夂': "🈳️", '夂': ,
'久': "🈳️", '久': ,
'桀': "🈳️", '桀': ,
'木': "🈳️", '木': ,
'東': "🈳️", '東': ,
'林': "🈳️", '林': ,
'才': "🈳️", '才': ,
'叒': "🈳️", '叒': ,
'之': "🈳️", '之': ,
'帀': "🈳️", '帀': ,
'出': "🈳️", '出': ,
'𣎵': "🈳️", '𣎵': ,
'生': "🈳️", '生': ,
'乇': "🈳️", '乇': ,
'𠂹': "🈳️", '𠂹': ,
'𠌶': "🈳️", '𠌶': ,
'華': "🈳️", '華': ,
'𥝌': "🈳️", '𥝌': ,
'稽': "🈳️", '稽': ,
'巢': "🈳️", '巢': ,
'桼': "🈳️", '桼': ,
'束': "🈳️", '束': ,
'㯻': "🈳️", '㯻': ,
'囗': "🈳️", '囗': ,
'員': "🈳️", '員': ,
'貝': "🈳️", '貝': ,
'邑': "🈳️", '邑': ,
'𨛜': "🈳️", '𨛜': ,
'日': "🈳️", '日': ,
'旦': "🈳️", '旦': ,
'倝': "🈳️", '倝': ,
'㫃': "🈳️", '㫃': ,
'冥': "🈳️", '冥': ,
'晶': "🈳️", '晶': ,
'月': "🈳️", '月': ,
'有': "🈳️", '有': ,
'朙': "🈳️", '朙': ,
'囧': "🈳️", '囧': ,
'夕': "🌇", '夕': "🌇",
'多': "🪩", '多': "🪩",
'毌': "🈳️", '毌': ,
'𢎘': "🈳️", '𢎘': ,
'𣐺': "🈳️", '𣐺': ,
'𠧪': "🈳️", '𠧪': ,
'齊': "🈳️", '齊': ,
'朿': "🈳️", '朿': ,
'片': "🈳️", '片': ,
'鼎': "🈳️", '鼎': ,
'克': "🈳️", '克': ,
'彔': "🈳️", '彔': ,
'禾': "🈳️", '禾': ,
'秝': "🈳️", '秝': ,
'黍': "🈳️", '黍': ,
'香': "🈳️", '香': ,
'米': "🈳️", '米': ,
'毇': "🈳️", '毇': ,
'臼': "🈳️", '臼': ,
'凶': "🈳️", '凶': ,
'朩': "🈳️", '朩': ,
'𣏟': "🈳️", '𣏟': ,
'麻': "🈳️", '麻': ,
'尗': "🈳️", '尗': ,
'耑': "🈳️", '耑': ,
'韭': "🈳️", '韭': ,
'瓜': "🈳️", '瓜': ,
'瓠': "🈳️", '瓠': ,
'宀': "🏠", '宀': "🏠",
'宮': "🏛", '宮': "🏛",
'呂': "🩻", '呂': "🩻",
'穴': "🕳️", '穴': "🕳️",
'㝱': "🈳️", '㝱': ,
'疒': "😷", '疒': "😷",
'冖': "🗃", '冖': "🗃",
'𠔼': "🈳️", '𠔼': ,
'冃': "🈳️", '冃': ,
'㒳': "🈳️", '㒳': ,
'网': "🈳️", '网': ,
'襾': "🈳️", '襾': ,
'巾': "🧣", '巾': "🧣",
'巿': "🈳️", '巿': ,
'帛': "🈳️", '帛': ,
'白': "⚪", '白': "⚪",
'㡀': "🈳️", '㡀': ,
'黹': "🈳️", '黹': ,
'𠤎': "🈳️", '𠤎': ,
'匕': "🈳️", '匕': ,
'从': "🈳️", '从': ,
'比': "🈳️", '比': ,
'北': "🈳️", '北': ,
'丘': "🈳️", '丘': ,
'㐺': "🈳️", '㐺': ,
'𡈼': "🈳️", '𡈼': ,
'重': "🈳️", '重': ,
'臥': "🈳️", '臥': ,
'身': "🈳️", '身': ,
'㐆': "🈳️", '㐆': ,
'衣': "🈳️", '衣': ,
'裘': "🈳️", '裘': ,
'老': "🈳️", '老': ,
'毛': "🈳️", '毛': ,
'毳': "🈳️", '毳': ,
'尸': "🈳️", '尸': ,
'尺': "🈳️", '尺': ,
'尾': "🈳️", '尾': ,
'履': "🈳️", '履': ,
'舟': "🛶", '舟': "🛶",
'方': "🈳️", '方': ,
'儿': "🈳️", '儿': ,
'兄': "🈳️", '兄': ,
'兂': "🈳️", '兂': ,
'皃': "🈳️", '皃': ,
'𠑹': "🈳️", '𠑹': ,
'先': "🈳️", '先': ,
'禿': "🈳️", '禿': ,
'見': "🈳️", '見': ,
'覞': "🈳️", '覞': ,
'欠': "🈳️", '欠': ,
'㱃': "🈳️", '㱃': ,
'㳄': "🈳️", '㳄': ,
'旡': "🈳️", '旡': ,
'頁': "🈳️", '頁': ,
'𦣻': "🈳️", '𦣻': ,
'面': "🈳️", '面': ,
'丏': "🈳️", '丏': ,
'首': "🈳️", '首': ,
'𥄉': "🈳️", '𥄉': ,
'須': "🈳️", '須': ,
'彡': "🈳️", '彡': ,
'彣': "🈳️", '彣': ,
'文': "🈳️", '文': ,
'髟': "🈳️", '髟': ,
'后': "🈳️", '后': ,
'司': "🈳️", '司': ,
'卮': "🈳️", '卮': ,
'卩': "🈳️", '卩': ,
'印': "🈳️", '印': ,
'色': "🈳️", '色': ,
'𠨍': "🈳️", '𠨍': ,
'辟': "🈳️", '辟': ,
'勹': "🈳️", '勹': ,
'包': "🈳️", '包': ,
'茍': "🈳️", '茍': ,
'鬼': "🈳️", '鬼': ,
'甶': "🈳️", '甶': ,
'厶': "🈳️", '厶': ,
'嵬': "🈳️", '嵬': ,
'山': "🈳️", '山': ,
'屾': "🈳️", '屾': ,
'屵': "🈳️", '屵': ,
'广': "🈳️", '广': ,
'厂': "🈳️", '厂': ,
'丸': "🈳️", '丸': ,
'危': "🈳️", '危': ,
'石': "🈳️", '石': ,
'長': "🈳️", '長': ,
'勿': "🈳️", '勿': ,
'冄': "🈳️", '冄': ,
'而': "🈳️", '而': ,
'豕': "🈳️", '豕': ,
'㣇': "🈳️", '㣇': ,
'彑': "🈳️", '彑': ,
'豚': "🈳️", '豚': ,
'豸': "🈳️", '豸': ,
'𤉡': "🈳️", '𤉡': ,
'易': "🈳️", '易': ,
'象': "🈳️", '象': ,
'馬': "🈳️", '馬': ,
'𢊁': "🈳️", '𢊁': ,
'鹿': "🈳️", '鹿': ,
'麤': "🈳️", '麤': ,
'㲋': "🈳️", '㲋': ,
'兔': "🈳️", '兔': ,
'萈': "🈳️", '萈': ,
'犬': "🈳️", '犬': ,
'㹜': "🈳️", '㹜': ,
'鼠': "🈳️", '鼠': ,
'能': "🈳️", '能': ,
'熊': "🈳️", '熊': ,
'火': "🈳️", '火': ,
'炎': "🈳️", '炎': ,
'黑': "🈳️", '黑': ,
'囪': "🈳️", '囪': ,
'焱': "🈳️", '焱': ,
'炙': "🈳️", '炙': ,
'赤': "🈳️", '赤': ,
'大': "🈳️", '大': ,
'亦': "🈳️", '亦': ,
'夨': "🈳️", '夨': ,
'夭': "🈳️", '夭': ,
'交': "🈳️", '交': ,
'尣': "🈳️", '尣': ,
'壺': "🈳️", '壺': ,
'壹': "🈳️", '壹': ,
'㚔': "🈳️", '㚔': ,
'奢': "🈳️", '奢': ,
'亢': "🈳️", '亢': ,
'夲': "🈳️", '夲': ,
'夰': "🈳️", '夰': ,
'亣': "🈳️", '亣': ,
'夫': "🈳️", '夫': ,
'立': "🈳️", '立': ,
'竝': "🈳️", '竝': ,
'囟': "🈳️", '囟': ,
'思': "🈳️", '思': ,
'心': "🈳️", '心': ,
'惢': "🈳️", '惢': ,
'水': "🈳️", '水': ,
'沝': "🈳️", '沝': ,
'瀕': "🈳️", '瀕': ,
'𡿨': "🈳️", '𡿨': ,
'巜': "🈳️", '巜': ,
'川': "🈳️", '川': ,
'泉': "🈳️", '泉': ,
'灥': "🈳️", '灥': ,
'永': "🈳️", '永': ,
'𠂢': "🈳️", '𠂢': ,
'谷': "🈳️", '谷': ,
'仌': "🈳️", '仌': ,
'雨': "🈳️", '雨': ,
'雲': "🈳️", '雲': ,
'魚': "🈳️", '魚': ,
'𩺰': "🈳️", '𩺰': ,
'燕': "🈳️", '燕': ,
'龍': "🈳️", '龍': ,
'飛': "🈳️", '飛': ,
'非': "🈳️", '非': ,
'卂': "🈳️", '卂': ,
'𠃉': "🈳️", '𠃉': ,
'不': "🈳️", '不': ,
'至': "🈳️", '至': ,
'西': "🈳️", '西': ,
'鹵': "🈳️", '鹵': ,
'鹽': "🈳️", '鹽': ,
'戶': "🈳️", '戶': ,
'門': "🈳️", '門': ,
'耳': "🈳️", '耳': ,
'𦣞': "🈳️", '𦣞': ,
'手': "✋", '手': "✋",
'𠦬': "🈳️", '𠦬': ,
'毋': "🈳️", '毋': ,
'民': "🈳️", '民': ,
'丿': "🈳️", '丿': ,
'𠂆': "🈳️", '𠂆': ,
'乁': "🈳️", '乁': ,
'氏': "🈳️", '氏': ,
'氐': "🈳️", '氐': ,
'戈': "🈳️", '戈': ,
'戉': "🈳️", '戉': ,
'我': "🈳️", '我': ,
'亅': "🈳️", '亅': ,
'珡': "🈳️", '珡': ,
'𠃊': "🈳️", '𠃊': ,
'亾': "🈳️", '亾': ,
'匸': "🈳️", '匸': ,
'匚': "🈳️", '匚': ,
'曲': "🈳️", '曲': ,
'甾': "🈳️", '甾': ,
'瓦': "🈳️", '瓦': ,
'弓': "🈳️", '弓': ,
'弜': "🈳️", '弜': ,
'弦': "🈳️", '弦': ,
'系': "🈳️", '系': ,
'糸': "🈳️", '糸': ,
'素': "🈳️", '素': ,
'絲': "🈳️", '絲': ,
'率': "🈳️", '率': ,
'虫': "🈳️", '虫': ,
'䖵': "🈳️", '䖵': ,
'蟲': "🈳️", '蟲': ,
'風': "🈳️", '風': ,
'它': "🈳️", '它': ,
'龜': "🈳️", '龜': ,
'黽': "🈳️", '黽': ,
'卵': "🈳️", '卵': ,
'二': "🈳️", '二': ,
'土': "🈳️", '土': ,
'垚': "🈳️", '垚': ,
'堇': "🈳️", '堇': ,
'里': "🈳️", '里': ,
'畕': "🈳️", '畕': ,
'黃': "🈳️", '黃': ,
'男': "🈳️", '男': ,
'力': "🈳️", '力': ,
'劦': "🈳️", '劦': ,
'金': "💰", '金': "💰",
'幵': "🈳️", '幵': ,
'勺': "🈳️", '勺': ,
'几': "🈳️", '几': ,
'且': "🈳️", '且': ,
'斤': "🈳️", '斤': ,
'斗': "🈳️", '斗': ,
'矛': "🈳️", '矛': ,
'車': "🈳️", '車': ,
'𠂤': "🈳️", '𠂤': ,
'𨸏': "🈳️", '𨸏': ,
'𨺅': "🈳️", '𨺅': ,
'厽': "🈳️", '厽': ,
'四': "🈳️", '四': ,
'宁': "🈳️", '宁': ,
'叕': "🈳️", '叕': ,
'亞': "🈳️", '亞': ,
'五': "🈳️", '五': ,
'六': "🈳️", '六': ,
'七': "🈳️", '七': ,
'九': "🈳️", '九': ,
'禸': "🈳️", '禸': ,
'嘼': "🈳️", '嘼': ,
'甲': "🈳️", '甲': ,
'乙': "🈳️", '乙': ,
'丙': "🈳️", '丙': ,
'丁': "🈳️", '丁': ,
'戊': "🈳️", '戊': ,
'己': "🈳️", '己': ,
'巴': "🈳️", '巴': ,
'庚': "🈳️", '庚': ,
'辛': "🈳️", '辛': ,
'辡': "🈳️", '辡': ,
'壬': "🈳️", '壬': ,
'癸': "🈳️", '癸': ,
'子': "🚼", '子': "🚼",
'了': "🈳️", '了': ,
'孨': "🈳️", '孨': ,
'𠫓': "🈳️", '𠫓': ,
'丑': "🈳️", '丑': ,
'寅': "🈳️", '寅': ,
'戼': "🈳️", '戼': ,
'辰': "🈳️", '辰': ,
'巳': "🈳️", '巳': ,
'午': "🈳️", '午': ,
'未': "🈳️", '未': ,
'申': "🈳️", '申': ,
'酉': "🈳️", '酉': ,
'酋': "🈳️", '酋': ,
'戌': "🈳️", '戌': ,
'亥': "🈳️", '亥': ,
} }

View File

@@ -1,12 +1,17 @@
package emozi package emozi
import ( import (
"errors"
"hash/crc32" "hash/crc32"
"strings" "strings"
base14 "github.com/fumiama/go-base16384" base14 "github.com/fumiama/go-base16384"
) )
var (
ErrInvalidEmoziString = errors.New("invalid EmoziString")
)
// EmoziString 一个颜文字汉字转写串, 包含串头 校验字节数*2 字节校验和 // EmoziString 一个颜文字汉字转写串, 包含串头 校验字节数*2 字节校验和
type EmoziString string type EmoziString string
@@ -35,7 +40,7 @@ func WrapRawEmoziString(s string) EmoziString {
// String 输出不包含串头的转写串 // String 输出不包含串头的转写串
func (es EmoziString) String() string { func (es EmoziString) String() string {
if !es.IsValid() { if !es.IsValid() {
return "ERROR: invalid EmoziString" return ErrInvalidEmoziString.Error()
} }
rs := []rune(es) rs := []rune(es)
sb := strings.Builder{} sb := strings.Builder{}