|  | // 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" | 
|  | "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 [language.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 := language.CompactIndex(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 := uint16(langIndex); p != 0; { | 
|  | p = internal.Parent[p] | 
|  | 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) | 
|  | } |