1
0
mirror of https://github.com/fumiama/emozi.git synced 2026-06-05 00:32:48 +08:00
This commit is contained in:
源文雨
2024-02-14 18:39:07 +09:00
parent 9463d5e7b6
commit ceb7686da6
18 changed files with 1776 additions and 0 deletions

6
.gitignore vendored
View File

@@ -19,3 +19,9 @@
# Go workspace file
go.work
# MacOS
.DS_Store
# json folder
/data

21
README.md Normal file
View File

@@ -0,0 +1,21 @@
<div align="center">
<h1>EMOZI</h1>
参考古埃及圣书体设计的一种基于颜文字的汉字抽象转写法<br>🐑🚬🧗👤🕸️😐🧗✍️👈🌞😨🏠🌹🧦😨👥🌹🔐😨💦⬅️☀️😨🏡💦💡🍉🌱🍵💡🧗🪓🍆👔😨🐶<br><br>
<img src="https://counter.seku.su/cmoe?name=emozi&theme=r34" /><br>
</div>
W.I.P.
## 实用工具
### 拼音识别拆分
将带声调的拼音拆分为以国际音标表示的声母韵母。
```go
s, y, t, err := emozi.SplitPinyin("jiǒng")
if err != nil {
panic(err)
}
fmt.Println(s, y, tone) // tɕ i̯ʊŋ 上声
```

54
codegen/radical/main.go Normal file
View File

@@ -0,0 +1,54 @@
package main
import (
"html/template"
"os"
"time"
sql "github.com/FloatTech/sqlite"
"github.com/fumiama/emozi"
)
const head = `// Code generated by codegen/radical. 已经经过修改, 不要手动重新运行.
package emozi
// 部首后备 内嵌的部首到颜文字的映射, 是第二优先查询顺位. 第一顺位是数据库的部首表. 第三位是回落到 🈳️.
var 部首后备 = map[rune]string{ {{range .}}
'{{.R}}': "🈳️",{{end}}
}
`
func main() {
db := sql.Sqlite{DBPath: emozi.EmoziDatabasePath}
err := db.Open(time.Minute)
if err != nil {
panic(err)
}
defer db.Close()
type r struct {
R rune
}
type sr struct {
R string
}
var s r
var ss = []sr{}
db.QueryFor("SELECT DISTINCT R FROM emozi;", &s, func() error {
ss = append(ss, sr{R: string(s.R)})
return nil
})
t, err := template.New("list").Parse(head)
if err != nil {
panic(err)
}
f, err := os.Create("radical.go")
if err != nil {
panic(err)
}
defer f.Close()
err = t.Execute(f, ss)
if err != nil {
panic(err)
}
}

178
coder.go Normal file
View File

@@ -0,0 +1,178 @@
package emozi
import (
"errors"
"strconv"
"strings"
"time"
sql "github.com/FloatTech/sqlite"
)
// Coder encoder/decoder
type Coder struct {
db sql.Sqlite
isRandom bool
}
// NewCoder israndom 随机挑选声母韵母的颜文字, 否则固定使用第一个
func NewCoder(israndom bool, cachettl time.Duration) (c Coder, err error) {
c.db.DBPath = EmoziDatabasePath
c.isRandom = israndom
err = c.db.Open(cachettl)
if err != nil {
return
}
err = c.db.Create(主字表名, &字表{})
if err != nil {
return
}
err = c.db.Create(附字表名, &字表{})
if err != nil {
return
}
err = c.db.Create(部首表名, &部首表{})
if err != nil {
return
}
_ = c.db.Query("CREATE INDEX IF NOT EXISTS IE ON "+部首表名+" (E);", nil)
return
}
// Close ...
func (c *Coder) Close() error {
return c.db.Close()
}
// Encode 从汉字序列生成 EmoziString
func (c *Coder) Encode(s string, selections ...int) (EmoziString, error) {
sb := strings.Builder{}
x := &字表{}
lst := []字表{}
write := func(x *字表) {
sb.WriteString(c.查声母(x.S))
sb.WriteString(c.查韵母(x.Y))
sb.WriteString(c.查声调(x.T))
sb.WriteString(c.查部首(x.R))
}
多音字计数 := 0
for _, ch := range s { // nolint: go-staticcheck
lst = lst[:0]
err := c.db.FindFor(附字表名, x, "WHERE W="+strconv.Itoa(int(ch)), func() error {
lst = append(lst, *x)
return nil
})
if err != nil {
lst = lst[:0]
err = c.db.FindFor(主字表名, x, "WHERE W="+strconv.Itoa(int(ch)), func() error {
lst = append(lst, *x)
return nil
})
}
if err != nil || len(lst) == 0 {
sb.WriteRune(ch)
continue
}
if len(lst) == 1 {
write(x)
continue
}
if len(selections) > 多音字计数 {
idx := selections[多音字计数]
多音字计数++
if idx >= 0 && idx < len(lst) {
write(&lst[idx])
continue
}
}
// 多音字
sb.WriteString("[")
write(&lst[0])
for _, x := range lst[1:] {
sb.WriteString("|")
write(&x)
}
sb.WriteString("]")
}
return WrapRawEmoziString(sb.String()), nil
}
// Add 向主库添加一个新字
//
// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音
func (c *Coder) Add(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
}
err = c.db.InsertUnique(主字表名, &字表{
ID: 颜表ID(rw, s, y, t),
W: rw, S: s, Y: y, T: t,
R: rr, P: p, F: f,
})
if err != nil {
return errors.New("已有同音同形的字 '" + w + "'")
}
return nil
}
// Overlay 向附加库添加一个新字, 覆盖在主库之上
//
// w: 字, r: 部首, p: 不带声调的拼音(可空), f: 带声调的拼音
func (c *Coder) Overlay(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
}
return c.overlay(w, p, f, s, y, t, rw, rr)
}
func (c *Coder) overlay(w, p, f string, s 声母枚举, y 韵母枚举, t 声调枚举, rw rune, rr rune) error {
err := c.db.InsertUnique(附字表名, &字表{
ID: 颜表ID(rw, s, y, t),
W: rw, S: s, Y: y, T: t,
R: rr, P: p, F: f,
})
if err != nil {
return errors.New("已有同音同形的字 '" + w + "'")
}
return nil
}
// ChangeOverlay 更改附加库的一项
func (c *Coder) ChangeOverlay(oldw, oldr, oldf, neww, newr, newf string) error {
s, y, t, rw, rr, err := 拆音识字(oldw, oldr, 去调(oldf), oldf)
if err != nil {
return err
}
newp := 去调(newf)
ns, ny, nt, nrw, nrr, err := 拆音识字(neww, newr, newp, newf)
if err != nil {
return err
}
q := "WHERE ID=" + strconv.FormatInt(颜表ID(rw, s, y, t), 10)
x := 字表{}
err = c.db.Find(附字表名, &x, q)
if err != nil {
return err
}
if x.R != rr {
return errors.New("提供的旧部首 '" + string(rr) + "' 与记载的 '" + string(x.R) + "' 不符")
}
err = c.db.Del(附字表名, q)
if err != nil {
return err
}
return c.overlay(neww, newp, newf, ns, ny, nt, nrw, nrr)
}
// OverlayRadical 添加一个部首
func (c *Coder) OverlayRadical(r rune, e string) error {
return c.db.InsertUnique(部首表名, &部首表{R: r, E: e})
}

29
coder_test.go Normal file
View File

@@ -0,0 +1,29 @@
package emozi
import (
"testing"
"time"
)
func TestEncode(t *testing.T) {
c, err := NewCoder(false, time.Minute)
if err != nil {
t.Fatal(err)
}
es, err := c.Encode("你好,世界!看看多音字:行。")
if err != nil {
t.Fatal(err)
}
t.Log(es.String())
if es.String() != "🥛👔🐴👤🌹🐱🐴👩,💦🌞😨🌍➕✌😨👨‍🌾!😭🔐🍉👁️😭🔐🍉👁️🔪🌀🍉🪩🐑🎵🍉🎵👈🌞😨🚼:[👇🦅🧗⛕|🌹👍🧗⛕]。" {
t.Fatal("got", es.String())
}
es, err = c.Encode("你好,世界!指定多音字:银行行。", 1, 0)
if err != nil {
t.Fatal(err)
}
t.Log(es.String())
if es.String() != "🥛👔🐴👤🌹🐱🐴👩,💦🌞😨🌍➕✌😨👨‍🌾!🐽🌞🐴✋🔪🦅😨🏠🔪🌀🍉🪩🐑🎵🍉🎵👈🌞😨🚼:🐑🎵🧗💰🌹👍🧗⛕👇🦅🧗⛕。" {
t.Fatal("got", es.String())
}
}

38
data.go Normal file
View File

@@ -0,0 +1,38 @@
package emozi
import _ "embed"
// 字数据库 数据来自 https://github.com/shuowenjiezi/shuowen
//
//var 字数据库 []byte
// DatabasePath 字数据库的路径 如找不到会向对应路径写入内嵌的字数据库
var EmoziDatabasePath = "字.db"
const (
主字表名 = "emozi"
附字表名 = "altzi"
部首表名 = "radcl"
)
// 字表 emozi表 定义
type 字表 struct {
ID int64 // ID 高 32 位 W 的 rune, 低 32 位 保留8 S8 Y8 T8
W rune
S 声母枚举
Y 韵母枚举
T 声调枚举
R rune
P string
F string
}
func 颜表ID(w rune, s 声母枚举, y 韵母枚举, t 声调枚举) int64 {
return int64((uint64(w) << 32) | (uint64(s) << 16) | (uint64(y) << 8) | (uint64(t)))
}
// 从表 从部首表
type 部首表 struct {
R rune // R 该部首
E string `db:"E,UNIQUE"` // E 该部首对应的颜文字
}

210
define.go Normal file
View File

@@ -0,0 +1,210 @@
package emozi
import (
"strings"
base14 "github.com/fumiama/go-base16384"
)
type 声母枚举 uint8
// String 国际音标
func (sm 声母枚举) String() string {
a := sm * 3
b := a + 3
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))
}
const (
声母b 声母枚举 = iota
声母p
声母m
声母f
声母d
声母t
声母n
声母l
声母g
声母k
声母h
声母j
声母q
声母x
声母zh
声母ch
声母sh
声母r
声母z
声母c
声母s
声母yi
声母w
声母yu
声母0
)
// 声母 可以有多个颜文字与之对应 但有时系统可能无法显示
var 声母 = [...][]string{
// b: 鼻笔八
{"👃", "🖊️", "🎱"},
// p: 跑跑跑票票牌牌
{"🏃", "🏃‍♀️", "🏃‍♂️", "🎫", "🎟️", "🃏", "🎴"},
// m: 马马猫猫猫猫锚面面门麦米木蜜帽帽帽麦
{"🐴", "🐎", "🐱", "🐈", "🐈‍⬛", "😺", "⚓️", "🍜", "🍝", "🚪", "🌾", "🍚", "🪵", "🍯", "🎩", "👒", "🧢", "🎙️"},
// f: 斧肺
{"🪓", "🫁"},
// d: 刀豆蛋灯灯洞
{"🔪", "🫘", "🥚", "💡", "🪔", "🕳️"},
// t: 腿糖兔兔桶桃
{"🦵", "🍬", "🐰", "🐇", "🪣", "🍑"},
// n: 奶鸟牛
{"🥛", "🐦", "🐮"},
// l: 鹿鹿冷龙龙莲
{"🦌", "🫎", "🥶", "🐲", "🐉", "🪷"},
// g: 瓜鼓龟狗勾跪跪跪
{"🍉", "🥁", "🐢", "🐶", "🪝", "🧎", "🧎‍♀️", "🧎‍♂️"},
// k: 哭裤
{"😭", "👖"},
// h: 花花花花花花花花火虎虎猴猴
{"🌹", "🌼", "💐", "🌷", "🌸", "🌺", "🥀", "🪻", "🔥", "🐯", "🐅", "🐒", "🐵"},
// j: 加减酒酒鸡鸡剑姜镜脚
{"", "", "🍷", "🍺", "🐔", "🐓", "🗡️", "🫚", "🪞", "🦶"},
// q: 茄钱钱钱钱钱棋
{"🍆", "💰", "💴", "💵", "💶", "💸", "♟️"},
// x: 下下虾虾虾雪星星星星熊象蟹
{"👇", "⬇️", "🦐", "🍤", "🦞", "❄️", "🌟", "✨", "⭐️", "💫", "🐻", "🐘", "🦀️"},
// zh: 猪猪针针
{"🐽", "🐷", "🪡", "💉"},
// ch: 茶茶车车车车床床除虫
{"🍵", "☕️", "🚗", "🚘", "🚙", "🛻", "🛏️", "🛌", "➗", "🐛"},
// sh: 水水书书书书书书上上上石山山山树树树蛇鼠鼠鼠珊
{"💦", "💧", "📖", "📕", "📗", "📘", "📙", "📚", "⬆️", "☝️", "👆", "🪨", "⛰️", "🏔️", "🗻", "🌲", "🌳", "🌴", "🐍", "🐭", "🐀", "🐁", "🪸"},
// r: 肉日日日
{"🥩", "🌞", "☀️", "🌅"},
// z: 左左左 c: 错错错错 s: 锁伞伞伞伞蒜
{"👈", "⬅️", "⬅"}, {"❌", "🙅", "🙅‍♀️", "🙅‍♂️"}, {"🔒", "🌂", "☔️", "⛱️", "☂️", "🧄"},
// yi: 羊羊牙药盐 w: 袜雾网碗 yu: 雨鱼鱼 0: 两种椅子
{"🐑", "🐐", "🦷", "💊", "🧂"}, {"🧦", "🌫️", "🕸️", "🥣"}, {"🐟", "🌧️", "🐠"}, {"🪑", "💺"},
}
type 韵母枚举 uint8
// String 国际音标
func (ym 韵母枚举) String() string {
return [...]string{
"ä", "ɔ", "ɤ", "i", "ɿ", "u", "y", "ɐɚ̯", "aɪ̯", "u̯aɪ̯", "eɪ̯", "u̯eɪ̯", "ɑʊ̯", "i̯ɑʊ̯",
"oʊ̯", "i̯oʊ̯", "i̯ä", "u̯ä", "u̯o", "i̯ɛ", "y̯ɛ", "an", "i̯ɛn", "u̯an", "y̯ɛn", "ən", "u̯ən",
"in", "yn", "ɑŋ", "i̯ɑŋ", "u̯ɑŋ", "ɤŋ", "iŋ", "u̯əŋ", "ʊŋ", "i̯ʊŋ",
}[ym]
}
const (
韵母a 韵母枚举 = iota
韵母o
韵母e
韵母yi
韵母ri
韵母wu
韵母yu
韵母er
韵母ai
韵母uai
韵母ei
韵母ui
韵母ao
韵母iao
韵母ou
韵母iu
韵母ia
韵母ua
韵母uo
韵母ie
韵母yue
韵母an
韵母ian
韵母uan
韵母yuan
韵母en
韵母un
韵母in
韵母yun
韵母ang
韵母iang
韵母uang
韵母eng
韵母ing
韵母ueng
韵母ong
韵母iong
)
// 韵母 仅由一种意象表示
var 韵母 = [...][]string{
{"😧", "🤔️"}, {"😲"}, {"😋"}, {"👔", "👗", "👕", "👚"}, {"🌞", "☀️", "🌅"},
{string([]rune("🌫️")[0]), "🌫️"}, {"🐟", "🌧️"}, {"👂"},
{string([]rune("❤️")[0]), "❤️", "💗", "💓", "🫶", "💕", "💖", "💘", "💙", "💚", "💛", "💜", "💝", "💞", "🧡"}, {"🥢"}, {"😩"}, {"🐢"},
{"🐱", "🐈", "🐈‍⬛", "😺"}, {"💊"}, {"🤮", "🤢"}, {"👉", "➡️"},
{"🦷"}, {"🧦"}, {"🌀"}, {string([]rune("✌️")[0]), "✌️"}, {"🌙", "🌕", "🌛", "🌝"}, {"🔐"}, {"🚬"}, {"🥣", "🍚"},
{string([]rune("⭕️")[0]), "⭕️"}, {"😐"}, {"😘", "💋"}, {"🎵"}, {string([]rune("☁️")[0]), "☁️"}, {"👍"}, {"🐑"},
{string([]rune("🕸️")[0]), "🕸️"}, {"💡", "🪔"}, {"🦅"}, {"🐝"}, {"🌈"}, {"😳"}, // jiong 囧
}
type 声调枚举 uint8
// String 返回传统调名
func (t 声调枚举) String() string {
return [...]string{
"阴平", "阳平", "上声", "去声", "轻声",
}[t]
}
const (
阴平 声调枚举 = iota
阳平
上声
去声
轻声
)
var 声调 = [...][]string{{"🍉"}, {"🧗", "🧗‍♀️", "🧗‍♂️", "🦎"}, {"🐴", "🐎"}, {"😨"}, {"😯"}}
// 校验表 用 校验表长度 个unicode控制字符做校验和验证此序列是由本程序生成而非手写的
//
// 具体做法是先对后面的文本做crc32然后取 校验表长度^校验字节数 的模
var 校验表 = func() []rune {
x, err := base14.UTF16BE2UTF8(base14.StringToBytes("\x20\x0b\x20\x0c\x20\x0d\x20\x0e\x20\x0f\x20\x2a\x20\x2b\x20\x2c\x20\x2d\x20\x2e\x20\x60\x20\x61\x20\x62\x20\x63\x20\x64\x20\x65\x20\x66\x20\x68\x20\x69\x20\x6a\x20\x6b\x20\x6c\x20\x6d\x20\x6e\x20\x6f"))
if err != nil {
panic(err)
}
return []rune(base14.BytesToString(x))
}()
// 校验表长度 25
var 校验表长度 = len(校验表)
// 校验字节数 默认为 3, 不得大于 3
const 校验字节数 = 3
// 校验模 校验表长度^校验字节数
var 校验模 = uint32(0)
// 校验倍数 校验表长度乘方表
var 校验倍数 = func() []uint32 {
tab := make([]uint32, 校验字节数)
tab[0] = 1
for i := 1; i < 校验字节数; i++ {
tab[i] = tab[i-1] * uint32(校验表长度)
}
校验模 = tab[校验字节数-1] * uint32(校验表长度)
return tab
}()
// 逆校验表 还原校验和用
var 逆校验表 = func() map[rune]uint8 {
m := make(map[rune]uint8, 64)
for i, c := range 校验表 {
m[c] = uint8(i)
}
return m
}()

22
define_test.go Normal file
View File

@@ -0,0 +1,22 @@
package emozi
import "testing"
func TestFirstEmojiSingle(t *testing.T) {
for i, lst := range 声母 {
if len([]rune(lst[0])) != 1 {
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])
}
}
for i, lst := range 声调 {
if len([]rune(lst[0])) != 1 {
t.Fatal("声调", i, "长度", len([]rune(lst[0])), "字", lst[0])
}
}
}

29
go.mod Normal file
View File

@@ -0,0 +1,29 @@
module github.com/fumiama/emozi
go 1.20
require (
github.com/FloatTech/sqlite v1.6.3
github.com/fumiama/go-base16384 v1.7.0
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5
)
require (
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b // indirect
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/text v0.3.7 // indirect
modernc.org/libc v1.21.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect
modernc.org/sqlite v1.20.0 // indirect
)
replace modernc.org/sqlite => github.com/fumiama/sqlite3 v1.20.0-with-win386
replace github.com/remyoudompheng/bigfft => github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b

43
go.sum Normal file
View File

@@ -0,0 +1,43 @@
github.com/FloatTech/sqlite v1.6.3 h1:MQkqBNlkPuCoKQQgoNLuTL/2Ci3tBTFAnVYBdD0Wy4M=
github.com/FloatTech/sqlite v1.6.3/go.mod h1:zFbHzRfB+CJ+VidfjuVbrcin3DAz283F7hF1hIeHzpY=
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b h1:tvciXWq2nuvTbFeJGLDNIdRX3BI546D3O7k7vrVueZw=
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:ir/IFJU5xbja5UaBEQLjcvn7aAU01nqU/NUyOBEU+ew=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0yifz6XDPZu48aSld8BWwBfr2JKB2bGWiEd4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b h1:Zt3pFQditAdWTHCOVkiloc9ZauBoWrb37guFV4iIRvE=
github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/sqlite3 v1.20.0-with-win386 h1:ZR1AXGBEtkfq9GAXehOVcwn+aaCG8itrkgEsz4ggx5k=
github.com/fumiama/sqlite3 v1.20.0-with-win386/go.mod h1:Os58MHwYCcYZCy2PGChBrQtBAw5/LS1ZZOkfc+C/I7s=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:qSmEGTgjkESUX5kPMSGJ4pcBUtYVDdkNzMrjQyvRvp0=
github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:x7SghIWwLVcJObXbjK7S2ENsT1cAcdJcPl7dRaSFog0=
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d h1:hTRDIpJ1FjS9ULJuEzu69n3qTgc18eI+ztw/pJv47hs=
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d/go.mod h1:7xD3p0XnHvJFQ3t/stEJd877CSIMkH/fACVWen5pYnc=
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5 h1:wnbHIeP1UX8ClYEWKGnw66PfYvReCHu9G5lXSte3Sqc=
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5/go.mod h1:7KaV9YIR92M1FpbczAcfYQ3UZ5ayT27pNtunDmXvLBo=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI=
modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=

53
lookup.go Normal file
View File

@@ -0,0 +1,53 @@
package emozi
import (
"math/rand"
"strconv"
)
var = "🈳️"
func (c *Coder) 查声母(s 声母枚举) string {
lst := 声母[s]
if len(lst) == 0 {
return
}
if len(lst) == 1 || !c.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) 查声调(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) 查部首(r rune) string {
x := &部首表{}
err := c.db.Find(部首表名, x, "WHERE R="+strconv.Itoa(int(r)))
if err == nil && len(x.E) > 0 && x.E != {
return x.E
}
if e, ok := 部首后备[r]; ok {
return e
}
return
}

336
pinyin.go Normal file
View File

@@ -0,0 +1,336 @@
package emozi
import (
"errors"
"strings"
)
var smap = map[string]声母枚举{
"b": 声母b,
"p": 声母p,
"m": 声母m,
"f": 声母f,
"d": 声母d,
"t": 声母t,
"n": 声母n,
"l": 声母l,
"g": 声母g,
"k": 声母k,
"h": 声母h,
"j": 声母j,
"q": 声母q,
"x": 声母x,
"r": 声母r,
"w": 声母w,
}
const 双字声母 = "zcs"
var amap = map[string]韵母枚举{
"a": 韵母a,
"ai": 韵母ai,
"ao": 韵母ao,
"an": 韵母an,
"ang": 韵母ang,
}
var wamap = map[string]韵母枚举{
"a": 韵母ua,
"ai": 韵母uai,
"an": 韵母uan,
"ang": 韵母uang,
}
var omap = map[string]韵母枚举{
"o": 韵母o,
"ou": 韵母ou,
"ong": 韵母ong,
}
var emap = map[string]韵母枚举{
"e": 韵母e,
"er": 韵母er,
"ei": 韵母ei,
"en": 韵母en,
"eng": 韵母eng,
}
var wemap = map[string]韵母枚举{
"ei": 韵母ei,
"en": 韵母en,
"eng": 韵母ueng,
}
var imap = map[string]韵母枚举{
"i": 韵母yi,
"iao": 韵母iao,
"iu": 韵母iu,
"ia": 韵母ia,
"ie": 韵母ie,
"ian": 韵母ian,
"in": 韵母in,
"iang": 韵母iang,
"ing": 韵母ing,
"iong": 韵母iong,
}
var umap = map[string]韵母枚举{
"u": 韵母wu,
"uai": 韵母uai,
"ui": 韵母ui,
"ua": 韵母ua,
"uo": 韵母uo,
"uan": 韵母uan,
"un": 韵母un,
"uang": 韵母uang,
"ueng": 韵母ueng,
}
var yumap = map[string]韵母枚举{
"u": 韵母yu,
"ue": 韵母yue,
"uan": 韵母yuan,
"un": 韵母yun,
"ü": 韵母yu,
"üe": 韵母yue,
"üan": 韵母yuan,
"ün": 韵母yun,
"v": 韵母yu,
"ve": 韵母yue,
"van": 韵母yuan,
"vn": 韵母yun,
}
func combine(maps ...map[string]韵母枚举) map[string]韵母枚举 {
newmap := make(map[string]韵母枚举, 128)
for _, m := range maps {
for k, v := range m {
newmap[k] = v
}
}
return newmap
}
var aoeiu = combine(amap, omap, emap, imap, umap)
var aoeu = combine(amap, omap, emap, umap)
const (
阴平字母 = "āōēīūǖ"
阳平字母 = "áóéíúǘń"
上声字母 = "ǎǒěǐǔǚň"
去声字母 = "àòèìùǜ"
G = 'ɡ'
A = 'ɑ'
)
var notonemap = map[rune]string{
'ā': "a", 'á': "a", 'ǎ': "a", 'à': "a",
'ō': "o", 'ó': "o", 'ǒ': "o", 'ò': "o",
'ē': "e", 'é': "e", 'ě': "e", 'è': "e",
'ī': "i", 'í': "i", 'ǐ': "i", 'ì': "i",
'ū': "u", 'ú': "u", 'ǔ': "u", 'ù': "u",
'ǖ': "ü", 'ǘ': "ü", 'ǚ': "ü", 'ǜ': "ü",
G: "g", A: "a", 'ń': "n", 'ň': "n",
}
func 去调(pf string) string {
sb := strings.Builder{}
for _, c := range pf {
if nc, ok := notonemap[c]; ok {
sb.WriteString(nc)
} else {
sb.WriteRune(c)
}
}
return sb.String()
}
// 识调 从拼音获得声调
func 识调(pf string) 声调枚举 {
switch {
case strings.ContainsAny(pf, 阴平字母):
return 阴平
case strings.ContainsAny(pf, 阳平字母):
return 阳平
case strings.ContainsAny(pf, 上声字母):
return 上声
case strings.ContainsAny(pf, 去声字母):
return 去声
}
return 轻声
}
// 拆音 拆分拼音为声母韵母
func 拆音(p string) (sm 声母枚举, ym 韵母枚举, err error) {
if len(p) == 1 {
sm = 声母0
// 韵母ê, 因为文字稀少, 并入 ie
if p == "ê" {
ym = 韵母ie
return
}
if y, ok := aoeiu[p]; ok {
ym = y
return
}
err = errors.New("无法识别零声母拼音" + p)
return
}
if s, ok := smap[p[:1]]; ok {
sm = s
ok = false
switch p[1:2] {
case "a":
if p[:1] == "w" {
ym, ok = wamap[p[1:]]
} else {
ym, ok = amap[p[1:]]
}
case "o":
if p[:1] == "w" {
ym = 韵母uo
ok = true
} else {
ym, ok = omap[p[1:]]
}
case "e":
if p[:1] == "w" {
ym, ok = wemap[p[1:]]
} else {
ym, ok = emap[p[1:]]
}
case "i":
if p[:1] == "r" {
ym = 韵母ri
ok = true
} else {
ym, ok = imap[p[1:]]
}
case "u":
if strings.Contains("jqx", p[:1]) {
ym, ok = yumap[p[1:]]
} else {
ym, ok = umap[p[1:]]
if !ok {
ym, ok = yumap[p[1:]]
}
}
case "v", "ü"[:1]:
ym, ok = yumap[p[1:]]
}
if !ok {
err = errors.New("无法识别拼音" + p + "的韵母部分" + p[1:])
}
return
}
ok := false
if strings.Contains(双字声母, p[:1]) {
if p[1:2] == "h" { // zh ch sh
switch p[:1] {
case "z":
sm = 声母zh
case "c":
sm = 声母ch
case "s":
sm = 声母sh
}
ym, ok = aoeu[p[2:]]
if !ok {
if p[2:] == "i" {
ym = 韵母ri
} else {
err = errors.New("无法识别拼音" + p)
}
}
return
}
switch p[:1] {
case "z":
sm = 声母z
case "c":
sm = 声母c
case "s":
sm = 声母s
}
ym, ok = aoeu[p[1:]]
if !ok {
if p[1:] == "i" {
ym = 韵母ri
} else {
err = errors.New("无法识别拼音" + p)
}
}
return
}
if p[:1] == "y" { // /j/ or /y/
if strings.Contains("uvü"[:3], p[1:2]) { // /y/
sm = 声母yu
ym, ok = yumap[p[1:]]
if !ok {
err = errors.New("无法识别拼音" + p)
}
return
}
if p[1:] == "ong" { // yong
sm = 声母yu
ym = 韵母iong
ok = true
return
}
sm = 声母yi
ym, ok = aoeiu[p[1:]]
if !ok {
err = errors.New("无法识别拼音" + p)
}
return
}
sm = 声母0
ym, ok = aoeiu[p]
if !ok {
err = errors.New("无法识别拼音" + p)
}
return
}
func 拆音识字(w, r, p, f string) (s 声母枚举, y 韵母枚举, t 声调枚举, rw, rr rune, err error) {
myp := 去调(f)
s, y, err = 拆音(p)
if err != nil {
return
}
mys, myy, err := 拆音(myp)
if err != nil {
return
}
if mys != s || myy != y {
err = errors.New("无声调拼音" + p + "与有声调拼音" + f + "不符")
return
}
t = 识调(f)
rws := []rune(w)
if len(rws) != 1 {
err = errors.New("无法正确识别文字" + w)
return
}
rw = rws[0]
rrs := []rune(r)
if len(rrs) != 1 {
err = errors.New("无法正确识别部首" + w)
return
}
rr = rrs[0]
return
}
// SplitPinyin 拆分带声调拼音
func SplitPinyin(pf string) (sm, ym, sd string, err error) {
p := 去调(pf)
s, y, err := 拆音(p)
if err != nil {
return
}
t := 识调(pf)
println(s, y, t)
return s.String(), y.String(), t.String(), nil
}

46
pinyin_test.go Normal file
View File

@@ -0,0 +1,46 @@
package emozi
import "testing"
func TestSplitPinyin(t *testing.T) {
s, y, tone, err := SplitPinyin("yōng")
if err != nil {
t.Fatal(err)
}
t.Log(s, y, tone)
if s+y+tone != "ɥi̯ʊŋ阴平" {
t.Fail()
}
s, y, tone, err = SplitPinyin("hóng")
if err != nil {
t.Fatal(err)
}
t.Log(s, y, tone)
if s+y+tone != "xʊŋ阳平" {
t.Fail()
}
s, y, tone, err = SplitPinyin("yǜn")
if err != nil {
t.Fatal(err)
}
t.Log(s, y, tone)
if s+y+tone != "ɥyn去声" {
t.Fail()
}
s, y, tone, err = SplitPinyin("jiǒng")
if err != nil {
t.Fatal(err)
}
t.Log(s, y, tone)
if s+y+tone != "tɕi̯ʊŋ上声" {
t.Fail()
}
s, y, tone, err = SplitPinyin("e")
if err != nil {
t.Fatal(err)
}
t.Log(s, y, tone)
if s+y+tone != "0ɤ轻声" {
t.Fail()
}
}

81
preprocess/main.go Normal file
View File

@@ -0,0 +1,81 @@
package main
// 数据来自 https://github.com/shuowenjiezi/shuowen
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/liuzl/gocc"
"github.com/fumiama/emozi"
)
type data struct {
W string `json:"wordhead"`
R string `json:"radical"`
P string `json:"pinyin"`
F string `json:"pinyin_full"`
A []string `json:"pinyin_alternative"`
}
func main() {
x := data{}
t2s, err := gocc.New("t2s")
if err != nil {
panic(fmt.Sprintf("ERROR: creating gocc: %v", err))
}
_ = os.RemoveAll(emozi.EmoziDatabasePath)
c, err := emozi.NewCoder(false, time.Minute)
if err != nil {
panic(fmt.Sprintf("ERROR: creating emozi coder: %v", err))
}
for i := 1; i <= 9833; i++ {
f, err := os.Open(fmt.Sprintf("./data/%d.json", i))
if err != nil {
panic(fmt.Sprintf("ERROR: opening data/%d.json: %v", i, err))
}
x.A = x.A[:0]
err = json.NewDecoder(f).Decode(&x)
if err != nil {
panic(fmt.Sprintf("ERROR: decoding data/%d.json: %v", i, err))
}
_ = f.Close()
x.P = strings.ReplaceAll(x.P, string(emozi.G), "g")
if len(x.P) == 0 {
panic(fmt.Sprintf("ERROR: decoding data/%d.json: p: %s, f: %s", i, x.P, x.F))
}
insert := func(w string) error {
err = c.Add(w, x.R, x.P, x.F)
if err != nil {
return fmt.Errorf("inserting table emozi of data/%d.json: %v", i, err)
}
for _, a := range x.A {
err = c.Add(w, x.R, "", a)
if err != nil {
return fmt.Errorf("inserting table emozi of data/%d.json, alter %s: %v", i, a, err)
}
}
return nil
}
fmt.Print("\r[", i, "/9833] Insert char: ", x.W, " ")
err = insert(x.W)
if err != nil {
fmt.Println("\n\t\tWARN:", err)
}
sc, err := t2s.Convert(x.W)
if err != nil {
continue
}
if sc != x.W {
fmt.Print("\r[", i, "/9833] insert simplified char: ", sc, " ")
err = insert(sc)
if err != nil {
fmt.Println("\n\t\tWARN:", err)
}
}
}
}

547
radical.go Normal file
View File

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

63
string.go Normal file
View File

@@ -0,0 +1,63 @@
package emozi
import (
"hash/crc32"
"strings"
base14 "github.com/fumiama/go-base16384"
)
// EmoziString 一个颜文字汉字转写串, 包含串头 校验字节数*2 字节校验和
type EmoziString string
// WrapRawEmoziString 为不包含串头的转写串加一个头使其成为合法 EmoziString
func WrapRawEmoziString(s string) EmoziString {
rs := []rune(s)
if len(rs) < 4 {
return ""
}
h := crc32.NewIEEE()
h.Write(base14.StringToBytes(s))
sum := h.Sum32() % 校验模
sb := strings.Builder{}
buf := [校验字节数]uint8{}
for i := 校验字节数 - 1; i >= 0; i-- {
buf[i] = uint8((sum / 校验倍数[i]) % uint32(校验表长度))
}
for i, n := range buf {
sb.WriteRune(rs[i])
sb.WriteRune(校验表[n])
}
sb.WriteString(string(rs[len(buf):]))
return EmoziString(sb.String())
}
// String 输出不包含串头的转写串
func (es EmoziString) String() string {
if !es.IsValid() {
return "ERROR: invalid EmoziString"
}
rs := []rune(es)
sb := strings.Builder{}
for i := 0; i < 校验字节数; i++ {
sb.WriteRune(rs[i*2])
}
sb.WriteString(string(rs[校验字节数*2:]))
return sb.String()
}
// IsValid 判断是否是合法 EmoziString
func (es EmoziString) IsValid() bool {
rs := []rune(es)
if len(rs) < 校验字节数+4 {
return false
}
h := crc32.NewIEEE()
sum := uint32(0)
for i := 0; i < 校验字节数; i++ {
h.Write(base14.StringToBytes(string(rs[i*2])))
sum += 校验倍数[i] * uint32(逆校验表[rs[i*2+1]])
}
h.Write(base14.StringToBytes(string(rs[校验字节数*2:])))
return h.Sum32()%校验模 == sum
}

20
string_test.go Normal file
View File

@@ -0,0 +1,20 @@
package emozi
import "testing"
func TestWrapUnwrap(t *testing.T) {
t.Log(校验表长度)
if !WrapRawEmoziString("😨😨😨😨").IsValid() {
t.Fail()
}
if EmoziString("😨😨😨😨😨😨😨").IsValid() {
t.Fail()
}
}
func TestString(t *testing.T) {
t.Log(校验表长度)
if WrapRawEmoziString("😨😨😨😨").String() != "😨😨😨😨" {
t.Fail()
}
}

BIN
字.db Normal file

Binary file not shown.