123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- // Copyright 2015 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.
- // +build ignore
- // Generator for currency-related data.
- package main
- import (
- "flag"
- "fmt"
- "log"
- "os"
- "sort"
- "strconv"
- "strings"
- "time"
- "golang.org/x/text/internal/language/compact"
- "golang.org/x/text/internal/gen"
- "golang.org/x/text/internal/tag"
- "golang.org/x/text/language"
- "golang.org/x/text/unicode/cldr"
- )
- var (
- test = flag.Bool("test", false,
- "test existing tables; can be used to compare web data with package data.")
- outputFile = flag.String("output", "tables.go", "output file")
- draft = flag.String("draft",
- "contributed",
- `Minimal draft requirements (approved, contributed, provisional, unconfirmed).`)
- )
- func main() {
- gen.Init()
- gen.Repackage("gen_common.go", "common.go", "currency")
- // Read the CLDR zip file.
- r := gen.OpenCLDRCoreZip()
- defer r.Close()
- d := &cldr.Decoder{}
- d.SetDirFilter("supplemental", "main")
- d.SetSectionFilter("numbers")
- data, err := d.DecodeZip(r)
- if err != nil {
- log.Fatalf("DecodeZip: %v", err)
- }
- w := gen.NewCodeWriter()
- defer w.WriteGoFile(*outputFile, "currency")
- fmt.Fprintln(w, `import "golang.org/x/text/internal/tag"`)
- gen.WriteCLDRVersion(w)
- b := &builder{}
- b.genCurrencies(w, data.Supplemental())
- b.genSymbols(w, data)
- }
- var constants = []string{
- // Undefined and testing.
- "XXX", "XTS",
- // G11 currencies https://en.wikipedia.org/wiki/G10_currencies.
- "USD", "EUR", "JPY", "GBP", "CHF", "AUD", "NZD", "CAD", "SEK", "NOK", "DKK",
- // Precious metals.
- "XAG", "XAU", "XPT", "XPD",
- // Additional common currencies as defined by CLDR.
- "BRL", "CNY", "INR", "RUB", "HKD", "IDR", "KRW", "MXN", "PLN", "SAR",
- "THB", "TRY", "TWD", "ZAR",
- }
- type builder struct {
- currencies tag.Index
- numCurrencies int
- }
- func (b *builder) genCurrencies(w *gen.CodeWriter, data *cldr.SupplementalData) {
- // 3-letter ISO currency codes
- // Start with dummy to let index start at 1.
- currencies := []string{"\x00\x00\x00\x00"}
- // currency codes
- for _, reg := range data.CurrencyData.Region {
- for _, cur := range reg.Currency {
- currencies = append(currencies, cur.Iso4217)
- }
- }
- // Not included in the list for some reasons:
- currencies = append(currencies, "MVP")
- sort.Strings(currencies)
- // Unique the elements.
- k := 0
- for i := 1; i < len(currencies); i++ {
- if currencies[k] != currencies[i] {
- currencies[k+1] = currencies[i]
- k++
- }
- }
- currencies = currencies[:k+1]
- // Close with dummy for simpler and faster searching.
- currencies = append(currencies, "\xff\xff\xff\xff")
- // Write currency values.
- fmt.Fprintln(w, "const (")
- for _, c := range constants {
- index := sort.SearchStrings(currencies, c)
- fmt.Fprintf(w, "\t%s = %d\n", strings.ToLower(c), index)
- }
- fmt.Fprint(w, ")")
- // Compute currency-related data that we merge into the table.
- for _, info := range data.CurrencyData.Fractions[0].Info {
- if info.Iso4217 == "DEFAULT" {
- continue
- }
- standard := getRoundingIndex(info.Digits, info.Rounding, 0)
- cash := getRoundingIndex(info.CashDigits, info.CashRounding, standard)
- index := sort.SearchStrings(currencies, info.Iso4217)
- currencies[index] += mkCurrencyInfo(standard, cash)
- }
- // Set default values for entries that weren't touched.
- for i, c := range currencies {
- if len(c) == 3 {
- currencies[i] += mkCurrencyInfo(0, 0)
- }
- }
- b.currencies = tag.Index(strings.Join(currencies, ""))
- w.WriteComment(`
- currency holds an alphabetically sorted list of canonical 3-letter currency
- identifiers. Each identifier is followed by a byte of type currencyInfo,
- defined in gen_common.go.`)
- w.WriteConst("currency", b.currencies)
- // Hack alert: gofmt indents a trailing comment after an indented string.
- // Ensure that the next thing written is not a comment.
- b.numCurrencies = (len(b.currencies) / 4) - 2
- w.WriteConst("numCurrencies", b.numCurrencies)
- // Create a table that maps regions to currencies.
- regionToCurrency := []toCurrency{}
- for _, reg := range data.CurrencyData.Region {
- if len(reg.Iso3166) != 2 {
- log.Fatalf("Unexpected group %q in region data", reg.Iso3166)
- }
- if len(reg.Currency) == 0 {
- continue
- }
- cur := reg.Currency[0]
- if cur.To != "" || cur.Tender == "false" {
- continue
- }
- regionToCurrency = append(regionToCurrency, toCurrency{
- region: regionToCode(language.MustParseRegion(reg.Iso3166)),
- code: uint16(b.currencies.Index([]byte(cur.Iso4217))),
- })
- }
- sort.Sort(byRegion(regionToCurrency))
- w.WriteType(toCurrency{})
- w.WriteVar("regionToCurrency", regionToCurrency)
- // Create a table that maps regions to currencies.
- regionData := []regionInfo{}
- for _, reg := range data.CurrencyData.Region {
- if len(reg.Iso3166) != 2 {
- log.Fatalf("Unexpected group %q in region data", reg.Iso3166)
- }
- for _, cur := range reg.Currency {
- from, _ := time.Parse("2006-01-02", cur.From)
- to, _ := time.Parse("2006-01-02", cur.To)
- code := uint16(b.currencies.Index([]byte(cur.Iso4217)))
- if cur.Tender == "false" {
- code |= nonTenderBit
- }
- regionData = append(regionData, regionInfo{
- region: regionToCode(language.MustParseRegion(reg.Iso3166)),
- code: code,
- from: toDate(from),
- to: toDate(to),
- })
- }
- }
- sort.Stable(byRegionCode(regionData))
- w.WriteType(regionInfo{})
- w.WriteVar("regionData", regionData)
- }
- type regionInfo struct {
- region uint16
- code uint16 // 0x8000 not legal tender
- from uint32
- to uint32
- }
- type byRegionCode []regionInfo
- func (a byRegionCode) Len() int { return len(a) }
- func (a byRegionCode) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
- func (a byRegionCode) Less(i, j int) bool { return a[i].region < a[j].region }
- type toCurrency struct {
- region uint16
- code uint16
- }
- type byRegion []toCurrency
- func (a byRegion) Len() int { return len(a) }
- func (a byRegion) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
- func (a byRegion) Less(i, j int) bool { return a[i].region < a[j].region }
- func mkCurrencyInfo(standard, cash int) string {
- return string([]byte{byte(cash<<cashShift | standard)})
- }
- func getRoundingIndex(digits, rounding string, defIndex int) int {
- round := roundings[defIndex] // default
- if digits != "" {
- round.scale = parseUint8(digits)
- }
- if rounding != "" && rounding != "0" { // 0 means 1 here in CLDR
- round.increment = parseUint8(rounding)
- }
- // Will panic if the entry doesn't exist:
- for i, r := range roundings {
- if r == round {
- return i
- }
- }
- log.Fatalf("Rounding entry %#v does not exist.", round)
- panic("unreachable")
- }
- // genSymbols generates the symbols used for currencies. Most symbols are
- // defined in root and there is only very small variation per language.
- // The following rules apply:
- // - A symbol can be requested as normal or narrow.
- // - If a symbol is not defined for a currency, it defaults to its ISO code.
- func (b *builder) genSymbols(w *gen.CodeWriter, data *cldr.CLDR) {
- d, err := cldr.ParseDraft(*draft)
- if err != nil {
- log.Fatalf("filter: %v", err)
- }
- const (
- normal = iota
- narrow
- numTypes
- )
- // language -> currency -> type -> symbol
- var symbols [compact.NumCompactTags][][numTypes]*string
- // Collect symbol information per language.
- for _, lang := range data.Locales() {
- ldml := data.RawLDML(lang)
- if ldml.Numbers == nil || ldml.Numbers.Currencies == nil {
- continue
- }
- langIndex, ok := compact.LanguageID(compact.Tag(language.MustParse(lang)))
- if !ok {
- log.Fatalf("No compact index for language %s", lang)
- }
- symbols[langIndex] = make([][numTypes]*string, b.numCurrencies+1)
- for _, c := range ldml.Numbers.Currencies.Currency {
- syms := cldr.MakeSlice(&c.Symbol)
- syms.SelectDraft(d)
- for _, sym := range c.Symbol {
- v := sym.Data()
- if v == c.Type {
- // We define "" to mean the ISO symbol.
- v = ""
- }
- cur := b.currencies.Index([]byte(c.Type))
- // XXX gets reassigned to 0 in the package's code.
- if c.Type == "XXX" {
- cur = 0
- }
- if cur == -1 {
- fmt.Println("Unsupported:", c.Type)
- continue
- }
- switch sym.Alt {
- case "":
- symbols[langIndex][cur][normal] = &v
- case "narrow":
- symbols[langIndex][cur][narrow] = &v
- }
- }
- }
- }
- // Remove values identical to the parent.
- for langIndex, data := range symbols {
- for curIndex, curs := range data {
- for typ, sym := range curs {
- if sym == nil {
- continue
- }
- for p := compact.ID(langIndex); p != 0; {
- p = p.Parent()
- x := symbols[p]
- if x == nil {
- continue
- }
- if v := x[curIndex][typ]; v != nil || p == 0 {
- // Value is equal to the default value root value is undefined.
- parentSym := ""
- if v != nil {
- parentSym = *v
- }
- if parentSym == *sym {
- // Value is the same as parent.
- data[curIndex][typ] = nil
- }
- break
- }
- }
- }
- }
- }
- // Create symbol index.
- symbolData := []byte{0}
- symbolLookup := map[string]uint16{"": 0} // 0 means default, so block that value.
- for _, data := range symbols {
- for _, curs := range data {
- for _, sym := range curs {
- if sym == nil {
- continue
- }
- if _, ok := symbolLookup[*sym]; !ok {
- symbolLookup[*sym] = uint16(len(symbolData))
- symbolData = append(symbolData, byte(len(*sym)))
- symbolData = append(symbolData, *sym...)
- }
- }
- }
- }
- w.WriteComment(`
- symbols holds symbol data of the form <n> <str>, where n is the length of
- the symbol string str.`)
- w.WriteConst("symbols", string(symbolData))
- // Create index from language to currency lookup to symbol.
- type curToIndex struct{ cur, idx uint16 }
- w.WriteType(curToIndex{})
- prefix := []string{"normal", "narrow"}
- // Create data for regular and narrow symbol data.
- for typ := normal; typ <= narrow; typ++ {
- indexes := []curToIndex{} // maps currency to symbol index
- languages := []uint16{}
- for _, data := range symbols {
- languages = append(languages, uint16(len(indexes)))
- for curIndex, curs := range data {
- if sym := curs[typ]; sym != nil {
- indexes = append(indexes, curToIndex{uint16(curIndex), symbolLookup[*sym]})
- }
- }
- }
- languages = append(languages, uint16(len(indexes)))
- w.WriteVar(prefix[typ]+"LangIndex", languages)
- w.WriteVar(prefix[typ]+"SymIndex", indexes)
- }
- }
- func parseUint8(str string) uint8 {
- x, err := strconv.ParseUint(str, 10, 8)
- if err != nil {
- // Show line number of where this function was called.
- log.New(os.Stderr, "", log.Lshortfile).Output(2, err.Error())
- os.Exit(1)
- }
- return uint8(x)
- }
|