pinyin.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. package pinyin
  2. import (
  3. "regexp"
  4. "strings"
  5. )
  6. // Meta
  7. const (
  8. Version = "0.18.0"
  9. Author = "mozillazg, 闲耘"
  10. License = "MIT"
  11. Copyright = "Copyright (c) 2016 mozillazg, 闲耘"
  12. )
  13. // 拼音风格(推荐)
  14. const (
  15. Normal = 0 // 普通风格,不带声调(默认风格)。如: zhong guo
  16. Tone = 1 // 声调风格1,拼音声调在韵母第一个字母上。如: zhōng guó
  17. Tone2 = 2 // 声调风格2,即拼音声调在各个韵母之后,用数字 [1-4] 进行表示。如: zho1ng guo2
  18. Tone3 = 8 // 声调风格3,即拼音声调在各个拼音之后,用数字 [1-4] 进行表示。如: zhong1 guo2
  19. Initials = 3 // 声母风格,只返回各个拼音的声母部分。如: zh g 。注意:不是所有的拼音都有声母
  20. FirstLetter = 4 // 首字母风格,只返回拼音的首字母部分。如: z g
  21. Finals = 5 // 韵母风格,只返回各个拼音的韵母部分,不带声调。如: ong uo
  22. FinalsTone = 6 // 韵母风格1,带声调,声调在韵母第一个字母上。如: ōng uó
  23. FinalsTone2 = 7 // 韵母风格2,带声调,声调在各个韵母之后,用数字 [1-4] 进行表示。如: o1ng uo2
  24. FinalsTone3 = 9 // 韵母风格3,带声调,声调在各个拼音之后,用数字 [1-4] 进行表示。如: ong1 uo2
  25. )
  26. // 拼音风格(兼容之前的版本)
  27. const (
  28. NORMAL = Normal
  29. TONE = Tone
  30. TONE2 = Tone2
  31. INITIALS = Initials
  32. FIRST_LETTER = FirstLetter
  33. FINALS = Finals
  34. FINALS_TONE = FinalsTone
  35. FINALS_TONE2 = FinalsTone2
  36. )
  37. // 声母表
  38. var initialArray = strings.Split(
  39. "b,p,m,f,d,t,n,l,g,k,h,j,q,x,r,zh,ch,sh,z,c,s",
  40. ",",
  41. )
  42. // 所有带声调的字符
  43. var rePhoneticSymbolSource = func(m map[string]string) string {
  44. s := ""
  45. for k := range m {
  46. s = s + k
  47. }
  48. return s
  49. }(phoneticSymbol)
  50. // 匹配带声调字符的正则表达式
  51. var rePhoneticSymbol = regexp.MustCompile("[" + rePhoneticSymbolSource + "]")
  52. // 匹配使用数字标识声调的字符的正则表达式
  53. var reTone2 = regexp.MustCompile("([aeoiuvnm])([1-4])$")
  54. // 匹配 Tone2 中标识韵母声调的正则表达式
  55. var reTone3 = regexp.MustCompile("^([a-z]+)([1-4])([a-z]*)$")
  56. // Args 配置信息
  57. type Args struct {
  58. Style int // 拼音风格(默认: Normal)
  59. Heteronym bool // 是否启用多音字模式(默认:禁用)
  60. Separator string // Slug 中使用的分隔符(默认:-)
  61. // 处理没有拼音的字符(默认忽略没有拼音的字符)
  62. // 函数返回的 slice 的长度为0 则表示忽略这个字符
  63. Fallback func(r rune, a Args) []string
  64. }
  65. // Style 默认配置:风格
  66. var Style = Normal
  67. // Heteronym 默认配置:是否启用多音字模式
  68. var Heteronym = false
  69. // Separator 默认配置: `Slug` 中 Join 所用的分隔符
  70. var Separator = "-"
  71. // Fallback 默认配置: 如何处理没有拼音的字符(忽略这个字符)
  72. var Fallback = func(r rune, a Args) []string {
  73. return []string{}
  74. }
  75. var finalExceptionsMap = map[string]string{
  76. "ū": "ǖ",
  77. "ú": "ǘ",
  78. "ǔ": "ǚ",
  79. "ù": "ǜ",
  80. }
  81. var reFinalExceptions = regexp.MustCompile("^(j|q|x)(ū|ú|ǔ|ù)$")
  82. var reFinal2Exceptions = regexp.MustCompile("^(j|q|x)u(\\d?)$")
  83. // NewArgs 返回包含默认配置的 `Args`
  84. func NewArgs() Args {
  85. return Args{Style, Heteronym, Separator, Fallback}
  86. }
  87. // 获取单个拼音中的声母
  88. func initial(p string) string {
  89. s := ""
  90. for _, v := range initialArray {
  91. if strings.HasPrefix(p, v) {
  92. s = v
  93. break
  94. }
  95. }
  96. return s
  97. }
  98. // 获取单个拼音中的韵母
  99. func final(p string) string {
  100. n := initial(p)
  101. if n == "" {
  102. return handleYW(p)
  103. }
  104. // 特例 j/q/x
  105. matches := reFinalExceptions.FindStringSubmatch(p)
  106. // jū -> jǖ
  107. if len(matches) == 3 && matches[1] != "" && matches[2] != "" {
  108. v, _ := finalExceptionsMap[matches[2]]
  109. return v
  110. }
  111. // ju -> jv, ju1 -> jv1
  112. p = reFinal2Exceptions.ReplaceAllString(p, "${1}v$2")
  113. return strings.Join(strings.SplitN(p, n, 2), "")
  114. }
  115. // 处理 y, w
  116. func handleYW(p string) string {
  117. // 特例 y/w
  118. if strings.HasPrefix(p, "yu") {
  119. p = "v" + p[2:] // yu -> v
  120. } else if strings.HasPrefix(p, "yi") {
  121. p = p[1:] // yi -> i
  122. } else if strings.HasPrefix(p, "y") {
  123. p = "i" + p[1:] // y -> i
  124. } else if strings.HasPrefix(p, "wu") {
  125. p = p[1:] // wu -> u
  126. } else if strings.HasPrefix(p, "w") {
  127. p = "u" + p[1:] // w -> u
  128. }
  129. return p
  130. }
  131. func toFixed(p string, a Args) string {
  132. if a.Style == Initials {
  133. return initial(p)
  134. }
  135. origP := p
  136. // 替换拼音中的带声调字符
  137. py := rePhoneticSymbol.ReplaceAllStringFunc(p, func(m string) string {
  138. symbol, _ := phoneticSymbol[m]
  139. switch a.Style {
  140. // 不包含声调
  141. case Normal, FirstLetter, Finals:
  142. // 去掉声调: a1 -> a
  143. m = reTone2.ReplaceAllString(symbol, "$1")
  144. case Tone2, FinalsTone2, Tone3, FinalsTone3:
  145. // 返回使用数字标识声调的字符
  146. m = symbol
  147. default:
  148. // 声调在头上
  149. }
  150. return m
  151. })
  152. switch a.Style {
  153. // 将声调移动到最后
  154. case Tone3, FinalsTone3:
  155. py = reTone3.ReplaceAllString(py, "$1$3$2")
  156. }
  157. switch a.Style {
  158. // 首字母
  159. case FirstLetter:
  160. py = string([]rune(py)[0])
  161. // 韵母
  162. case Finals, FinalsTone, FinalsTone2, FinalsTone3:
  163. // 转换为 []rune unicode 编码用于获取第一个拼音字符
  164. // 因为 string 是 utf-8 编码不方便获取第一个拼音字符
  165. rs := []rune(origP)
  166. switch string(rs[0]) {
  167. // 因为鼻音没有声母所以不需要去掉声母部分
  168. case "ḿ", "ń", "ň", "ǹ":
  169. default:
  170. py = final(py)
  171. }
  172. }
  173. return py
  174. }
  175. func applyStyle(p []string, a Args) []string {
  176. newP := []string{}
  177. for _, v := range p {
  178. newP = append(newP, toFixed(v, a))
  179. }
  180. return newP
  181. }
  182. // SinglePinyin 把单个 `rune` 类型的汉字转换为拼音.
  183. func SinglePinyin(r rune, a Args) []string {
  184. if a.Fallback == nil {
  185. a.Fallback = Fallback
  186. }
  187. value, ok := PinyinDict[int(r)]
  188. pys := []string{}
  189. if ok {
  190. pys = strings.Split(value, ",")
  191. } else {
  192. pys = a.Fallback(r, a)
  193. }
  194. if len(pys) > 0 {
  195. if !a.Heteronym {
  196. pys = []string{pys[0]}
  197. }
  198. return applyStyle(pys, a)
  199. }
  200. return pys
  201. }
  202. // Pinyin 汉字转拼音,支持多音字模式.
  203. func Pinyin(s string, a Args) [][]string {
  204. pys := [][]string{}
  205. for _, r := range s {
  206. py := SinglePinyin(r, a)
  207. if len(py) > 0 {
  208. pys = append(pys, py)
  209. }
  210. }
  211. return pys
  212. }
  213. // LazyPinyin 汉字转拼音,与 `Pinyin` 的区别是:
  214. // 返回值类型不同,并且不支持多音字模式,每个汉字只取第一个音.
  215. func LazyPinyin(s string, a Args) []string {
  216. a.Heteronym = false
  217. pys := []string{}
  218. for _, v := range Pinyin(s, a) {
  219. pys = append(pys, v[0])
  220. }
  221. return pys
  222. }
  223. // Slug join `LazyPinyin` 的返回值.
  224. // 建议改用 https://github.com/mozillazg/go-slugify
  225. func Slug(s string, a Args) string {
  226. separator := a.Separator
  227. return strings.Join(LazyPinyin(s, a), separator)
  228. }
  229. // Convert 跟 Pinyin 的唯一区别就是 a 参数可以是 nil
  230. func Convert(s string, a *Args) [][]string {
  231. if a == nil {
  232. args := NewArgs()
  233. a = &args
  234. }
  235. return Pinyin(s, *a)
  236. }
  237. // LazyConvert 跟 LazyPinyin 的唯一区别就是 a 参数可以是 nil
  238. func LazyConvert(s string, a *Args) []string {
  239. if a == nil {
  240. args := NewArgs()
  241. a = &args
  242. }
  243. return LazyPinyin(s, *a)
  244. }