123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- package pinyin
- import (
- "regexp"
- "strings"
- )
- // Meta
- const (
- Version = "0.18.0"
- Author = "mozillazg, 闲耘"
- License = "MIT"
- Copyright = "Copyright (c) 2016 mozillazg, 闲耘"
- )
- // 拼音风格(推荐)
- const (
- Normal = 0 // 普通风格,不带声调(默认风格)。如: zhong guo
- Tone = 1 // 声调风格1,拼音声调在韵母第一个字母上。如: zhōng guó
- Tone2 = 2 // 声调风格2,即拼音声调在各个韵母之后,用数字 [1-4] 进行表示。如: zho1ng guo2
- Tone3 = 8 // 声调风格3,即拼音声调在各个拼音之后,用数字 [1-4] 进行表示。如: zhong1 guo2
- Initials = 3 // 声母风格,只返回各个拼音的声母部分。如: zh g 。注意:不是所有的拼音都有声母
- FirstLetter = 4 // 首字母风格,只返回拼音的首字母部分。如: z g
- Finals = 5 // 韵母风格,只返回各个拼音的韵母部分,不带声调。如: ong uo
- FinalsTone = 6 // 韵母风格1,带声调,声调在韵母第一个字母上。如: ōng uó
- FinalsTone2 = 7 // 韵母风格2,带声调,声调在各个韵母之后,用数字 [1-4] 进行表示。如: o1ng uo2
- FinalsTone3 = 9 // 韵母风格3,带声调,声调在各个拼音之后,用数字 [1-4] 进行表示。如: ong1 uo2
- )
- // 拼音风格(兼容之前的版本)
- const (
- NORMAL = Normal
- TONE = Tone
- TONE2 = Tone2
- INITIALS = Initials
- FIRST_LETTER = FirstLetter
- FINALS = Finals
- FINALS_TONE = FinalsTone
- FINALS_TONE2 = FinalsTone2
- )
- // 声母表
- var initialArray = strings.Split(
- "b,p,m,f,d,t,n,l,g,k,h,j,q,x,r,zh,ch,sh,z,c,s",
- ",",
- )
- // 所有带声调的字符
- var rePhoneticSymbolSource = func(m map[string]string) string {
- s := ""
- for k := range m {
- s = s + k
- }
- return s
- }(phoneticSymbol)
- // 匹配带声调字符的正则表达式
- var rePhoneticSymbol = regexp.MustCompile("[" + rePhoneticSymbolSource + "]")
- // 匹配使用数字标识声调的字符的正则表达式
- var reTone2 = regexp.MustCompile("([aeoiuvnm])([1-4])$")
- // 匹配 Tone2 中标识韵母声调的正则表达式
- var reTone3 = regexp.MustCompile("^([a-z]+)([1-4])([a-z]*)$")
- // Args 配置信息
- type Args struct {
- Style int // 拼音风格(默认: Normal)
- Heteronym bool // 是否启用多音字模式(默认:禁用)
- Separator string // Slug 中使用的分隔符(默认:-)
- // 处理没有拼音的字符(默认忽略没有拼音的字符)
- // 函数返回的 slice 的长度为0 则表示忽略这个字符
- Fallback func(r rune, a Args) []string
- }
- // Style 默认配置:风格
- var Style = Normal
- // Heteronym 默认配置:是否启用多音字模式
- var Heteronym = false
- // Separator 默认配置: `Slug` 中 Join 所用的分隔符
- var Separator = "-"
- // Fallback 默认配置: 如何处理没有拼音的字符(忽略这个字符)
- var Fallback = func(r rune, a Args) []string {
- return []string{}
- }
- var finalExceptionsMap = map[string]string{
- "ū": "ǖ",
- "ú": "ǘ",
- "ǔ": "ǚ",
- "ù": "ǜ",
- }
- var reFinalExceptions = regexp.MustCompile("^(j|q|x)(ū|ú|ǔ|ù)$")
- var reFinal2Exceptions = regexp.MustCompile("^(j|q|x)u(\\d?)$")
- // NewArgs 返回包含默认配置的 `Args`
- func NewArgs() Args {
- return Args{Style, Heteronym, Separator, Fallback}
- }
- // 获取单个拼音中的声母
- func initial(p string) string {
- s := ""
- for _, v := range initialArray {
- if strings.HasPrefix(p, v) {
- s = v
- break
- }
- }
- return s
- }
- // 获取单个拼音中的韵母
- func final(p string) string {
- n := initial(p)
- if n == "" {
- return handleYW(p)
- }
- // 特例 j/q/x
- matches := reFinalExceptions.FindStringSubmatch(p)
- // jū -> jǖ
- if len(matches) == 3 && matches[1] != "" && matches[2] != "" {
- v, _ := finalExceptionsMap[matches[2]]
- return v
- }
- // ju -> jv, ju1 -> jv1
- p = reFinal2Exceptions.ReplaceAllString(p, "${1}v$2")
- return strings.Join(strings.SplitN(p, n, 2), "")
- }
- // 处理 y, w
- func handleYW(p string) string {
- // 特例 y/w
- if strings.HasPrefix(p, "yu") {
- p = "v" + p[2:] // yu -> v
- } else if strings.HasPrefix(p, "yi") {
- p = p[1:] // yi -> i
- } else if strings.HasPrefix(p, "y") {
- p = "i" + p[1:] // y -> i
- } else if strings.HasPrefix(p, "wu") {
- p = p[1:] // wu -> u
- } else if strings.HasPrefix(p, "w") {
- p = "u" + p[1:] // w -> u
- }
- return p
- }
- func toFixed(p string, a Args) string {
- if a.Style == Initials {
- return initial(p)
- }
- origP := p
- // 替换拼音中的带声调字符
- py := rePhoneticSymbol.ReplaceAllStringFunc(p, func(m string) string {
- symbol, _ := phoneticSymbol[m]
- switch a.Style {
- // 不包含声调
- case Normal, FirstLetter, Finals:
- // 去掉声调: a1 -> a
- m = reTone2.ReplaceAllString(symbol, "$1")
- case Tone2, FinalsTone2, Tone3, FinalsTone3:
- // 返回使用数字标识声调的字符
- m = symbol
- default:
- // 声调在头上
- }
- return m
- })
- switch a.Style {
- // 将声调移动到最后
- case Tone3, FinalsTone3:
- py = reTone3.ReplaceAllString(py, "$1$3$2")
- }
- switch a.Style {
- // 首字母
- case FirstLetter:
- py = string([]rune(py)[0])
- // 韵母
- case Finals, FinalsTone, FinalsTone2, FinalsTone3:
- // 转换为 []rune unicode 编码用于获取第一个拼音字符
- // 因为 string 是 utf-8 编码不方便获取第一个拼音字符
- rs := []rune(origP)
- switch string(rs[0]) {
- // 因为鼻音没有声母所以不需要去掉声母部分
- case "ḿ", "ń", "ň", "ǹ":
- default:
- py = final(py)
- }
- }
- return py
- }
- func applyStyle(p []string, a Args) []string {
- newP := []string{}
- for _, v := range p {
- newP = append(newP, toFixed(v, a))
- }
- return newP
- }
- // SinglePinyin 把单个 `rune` 类型的汉字转换为拼音.
- func SinglePinyin(r rune, a Args) []string {
- if a.Fallback == nil {
- a.Fallback = Fallback
- }
- value, ok := PinyinDict[int(r)]
- pys := []string{}
- if ok {
- pys = strings.Split(value, ",")
- } else {
- pys = a.Fallback(r, a)
- }
- if len(pys) > 0 {
- if !a.Heteronym {
- pys = []string{pys[0]}
- }
- return applyStyle(pys, a)
- }
- return pys
- }
- // Pinyin 汉字转拼音,支持多音字模式.
- func Pinyin(s string, a Args) [][]string {
- pys := [][]string{}
- for _, r := range s {
- py := SinglePinyin(r, a)
- if len(py) > 0 {
- pys = append(pys, py)
- }
- }
- return pys
- }
- // LazyPinyin 汉字转拼音,与 `Pinyin` 的区别是:
- // 返回值类型不同,并且不支持多音字模式,每个汉字只取第一个音.
- func LazyPinyin(s string, a Args) []string {
- a.Heteronym = false
- pys := []string{}
- for _, v := range Pinyin(s, a) {
- pys = append(pys, v[0])
- }
- return pys
- }
- // Slug join `LazyPinyin` 的返回值.
- // 建议改用 https://github.com/mozillazg/go-slugify
- func Slug(s string, a Args) string {
- separator := a.Separator
- return strings.Join(LazyPinyin(s, a), separator)
- }
- // Convert 跟 Pinyin 的唯一区别就是 a 参数可以是 nil
- func Convert(s string, a *Args) [][]string {
- if a == nil {
- args := NewArgs()
- a = &args
- }
- return Pinyin(s, *a)
- }
- // LazyConvert 跟 LazyPinyin 的唯一区别就是 a 参数可以是 nil
- func LazyConvert(s string, a *Args) []string {
- if a == nil {
- args := NewArgs()
- a = &args
- }
- return LazyPinyin(s, *a)
- }
|