| // Copyright 2012 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build icu | |
| package main | |
| /* | |
| #cgo LDFLAGS: -licui18n -licuuc | |
| #include <stdlib.h> | |
| #include <unicode/ucol.h> | |
| #include <unicode/uiter.h> | |
| #include <unicode/utypes.h> | |
| */ | |
| import "C" | |
| import ( | |
| "fmt" | |
| "log" | |
| "unicode/utf16" | |
| "unicode/utf8" | |
| "unsafe" | |
| ) | |
| func init() { | |
| AddFactory(CollatorFactory{"icu", newUTF16, | |
| "Main ICU collator, using native strings."}) | |
| AddFactory(CollatorFactory{"icu8", newUTF8iter, | |
| "ICU collator using ICU iterators to process UTF8."}) | |
| AddFactory(CollatorFactory{"icu16", newUTF8conv, | |
| "ICU collation by first converting UTF8 to UTF16."}) | |
| } | |
| func icuCharP(s []byte) *C.char { | |
| return (*C.char)(unsafe.Pointer(&s[0])) | |
| } | |
| func icuUInt8P(s []byte) *C.uint8_t { | |
| return (*C.uint8_t)(unsafe.Pointer(&s[0])) | |
| } | |
| func icuUCharP(s []uint16) *C.UChar { | |
| return (*C.UChar)(unsafe.Pointer(&s[0])) | |
| } | |
| func icuULen(s []uint16) C.int32_t { | |
| return C.int32_t(len(s)) | |
| } | |
| func icuSLen(s []byte) C.int32_t { | |
| return C.int32_t(len(s)) | |
| } | |
| // icuCollator implements a Collator based on ICU. | |
| type icuCollator struct { | |
| loc *C.char | |
| col *C.UCollator | |
| keyBuf []byte | |
| } | |
| const growBufSize = 10 * 1024 * 1024 | |
| func (c *icuCollator) init(locale string) error { | |
| err := C.UErrorCode(0) | |
| c.loc = C.CString(locale) | |
| c.col = C.ucol_open(c.loc, &err) | |
| if err > 0 { | |
| return fmt.Errorf("failed opening collator for %q", locale) | |
| } else if err < 0 { | |
| loc := C.ucol_getLocaleByType(c.col, 0, &err) | |
| fmt, ok := map[int]string{ | |
| -127: "warning: using default collator: %s", | |
| -128: "warning: using fallback collator: %s", | |
| }[int(err)] | |
| if ok { | |
| log.Printf(fmt, C.GoString(loc)) | |
| } | |
| } | |
| c.keyBuf = make([]byte, 0, growBufSize) | |
| return nil | |
| } | |
| func (c *icuCollator) buf() (*C.uint8_t, C.int32_t) { | |
| if len(c.keyBuf) == cap(c.keyBuf) { | |
| c.keyBuf = make([]byte, 0, growBufSize) | |
| } | |
| b := c.keyBuf[len(c.keyBuf):cap(c.keyBuf)] | |
| return icuUInt8P(b), icuSLen(b) | |
| } | |
| func (c *icuCollator) extendBuf(n C.int32_t) []byte { | |
| end := len(c.keyBuf) + int(n) | |
| if end > cap(c.keyBuf) { | |
| if len(c.keyBuf) == 0 { | |
| log.Fatalf("icuCollator: max string size exceeded: %v > %v", n, growBufSize) | |
| } | |
| c.keyBuf = make([]byte, 0, growBufSize) | |
| return nil | |
| } | |
| b := c.keyBuf[len(c.keyBuf):end] | |
| c.keyBuf = c.keyBuf[:end] | |
| return b | |
| } | |
| func (c *icuCollator) Close() error { | |
| C.ucol_close(c.col) | |
| C.free(unsafe.Pointer(c.loc)) | |
| return nil | |
| } | |
| // icuUTF16 implements the Collator interface. | |
| type icuUTF16 struct { | |
| icuCollator | |
| } | |
| func newUTF16(locale string) (Collator, error) { | |
| c := &icuUTF16{} | |
| return c, c.init(locale) | |
| } | |
| func (c *icuUTF16) Compare(a, b Input) int { | |
| return int(C.ucol_strcoll(c.col, icuUCharP(a.UTF16), icuULen(a.UTF16), icuUCharP(b.UTF16), icuULen(b.UTF16))) | |
| } | |
| func (c *icuUTF16) Key(s Input) []byte { | |
| bp, bn := c.buf() | |
| n := C.ucol_getSortKey(c.col, icuUCharP(s.UTF16), icuULen(s.UTF16), bp, bn) | |
| if b := c.extendBuf(n); b != nil { | |
| return b | |
| } | |
| return c.Key(s) | |
| } | |
| // icuUTF8iter implements the Collator interface | |
| // This implementation wraps the UTF8 string in an iterator | |
| // which is passed to the collator. | |
| type icuUTF8iter struct { | |
| icuCollator | |
| a, b C.UCharIterator | |
| } | |
| func newUTF8iter(locale string) (Collator, error) { | |
| c := &icuUTF8iter{} | |
| return c, c.init(locale) | |
| } | |
| func (c *icuUTF8iter) Compare(a, b Input) int { | |
| err := C.UErrorCode(0) | |
| C.uiter_setUTF8(&c.a, icuCharP(a.UTF8), icuSLen(a.UTF8)) | |
| C.uiter_setUTF8(&c.b, icuCharP(b.UTF8), icuSLen(b.UTF8)) | |
| return int(C.ucol_strcollIter(c.col, &c.a, &c.b, &err)) | |
| } | |
| func (c *icuUTF8iter) Key(s Input) []byte { | |
| err := C.UErrorCode(0) | |
| state := [2]C.uint32_t{} | |
| C.uiter_setUTF8(&c.a, icuCharP(s.UTF8), icuSLen(s.UTF8)) | |
| bp, bn := c.buf() | |
| n := C.ucol_nextSortKeyPart(c.col, &c.a, &(state[0]), bp, bn, &err) | |
| if n >= bn { | |
| // Force failure. | |
| if c.extendBuf(n+1) != nil { | |
| log.Fatal("expected extension to fail") | |
| } | |
| return c.Key(s) | |
| } | |
| return c.extendBuf(n) | |
| } | |
| // icuUTF8conv implements the Collator interface. | |
| // This implementation first converts the give UTF8 string | |
| // to UTF16 and then calls the main ICU collation function. | |
| type icuUTF8conv struct { | |
| icuCollator | |
| } | |
| func newUTF8conv(locale string) (Collator, error) { | |
| c := &icuUTF8conv{} | |
| return c, c.init(locale) | |
| } | |
| func (c *icuUTF8conv) Compare(sa, sb Input) int { | |
| a := encodeUTF16(sa.UTF8) | |
| b := encodeUTF16(sb.UTF8) | |
| return int(C.ucol_strcoll(c.col, icuUCharP(a), icuULen(a), icuUCharP(b), icuULen(b))) | |
| } | |
| func (c *icuUTF8conv) Key(s Input) []byte { | |
| a := encodeUTF16(s.UTF8) | |
| bp, bn := c.buf() | |
| n := C.ucol_getSortKey(c.col, icuUCharP(a), icuULen(a), bp, bn) | |
| if b := c.extendBuf(n); b != nil { | |
| return b | |
| } | |
| return c.Key(s) | |
| } | |
| func encodeUTF16(b []byte) []uint16 { | |
| a := []uint16{} | |
| for len(b) > 0 { | |
| r, sz := utf8.DecodeRune(b) | |
| b = b[sz:] | |
| r1, r2 := utf16.EncodeRune(r) | |
| if r1 != 0xFFFD { | |
| a = append(a, uint16(r1), uint16(r2)) | |
| } else { | |
| a = append(a, uint16(r)) | |
| } | |
| } | |
| return a | |
| } |