1package numfmt23import (4 "strings"5 "unicode/utf8"67 "src.rest/cat/012/internal/locale"8)910// AdjustDecimals adds (delta > 0) or removes zeros after the decimal11// point of every section of a number pattern, as Sheets' Increase and12// Decrease decimal places do. Date and time patterns are returned13// unchanged.14func AdjustDecimals(pat string, delta int) string {15 secs := splitSections(pat)16 for i, sec := range secs {17 if isDatePattern(lexPattern(sec)) {18 return pat19 }20 secs[i] = adjustSection(sec, delta)21 }22 return strings.Join(secs, ";")23}2425func adjustSection(sec string, delta int) string {26 // Find the last digit placeholder outside quotes; decimals follow a '.'.27 last, dot := -1, -128 inQuote := false29 for i := 0; i < len(sec); i++ {30 switch c := sec[i]; {31 case c == '"':32 inQuote = !inQuote33 case inQuote:34 case c == '\\' || c == '_' || c == '*':35 i++36 case c == '.' && dot < 0:37 dot = i38 case c == '0' || c == '#' || c == '?':39 last = i40 case c == 'E' || c == 'e':41 if last >= 0 {42 // Stop at an exponent: its digits aren't decimals.43 i = len(sec)44 }45 }46 }47 if last < 0 {48 return sec49 }50 switch {51 case delta > 0 && dot < 0:52 return sec[:last+1] + "." + strings.Repeat("0", delta) + sec[last+1:]53 case delta > 0:54 return sec[:last+1] + strings.Repeat("0", delta) + sec[last+1:]55 case dot < 0 || last < dot:56 return sec57 }58 decimals := last - dot59 remove := min(-delta, decimals)60 if remove == decimals { // drop the point too61 return sec[:dot] + sec[last+1:]62 }63 return sec[:last+1-remove] + sec[last+1:]64}6566// Accounting lays out Sheets' Accounting format with dec decimals in67// width columns: the $ at the left, the number right-aligned with room68// for a closing parenthesis, negatives in parentheses and zero as a dash.69// It reports false when the number doesn't fit.70func Accounting(v float64, dec, width int) (string, bool) {71 return AccountingIn(v, dec, width, locale.Canonical)72}7374// AccountingIn is Accounting as shown in loc: its separators, and its75// currency symbol at the left of the cell, or after the number where loc76// writes the symbol after it ("1.234,56 € ").77func AccountingIn(v float64, dec, width int, loc *locale.Locale) (string, bool) {78 whole, frac := Fixed(v, dec, HalfUp)79 var num string80 switch {81 case strings.Trim(whole+frac, "0") == "":82 num = "-" + strings.Repeat(" ", dec) + " "83 case v < 0:84 num = "(" + FormatIn(-v, "#,##0"+decimals(dec), loc) + ")"85 default:86 num = FormatIn(v, "#,##0"+decimals(dec), loc) + " "87 }88 sym := loc.Currency89 if loc.After {90 body := strings.TrimSuffix(num, " ") + " " + sym + " "91 gap := width - utf8.RuneCountInString(body)92 if gap < 0 {93 return "", false94 }95 return strings.Repeat(" ", gap) + body, true96 }97 gap := width - utf8.RuneCountInString(sym) - utf8.RuneCountInString(num)98 switch {99 case gap < 0:100 return "", false101 case gap >= 1:102 // Leave a column of padding before the symbol, like other cells.103 return " " + sym + strings.Repeat(" ", gap-1) + num, true104 }105 return sym + strings.Repeat(" ", gap) + num, true106}107108func decimals(n int) string {109 if n <= 0 {110 return ""111 }112 return "." + strings.Repeat("0", n)113}