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 .}}
'{{.R}}': "🈳️",{{end}}
'{{.R}}': ,{{end}}
}
`

116
coder.go
View File

@@ -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: 带声调的拼音

View File

@@ -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)
}
}

86
data.go
View File

@@ -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
}
// 从表 从部首表

View File

@@ -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
}()

149
lookup.go
View File

@@ -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
}

View File

@@ -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")

View File

@@ -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))
}

View File

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

View File

@@ -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{}