1 // Copyright 2015 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
7 // Generator for currency-related data.
21 "golang.org/x/text/internal"
22 "golang.org/x/text/internal/gen"
23 "golang.org/x/text/internal/tag"
24 "golang.org/x/text/language"
25 "golang.org/x/text/unicode/cldr"
29 test = flag.Bool("test", false,
30 "test existing tables; can be used to compare web data with package data.")
31 outputFile = flag.String("output", "tables.go", "output file")
33 draft = flag.String("draft",
35 `Minimal draft requirements (approved, contributed, provisional, unconfirmed).`)
41 gen.Repackage("gen_common.go", "common.go", "currency")
43 // Read the CLDR zip file.
44 r := gen.OpenCLDRCoreZip()
48 d.SetDirFilter("supplemental", "main")
49 d.SetSectionFilter("numbers")
50 data, err := d.DecodeZip(r)
52 log.Fatalf("DecodeZip: %v", err)
55 w := gen.NewCodeWriter()
56 defer w.WriteGoFile(*outputFile, "currency")
58 fmt.Fprintln(w, `import "golang.org/x/text/internal/tag"`)
60 gen.WriteCLDRVersion(w)
62 b.genCurrencies(w, data.Supplemental())
66 var constants = []string{
67 // Undefined and testing.
69 // G11 currencies https://en.wikipedia.org/wiki/G10_currencies.
70 "USD", "EUR", "JPY", "GBP", "CHF", "AUD", "NZD", "CAD", "SEK", "NOK", "DKK",
72 "XAG", "XAU", "XPT", "XPD",
74 // Additional common currencies as defined by CLDR.
75 "BRL", "CNY", "INR", "RUB", "HKD", "IDR", "KRW", "MXN", "PLN", "SAR",
76 "THB", "TRY", "TWD", "ZAR",
84 func (b *builder) genCurrencies(w *gen.CodeWriter, data *cldr.SupplementalData) {
85 // 3-letter ISO currency codes
86 // Start with dummy to let index start at 1.
87 currencies := []string{"\x00\x00\x00\x00"}
90 for _, reg := range data.CurrencyData.Region {
91 for _, cur := range reg.Currency {
92 currencies = append(currencies, cur.Iso4217)
95 // Not included in the list for some reasons:
96 currencies = append(currencies, "MVP")
98 sort.Strings(currencies)
99 // Unique the elements.
101 for i := 1; i < len(currencies); i++ {
102 if currencies[k] != currencies[i] {
103 currencies[k+1] = currencies[i]
107 currencies = currencies[:k+1]
109 // Close with dummy for simpler and faster searching.
110 currencies = append(currencies, "\xff\xff\xff\xff")
112 // Write currency values.
113 fmt.Fprintln(w, "const (")
114 for _, c := range constants {
115 index := sort.SearchStrings(currencies, c)
116 fmt.Fprintf(w, "\t%s = %d\n", strings.ToLower(c), index)
120 // Compute currency-related data that we merge into the table.
121 for _, info := range data.CurrencyData.Fractions[0].Info {
122 if info.Iso4217 == "DEFAULT" {
125 standard := getRoundingIndex(info.Digits, info.Rounding, 0)
126 cash := getRoundingIndex(info.CashDigits, info.CashRounding, standard)
128 index := sort.SearchStrings(currencies, info.Iso4217)
129 currencies[index] += mkCurrencyInfo(standard, cash)
132 // Set default values for entries that weren't touched.
133 for i, c := range currencies {
135 currencies[i] += mkCurrencyInfo(0, 0)
139 b.currencies = tag.Index(strings.Join(currencies, ""))
141 currency holds an alphabetically sorted list of canonical 3-letter currency
142 identifiers. Each identifier is followed by a byte of type currencyInfo,
143 defined in gen_common.go.`)
144 w.WriteConst("currency", b.currencies)
146 // Hack alert: gofmt indents a trailing comment after an indented string.
147 // Ensure that the next thing written is not a comment.
148 b.numCurrencies = (len(b.currencies) / 4) - 2
149 w.WriteConst("numCurrencies", b.numCurrencies)
151 // Create a table that maps regions to currencies.
152 regionToCurrency := []toCurrency{}
154 for _, reg := range data.CurrencyData.Region {
155 if len(reg.Iso3166) != 2 {
156 log.Fatalf("Unexpected group %q in region data", reg.Iso3166)
158 if len(reg.Currency) == 0 {
161 cur := reg.Currency[0]
162 if cur.To != "" || cur.Tender == "false" {
165 regionToCurrency = append(regionToCurrency, toCurrency{
166 region: regionToCode(language.MustParseRegion(reg.Iso3166)),
167 code: uint16(b.currencies.Index([]byte(cur.Iso4217))),
170 sort.Sort(byRegion(regionToCurrency))
172 w.WriteType(toCurrency{})
173 w.WriteVar("regionToCurrency", regionToCurrency)
175 // Create a table that maps regions to currencies.
176 regionData := []regionInfo{}
178 for _, reg := range data.CurrencyData.Region {
179 if len(reg.Iso3166) != 2 {
180 log.Fatalf("Unexpected group %q in region data", reg.Iso3166)
182 for _, cur := range reg.Currency {
183 from, _ := time.Parse("2006-01-02", cur.From)
184 to, _ := time.Parse("2006-01-02", cur.To)
185 code := uint16(b.currencies.Index([]byte(cur.Iso4217)))
186 if cur.Tender == "false" {
189 regionData = append(regionData, regionInfo{
190 region: regionToCode(language.MustParseRegion(reg.Iso3166)),
197 sort.Stable(byRegionCode(regionData))
199 w.WriteType(regionInfo{})
200 w.WriteVar("regionData", regionData)
203 type regionInfo struct {
205 code uint16 // 0x8000 not legal tender
210 type byRegionCode []regionInfo
212 func (a byRegionCode) Len() int { return len(a) }
213 func (a byRegionCode) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
214 func (a byRegionCode) Less(i, j int) bool { return a[i].region < a[j].region }
216 type toCurrency struct {
221 type byRegion []toCurrency
223 func (a byRegion) Len() int { return len(a) }
224 func (a byRegion) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
225 func (a byRegion) Less(i, j int) bool { return a[i].region < a[j].region }
227 func mkCurrencyInfo(standard, cash int) string {
228 return string([]byte{byte(cash<<cashShift | standard)})
231 func getRoundingIndex(digits, rounding string, defIndex int) int {
232 round := roundings[defIndex] // default
235 round.scale = parseUint8(digits)
237 if rounding != "" && rounding != "0" { // 0 means 1 here in CLDR
238 round.increment = parseUint8(rounding)
241 // Will panic if the entry doesn't exist:
242 for i, r := range roundings {
247 log.Fatalf("Rounding entry %#v does not exist.", round)
251 // genSymbols generates the symbols used for currencies. Most symbols are
252 // defined in root and there is only very small variation per language.
253 // The following rules apply:
254 // - A symbol can be requested as normal or narrow.
255 // - If a symbol is not defined for a currency, it defaults to its ISO code.
256 func (b *builder) genSymbols(w *gen.CodeWriter, data *cldr.CLDR) {
257 d, err := cldr.ParseDraft(*draft)
259 log.Fatalf("filter: %v", err)
267 // language -> currency -> type -> symbol
268 var symbols [language.NumCompactTags][][numTypes]*string
270 // Collect symbol information per language.
271 for _, lang := range data.Locales() {
272 ldml := data.RawLDML(lang)
273 if ldml.Numbers == nil || ldml.Numbers.Currencies == nil {
277 langIndex, ok := language.CompactIndex(language.MustParse(lang))
279 log.Fatalf("No compact index for language %s", lang)
282 symbols[langIndex] = make([][numTypes]*string, b.numCurrencies+1)
284 for _, c := range ldml.Numbers.Currencies.Currency {
285 syms := cldr.MakeSlice(&c.Symbol)
288 for _, sym := range c.Symbol {
291 // We define "" to mean the ISO symbol.
294 cur := b.currencies.Index([]byte(c.Type))
295 // XXX gets reassigned to 0 in the package's code.
300 fmt.Println("Unsupported:", c.Type)
306 symbols[langIndex][cur][normal] = &v
308 symbols[langIndex][cur][narrow] = &v
314 // Remove values identical to the parent.
315 for langIndex, data := range symbols {
316 for curIndex, curs := range data {
317 for typ, sym := range curs {
321 for p := uint16(langIndex); p != 0; {
322 p = internal.Parent[p]
327 if v := x[curIndex][typ]; v != nil || p == 0 {
328 // Value is equal to the default value root value is undefined.
333 if parentSym == *sym {
334 // Value is the same as parent.
335 data[curIndex][typ] = nil
344 // Create symbol index.
345 symbolData := []byte{0}
346 symbolLookup := map[string]uint16{"": 0} // 0 means default, so block that value.
347 for _, data := range symbols {
348 for _, curs := range data {
349 for _, sym := range curs {
353 if _, ok := symbolLookup[*sym]; !ok {
354 symbolLookup[*sym] = uint16(len(symbolData))
355 symbolData = append(symbolData, byte(len(*sym)))
356 symbolData = append(symbolData, *sym...)
362 symbols holds symbol data of the form <n> <str>, where n is the length of
363 the symbol string str.`)
364 w.WriteConst("symbols", string(symbolData))
366 // Create index from language to currency lookup to symbol.
367 type curToIndex struct{ cur, idx uint16 }
368 w.WriteType(curToIndex{})
370 prefix := []string{"normal", "narrow"}
371 // Create data for regular and narrow symbol data.
372 for typ := normal; typ <= narrow; typ++ {
374 indexes := []curToIndex{} // maps currency to symbol index
375 languages := []uint16{}
377 for _, data := range symbols {
378 languages = append(languages, uint16(len(indexes)))
379 for curIndex, curs := range data {
381 if sym := curs[typ]; sym != nil {
382 indexes = append(indexes, curToIndex{uint16(curIndex), symbolLookup[*sym]})
386 languages = append(languages, uint16(len(indexes)))
388 w.WriteVar(prefix[typ]+"LangIndex", languages)
389 w.WriteVar(prefix[typ]+"SymIndex", indexes)
392 func parseUint8(str string) uint8 {
393 x, err := strconv.ParseUint(str, 10, 8)
395 // Show line number of where this function was called.
396 log.New(os.Stderr, "", log.Lshortfile).Output(2, err.Error())