1
0
mirror of https://github.com/pbnjay/grate.git synced 2024-12-13 13:58:27 +02:00
grate/commonxl/formats.go

287 lines
7.1 KiB
Go

package commonxl
import (
"errors"
"fmt"
"regexp"
"strings"
)
// Formatter contains formatting methods common to Excel spreadsheets.
type Formatter struct {
flags uint64
customCodes map[uint16]FmtFunc
}
const (
fMode1904 uint64 = 1
)
// Mode1904 indicates that dates start on Jan 1, 1904
// this setting was used in early MacOS Excel applications.
func (x *Formatter) Mode1904(enabled bool) {
if enabled {
x.flags |= fMode1904
} else {
x.flags = x.flags &^ fMode1904
}
}
// Add a custom number format to the formatter.
func (x *Formatter) Add(fmtID uint16, formatCode string) error {
_, ok := goFormatters[fmtID]
if ok {
return errors.New("grate/commonxl: cannot replace default number formats")
}
if x.customCodes == nil {
x.customCodes = make(map[uint16]FmtFunc)
}
_, ok2 := x.customCodes[fmtID]
if ok2 {
return errors.New("grate/commonxl: cannot replace existing number formats")
}
x.customCodes[fmtID] = makeFormatter(formatCode)
return nil
}
var (
minsMatch = regexp.MustCompile("h.*m.*s")
nonEsc = regexp.MustCompile(`([^"]|^)"`)
squash = regexp.MustCompile(`[*_].`)
fixEsc = regexp.MustCompile(`\\(.)`)
formatMatchBrackets = regexp.MustCompile(`\[[^\]]*\]`)
formatMatchTextLiteral = regexp.MustCompile(`"[^"]*"`)
)
func makeFormatter(s string) FmtFunc {
//log.Printf("makeFormatter('%s')", s)
// remove any coloring marks
s = formatMatchBrackets.ReplaceAllString(s, "")
if strings.Contains(s, ";") {
parts := strings.Split(s, ";")
posFF := makeFormatter(parts[0])
rem := make([]FmtFunc, len(parts)-1)
for i, ps := range parts[1:] {
rem[i] = makeFormatter(ps)
}
return switchFmtFunc(posFF, rem...)
}
// escaped characters, and quoted text
s2 := fixEsc.ReplaceAllString(s, "")
s2 = formatMatchTextLiteral.ReplaceAllString(s, "")
if strings.ContainsAny(s2, "ymdhs") {
// it's a date/time format
if loc := minsMatch.FindStringIndex(s); loc != nil {
// m or mm in loc[0]:loc[1] is a minute format
inner := s[loc[0]:loc[1]]
inner = strings.Replace(inner, "mm", "04", 1)
inner = strings.Replace(inner, "m", "4", 1)
s = s[:loc[0]] + inner + s[loc[1]:]
}
dfreps := [][]string{
{"hh", "15"}, {"h", "15"},
{"ss", "05"}, {"s", "5"},
{"mmmmm", "Jan"}, // super ambiguous, replace with 3-letter month
{"mmmm", "January"}, {"mmm", "Jan"},
{"mm", "01"}, {"m", "1"},
{"dddd", "Monday"}, {"ddd", "Mon"},
{"dd", "02"}, {"d", "2"},
{"yyyy", "2006"}, {"yy", "06"},
}
if strings.Contains(s, "AM") || strings.Contains(s, "PM") {
dfreps[0][1] = "03"
dfreps[1][1] = "3"
}
for _, dfr := range dfreps {
s = strings.Replace(s, dfr[0], dfr[1], 1)
}
s = nonEsc.ReplaceAllString(s, `$1`)
s = squash.ReplaceAllString(s, ``)
s = fixEsc.ReplaceAllString(s, `$1`)
//log.Printf(" made time formatter '%s'", s)
return timeFmtFunc(s)
}
var ff FmtFunc
if strings.ContainsAny(s, ".Ee") {
verb := "f"
if strings.ContainsAny(s, "Ee") {
verb = "E"
}
s = regexp.MustCompile("[eE]+[+-]0+").ReplaceAllString(s, "")
s2 := strings.ReplaceAll(s, ",", "")
i1 := strings.IndexAny(s2, "0")
i2 := strings.IndexByte(s2, '.')
i3 := strings.LastIndexAny(s2, "0.")
mul := 1
if strings.Contains(s2, "%") {
mul = 100
}
sf := fmt.Sprintf("%%%d.%d%s", i3-i1, i3-i2, verb)
//log.Printf(" made float formatter '%s'", sf)
ff = sprintfFunc(sf, mul)
} else {
s2 := strings.ReplaceAll(s, ",", "")
i1 := strings.IndexAny(s2, "0")
i2 := strings.LastIndexAny(s2, "0.")
mul := 1
if strings.Contains(s2, "%") {
mul = 100
}
sf := fmt.Sprintf("%%%dd", i2-i1)
if (i2 - i1) == 0 {
sf = "%d"
}
//log.Printf(" made int formatter '%s'", sf)
ff = sprintfFunc(sf, mul)
}
if strings.Contains(s, ",") {
ff = addCommas(ff)
//log.Printf(" added commas")
}
surReg := regexp.MustCompile(`[0#?,.]+`)
prepost := surReg.Split(s, 2)
if len(prepost) > 0 && len(prepost[0]) > 0 {
prepost[0] = nonEsc.ReplaceAllString(prepost[0], `$1`)
prepost[0] = squash.ReplaceAllString(prepost[0], ``)
prepost[0] = fixEsc.ReplaceAllString(prepost[0], `$1`)
}
if len(prepost) == 1 {
if prepost[0] == "@" {
return identFunc
}
//log.Printf(" added static ('%s')", prepost[0])
return staticFmtFunc(prepost[0])
}
if len(prepost[0]) > 0 || len(prepost[1]) > 0 {
prepost[1] = nonEsc.ReplaceAllString(prepost[1], `$1`)
prepost[1] = squash.ReplaceAllString(prepost[1], ``)
prepost[1] = fixEsc.ReplaceAllString(prepost[1], `$1`)
ff = surround(prepost[0], ff, prepost[1])
//log.Printf(" added surround ('%s' ... '%s')", prepost[0], prepost[1])
}
return ff
}
// Get the number format func to use for formatting values,
// it returns false when fmtID is unknown.
func (x *Formatter) Get(fmtID uint16) (FmtFunc, bool) {
ff, ok := goFormatters[fmtID]
if !ok {
fs, ok2 := x.customCodes[fmtID]
if ok2 {
return fs, true
}
ff = identFunc
}
return ff, ok
}
// Apply the specified number format to the value.
// Returns false when fmtID is unknown.
func (x *Formatter) Apply(fmtID uint16, val interface{}) (string, bool) {
ff, ok := goFormatters[fmtID]
if !ok {
fs, ok2 := x.customCodes[fmtID]
if ok2 {
return fs(x, val), true
}
}
return ff(x, val), ok
}
// builtInFormats are all the built-in number formats for XLS/XLSX.
var builtInFormats = map[uint16]string{
0: `General`,
1: `0`,
2: `0.00`,
3: `#,##0`,
4: `#,##0.00`,
9: `0%`,
10: `0.00%`,
11: `0.00E+00`,
12: `# ?/?`,
13: `# ??/??`,
14: `mm-dd-yy`,
15: `d-mmm-yy`,
16: `d-mmm`,
17: `mmm-yy`,
18: `h:mm AM/PM`,
19: `h:mm:ss AM/PM`,
20: `h:mm`,
21: `h:mm:ss`,
22: `m/d/yy h:mm`,
37: `#,##0 ;(#,##0)`,
38: `#,##0 ;[Red](#,##0)`,
39: `#,##0.00;(#,##0.00)`,
40: `#,##0.00;[Red](#,##0.00)`,
41: `_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)`,
42: `_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)`,
43: `_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)`,
44: `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`,
45: `mm:ss`,
46: `[h]:mm:ss`,
47: `mmss.0`,
48: `##0.0E+0`,
49: `@`,
// zh-cn format codes
27: `yyyy"年"m"月"`,
28: `m"月"d"日"`,
29: `m"月"d"日"`,
30: `m-d-yy`,
31: `yyyy"年"m"月"d"日"`,
32: `h"时"mm"分"`,
33: `h"时"mm"分"ss"秒"`,
34: `上午/下午 h"时"mm"分"`,
35: `上午/下午 h"时"mm"分"ss"秒"`,
36: `yyyy"年"m"月"`,
50: `yyyy"年"m"月"`,
51: `m"月"d"日"`,
52: `yyyy"年"m"月"`,
53: `m"月"d"日"`,
54: `m"月"d"日"`,
55: `上午/下午 h"时"mm"分"`,
56: `上午/下午 h"时"mm"分"ss"秒`,
57: `yyyy"年"m"月"`,
58: `m"月"d"日"`,
// th-th format codes
59: `t0`,
60: `t0.00`,
61: `t#,##0`,
62: `t#,##0.00`,
67: `t0%`,
68: `t0.00%`,
69: `t# ?/?`,
70: `t# ??/??`,
// th format code, but translated to aid the parser
71: `d/m/yyyy`, // `ว/ด/ปปปป`,
72: `d-mmm-yy`, // `ว-ดดด-ปป`,
73: `d-mmm`, // `ว-ดดด`,
74: `mmm-yy`, // `ดดด-ปป`,
75: `h:mm`, // `ช:นน`,
76: `h:mm:ss`, // `ช:นน:ทท`,
77: `d/m/yyyy h:mm`, // `ว/ด/ปปปป ช:นน`,
78: `mm:ss`, // `นน:ทท`,
79: `[h]:mm:ss`, // `[ช]:นน:ทท`,
80: `mm:ss.0`, // `นน:ทท.0`,
81: `d/m/bb`, // `d/m/bb`,
}