package unorm
import (
"sort"
"strings"
"unicode/utf8"
)
type Form uint8
const (
NFC Form = iota
NFD
NFKC
NFKD
)
func ParseForm (name string ) (Form , bool ) {
switch strings .ToUpper (name ) {
case "NFC" :
return NFC , true
case "NFD" :
return NFD , true
case "NFKC" :
return NFKC , true
case "NFKD" :
return NFKD , true
}
return 0 , false
}
const (
hangulSBase = 0xAC00
hangulLBase = 0x1100
hangulVBase = 0x1161
hangulTBase = 0x11A7
hangulLCount = 19
hangulVCount = 21
hangulTCount = 28
hangulNCount = hangulVCount * hangulTCount
hangulSCount = hangulLCount * hangulNCount
)
func combiningClass(r rune ) uint8 {
lo , hi := 0 , len (cccKey )
for lo < hi {
mid := (lo + hi ) / 2
if cccKey [mid ] < r {
lo = mid + 1
} else {
hi = mid
}
}
if lo < len (cccKey ) && cccKey [lo ] == r {
return cccVal [lo ]
}
return 0
}
func decompose(r rune , compat bool ) []rune {
key , idx , pool := canonKey [:], canonIdx [:], canonPool [:]
if compat {
key , idx , pool = compatKey [:], compatIdx [:], compatPool [:]
}
lo , hi := 0 , len (key )
for lo < hi {
mid := (lo + hi ) / 2
if key [mid ] < r {
lo = mid + 1
} else {
hi = mid
}
}
if lo < len (key ) && key [lo ] == r {
v := idx [lo ]
off , n := v >>8 , v &0xff
return pool [off : off +n ]
}
return nil
}
func composePair(a , b rune ) rune {
if a >= hangulLBase && a < hangulLBase +hangulLCount &&
b >= hangulVBase && b < hangulVBase +hangulVCount {
return hangulSBase + ((a -hangulLBase )*hangulVCount +(b -hangulVBase ))*hangulTCount
}
if a >= hangulSBase && a < hangulSBase +hangulSCount && (a -hangulSBase )%hangulTCount == 0 &&
b > hangulTBase && b < hangulTBase +hangulTCount {
return a + (b - hangulTBase )
}
lo , hi := 0 , len (compFirst )
for lo < hi {
mid := (lo + hi ) / 2
if compFirst [mid ] < a || (compFirst [mid ] == a && compSecond [mid ] < b ) {
lo = mid + 1
} else {
hi = mid
}
}
if lo < len (compFirst ) && compFirst [lo ] == a && compSecond [lo ] == b {
return compResult [lo ]
}
return 0
}
func quickYes(s string , form Form ) bool {
var lo , hi []rune
switch form {
case NFC :
lo , hi = qcNFCLo [:], qcNFCHi [:]
case NFD :
lo , hi = qcNFDLo [:], qcNFDHi [:]
case NFKC :
lo , hi = qcNFKCLo [:], qcNFKCHi [:]
default :
lo , hi = qcNFKDLo [:], qcNFKDHi [:]
}
for i := 0 ; i < len (s ); {
c := s [i ]
if c < utf8 .RuneSelf {
i ++
continue
}
r , n := utf8 .DecodeRuneInString (s [i :])
i += n
j := sort .Search (len (lo ), func (k int ) bool { return hi [k ] >= r })
if j < len (lo ) && lo [j ] <= r {
return false
}
}
return true
}
func appendDecomposed(dst []rune , r rune , compat bool ) []rune {
if r >= hangulSBase && r < hangulSBase +hangulSCount {
si := r - hangulSBase
dst = append (dst , hangulLBase +si /hangulNCount , hangulVBase +(si %hangulNCount )/hangulTCount )
if t := si % hangulTCount ; t != 0 {
dst = append (dst , hangulTBase +t )
}
return dst
}
if d := decompose (r , compat ); d != nil {
return append (dst , d ...)
}
return append (dst , r )
}
func canonicalOrder(rs []rune ) {
for i := 0 ; i < len (rs ); {
if combiningClass (rs [i ]) == 0 {
i ++
continue
}
j := i
for j < len (rs ) && combiningClass (rs [j ]) != 0 {
j ++
}
sort .SliceStable (rs [i :j ], func (a , b int ) bool {
return combiningClass (rs [i +a ]) < combiningClass (rs [i +b ])
})
i = j
}
}
func compose(rs []rune ) []rune {
if len (rs ) == 0 {
return rs
}
out := rs [:1 ]
last := -1
if combiningClass (rs [0 ]) == 0 {
last = 0
}
lastCC := combiningClass (rs [0 ])
for _ , r := range rs [1 :] {
cc := combiningClass (r )
if last >= 0 && (lastCC == 0 || lastCC < cc ) {
if c := composePair (out [last ], r ); c != 0 {
out [last ] = c
continue
}
}
out = append (out , r )
if cc == 0 {
last = len (out ) - 1
lastCC = 0
} else {
lastCC = cc
}
}
return out
}
func Normalize (s string , form Form ) string {
if quickYes (s , form ) {
return s
}
compat := form == NFKC || form == NFKD
rs := make ([]rune , 0 , len (s )+8 )
for _ , r := range s {
rs = appendDecomposed (rs , r , compat )
}
canonicalOrder (rs )
if form == NFC || form == NFKC {
rs = compose (rs )
}
return string (rs )
}
func IsNormalized (s string , form Form ) bool {
if quickYes (s , form ) {
return true
}
return Normalize (s , form ) == s
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .