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