mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
format: add support for fraction formatting
This commit is contained in:
parent
32bc9e3a50
commit
1103aa1edb
@ -12,28 +12,37 @@ import (
|
||||
"log"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// constants used when formatting generic values to determine when to start
|
||||
// rounding
|
||||
const maxGeneric = 1e11
|
||||
const minGeneric = 1e-10
|
||||
|
||||
// Format is a parsed number format.
|
||||
type Format struct {
|
||||
Whole []PlaceHolder
|
||||
Fractional []PlaceHolder
|
||||
Exponent []PlaceHolder
|
||||
Whole []Token
|
||||
Fractional []Token
|
||||
Exponent []Token
|
||||
IsExponential bool
|
||||
|
||||
isFraction bool
|
||||
isPercent bool
|
||||
isGeneral bool
|
||||
hasThousands bool
|
||||
skipNext bool
|
||||
seenDecimal bool
|
||||
|
||||
denom int64
|
||||
}
|
||||
|
||||
// FmtType is the type of a format token.
|
||||
//go:generate stringer -type=FmtType
|
||||
type FmtType byte
|
||||
|
||||
// Format type constants.
|
||||
const (
|
||||
FmtTypeLiteral FmtType = iota
|
||||
FmtTypeDigit
|
||||
@ -46,15 +55,18 @@ const (
|
||||
FmtTypeUnderscore
|
||||
FmtTypeDate
|
||||
FmtTypeTime
|
||||
FmtTypeFraction
|
||||
)
|
||||
|
||||
type PlaceHolder struct {
|
||||
// Token is a format token in the Excel format string.
|
||||
type Token struct {
|
||||
Type FmtType
|
||||
Literal byte
|
||||
DateTime string
|
||||
}
|
||||
|
||||
func (f *Format) AddPlaceholder(t FmtType, l []byte) {
|
||||
// AddToken adds a format token to the format.
|
||||
func (f *Format) AddToken(t FmtType, l []byte) {
|
||||
if f.skipNext {
|
||||
f.skipNext = false
|
||||
return
|
||||
@ -65,7 +77,7 @@ func (f *Format) AddPlaceholder(t FmtType, l []byte) {
|
||||
case FmtTypeUnderscore:
|
||||
f.skipNext = true
|
||||
case FmtTypeDate, FmtTypeTime:
|
||||
f.Whole = append(f.Whole, PlaceHolder{Type: t, DateTime: string(l)})
|
||||
f.Whole = append(f.Whole, Token{Type: t, DateTime: string(l)})
|
||||
case FmtTypePercent:
|
||||
f.isPercent = true
|
||||
t = FmtTypeLiteral
|
||||
@ -79,15 +91,22 @@ func (f *Format) AddPlaceholder(t FmtType, l []byte) {
|
||||
}
|
||||
for _, c := range l {
|
||||
if f.IsExponential {
|
||||
f.Exponent = append(f.Exponent, PlaceHolder{Type: t, Literal: c})
|
||||
f.Exponent = append(f.Exponent, Token{Type: t, Literal: c})
|
||||
} else if !f.seenDecimal {
|
||||
f.Whole = append(f.Whole, PlaceHolder{Type: t, Literal: c})
|
||||
f.Whole = append(f.Whole, Token{Type: t, Literal: c})
|
||||
} else {
|
||||
f.Fractional = append(f.Fractional, PlaceHolder{Type: t, Literal: c})
|
||||
f.Fractional = append(f.Fractional, Token{Type: t, Literal: c})
|
||||
}
|
||||
}
|
||||
case FmtTypeDigitOptThousands:
|
||||
f.hasThousands = true
|
||||
case FmtTypeFraction:
|
||||
sp := strings.Split(string(l), "/")
|
||||
if len(sp) == 2 {
|
||||
f.isFraction = true
|
||||
f.denom, _ = strconv.ParseInt(sp[1], 10, 64)
|
||||
// TODO: if anyone cares, parse and use the numerator format.
|
||||
}
|
||||
default:
|
||||
log.Printf("unsupported ph type in parse %v", t)
|
||||
}
|
||||
@ -111,6 +130,8 @@ func Number(v float64, f string) string {
|
||||
return number(v, fmts[0], false)
|
||||
}
|
||||
|
||||
// String returns the string formatted according to the type.
|
||||
// TODO: implement if anyone needs this.
|
||||
func String(v string, f string) string {
|
||||
return v
|
||||
}
|
||||
@ -124,9 +145,6 @@ func reverse(b []byte) []byte {
|
||||
}
|
||||
|
||||
func number(vOrig float64, f Format, isNeg bool) string {
|
||||
epoch := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
|
||||
t := epoch.Add(time.Duration(vOrig * float64(24*time.Hour)))
|
||||
t = asLocal(t)
|
||||
if f.isGeneral {
|
||||
return NumberGeneric(vOrig)
|
||||
}
|
||||
@ -134,10 +152,7 @@ func number(vOrig float64, f Format, isNeg bool) string {
|
||||
wasNeg := math.Signbit(vOrig)
|
||||
v := math.Abs(vOrig)
|
||||
|
||||
// percent symbol implies multiplying the value by 100
|
||||
if f.isPercent {
|
||||
v *= 100
|
||||
}
|
||||
fractNum := int64(0)
|
||||
exp := int64(0)
|
||||
if f.IsExponential {
|
||||
for v >= 10 {
|
||||
@ -148,6 +163,14 @@ func number(vOrig float64, f Format, isNeg bool) string {
|
||||
exp--
|
||||
v *= 10
|
||||
}
|
||||
} else if f.isPercent {
|
||||
// percent symbol implies multiplying the value by 100
|
||||
v *= 100
|
||||
} else if f.isFraction {
|
||||
fractNum = int64(v*float64(f.denom) + 0.5)
|
||||
if len(f.Whole) > 0 {
|
||||
fractNum = fractNum % f.denom
|
||||
}
|
||||
}
|
||||
|
||||
// round up now as this avoids rounding up on just the decimal portion which
|
||||
@ -162,192 +185,17 @@ func number(vOrig float64, f Format, isNeg bool) string {
|
||||
|
||||
// split into whole and decimal portions
|
||||
pre, post := math.Modf(v)
|
||||
buf = append(buf, formatWholeNumber(pre, vOrig, f)...)
|
||||
buf = append(buf, formatFractional(post, vOrig, f)...)
|
||||
buf = append(buf, formatExponential(exp, f)...)
|
||||
|
||||
if len(f.Whole) > 0 {
|
||||
raw := strconv.AppendFloat(nil, pre, 'f', -1, 64)
|
||||
op := make([]byte, 0, len(raw))
|
||||
consumed := 0
|
||||
lastIdx := 1
|
||||
lfor:
|
||||
for i := len(f.Whole) - 1; i >= 0; i-- {
|
||||
bidx := len(raw) - 1 - consumed
|
||||
ph := f.Whole[i]
|
||||
switch ph.Type {
|
||||
// '0' consumes a digit or prints a '0' if there is no digit
|
||||
case FmtTypeDigit:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
lastIdx = i
|
||||
} else {
|
||||
op = append(op, '0')
|
||||
}
|
||||
// '#' consumes a digit or prints nothing
|
||||
case FmtTypeDigitOpt:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
lastIdx = i
|
||||
} else {
|
||||
// we don't skip everything, just #/,/?. This is used so
|
||||
// that formats like (#,###) with '1' turn into '(1)' and
|
||||
// not '1)'
|
||||
for j := i; j >= 0; j-- {
|
||||
c := f.Whole[j]
|
||||
if c.Type == FmtTypeLiteral {
|
||||
op = append(op, c.Literal)
|
||||
}
|
||||
}
|
||||
break lfor
|
||||
}
|
||||
case FmtTypeDollar:
|
||||
for i := consumed; i < len(raw); i++ {
|
||||
op = append(op, raw[len(raw)-1-i])
|
||||
consumed++
|
||||
}
|
||||
op = append(op, '$')
|
||||
case FmtTypeComma:
|
||||
if !f.hasThousands {
|
||||
op = append(op, ',')
|
||||
}
|
||||
case FmtTypeLiteral:
|
||||
op = append(op, ph.Literal)
|
||||
case FmtTypeDate:
|
||||
op = append(op, reverse(dDate(t, ph.DateTime))...)
|
||||
case FmtTypeTime:
|
||||
op = append(op, reverse(dTime(t, vOrig, ph.DateTime))...)
|
||||
default:
|
||||
log.Printf("unsupported type in whole %v", ph)
|
||||
}
|
||||
}
|
||||
buf = append(buf, reverse(op)...)
|
||||
|
||||
// didn't consume all of the number characters, so insert the rest where
|
||||
// we were last inserting
|
||||
if consumed < len(raw) && (consumed != 0 || f.seenDecimal) {
|
||||
rem := len(raw) - consumed
|
||||
o := make([]byte, len(buf)+rem)
|
||||
copy(o, buf[0:lastIdx])
|
||||
copy(o[lastIdx:], raw[0:])
|
||||
copy(o[lastIdx+rem:], buf[lastIdx:])
|
||||
buf = o
|
||||
}
|
||||
if f.hasThousands {
|
||||
b := bytes.Buffer{}
|
||||
nonTerm := 0
|
||||
for i := len(buf) - 1; i >= 0; i-- {
|
||||
if !(buf[i] >= '0' && buf[i] <= '9') {
|
||||
nonTerm++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(buf); i++ {
|
||||
idx := (len(buf) - i - nonTerm)
|
||||
if idx%3 == 0 && idx != 0 && i != 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
b.WriteByte(buf[i])
|
||||
|
||||
}
|
||||
buf = b.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.Fractional) != 0 {
|
||||
buf = append(buf, '.')
|
||||
raw := strconv.AppendFloat(nil, post, 'f', -1, 64)
|
||||
if len(raw) > 2 {
|
||||
raw = raw[2:] // skip the decimal portion (ie. '0.')
|
||||
} else {
|
||||
raw = nil
|
||||
}
|
||||
op := make([]byte, 0, len(raw))
|
||||
consumed := 0
|
||||
lforPost:
|
||||
for i := 0; i < len(f.Fractional); i++ {
|
||||
bidx := i
|
||||
ph := f.Fractional[i]
|
||||
switch ph.Type {
|
||||
// '0' consumes a digit or prints a '0' if there is no digit
|
||||
case FmtTypeDigit:
|
||||
if bidx < len(raw) {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
} else {
|
||||
op = append(op, '0')
|
||||
}
|
||||
// '#' consumes a digit or prints nothing
|
||||
case FmtTypeDigitOpt:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
} else {
|
||||
break lforPost
|
||||
}
|
||||
case FmtTypeLiteral:
|
||||
op = append(op, ph.Literal)
|
||||
default:
|
||||
log.Printf("unsupported type in fractional %v", ph)
|
||||
}
|
||||
}
|
||||
// remaining digits are truncated
|
||||
buf = append(buf, op...)
|
||||
}
|
||||
|
||||
if f.IsExponential {
|
||||
if len(f.Exponent) > 0 {
|
||||
buf = append(buf, 'E')
|
||||
if exp >= 0 {
|
||||
buf = append(buf, '+')
|
||||
} else {
|
||||
buf = append(buf, '-')
|
||||
exp *= -1
|
||||
}
|
||||
raw := strconv.AppendInt(nil, exp, 10)
|
||||
op := make([]byte, 0, len(raw))
|
||||
consumed := 0
|
||||
|
||||
lexfor:
|
||||
for i := len(f.Exponent) - 1; i >= 0; i-- {
|
||||
bidx := len(raw) - 1 - consumed
|
||||
ph := f.Exponent[i]
|
||||
switch ph.Type {
|
||||
// '0' consumes a digit or prints a '0' if there is no digit
|
||||
case FmtTypeDigit:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
} else {
|
||||
op = append(op, '0')
|
||||
}
|
||||
// '#' consumes a digit or prints nothing
|
||||
case FmtTypeDigitOpt:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
} else {
|
||||
for j := i; j >= 0; j-- {
|
||||
c := f.Exponent[j]
|
||||
if c.Type == FmtTypeLiteral {
|
||||
op = append(op, c.Literal)
|
||||
}
|
||||
}
|
||||
break lexfor
|
||||
}
|
||||
case FmtTypeLiteral:
|
||||
op = append(op, ph.Literal)
|
||||
default:
|
||||
log.Printf("unsupported type in exp %v", ph)
|
||||
}
|
||||
}
|
||||
// remaining non-consumed digits in the exponent
|
||||
if consumed < len(raw) {
|
||||
op = append(op, raw[len(raw)-consumed-1:consumed-1]...)
|
||||
}
|
||||
buf = append(buf, reverse(op)...)
|
||||
|
||||
}
|
||||
// fractions are special, the whole number portion is handled above (if
|
||||
// len(f.whole) > 0). This is for the fractional portion, or in the case if
|
||||
// no whole portion, the numerator will be greater than the denominator.
|
||||
if f.isFraction {
|
||||
buf = strconv.AppendInt(buf, fractNum, 10)
|
||||
buf = append(buf, '/')
|
||||
buf = strconv.AppendInt(buf, f.denom, 10)
|
||||
}
|
||||
// if the number was negative, but this isn't a 'negative' format, then
|
||||
// we need to prepend a negative sign
|
||||
@ -357,6 +205,216 @@ func number(vOrig float64, f Format, isNeg bool) string {
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func formatWholeNumber(pre, vOrig float64, f Format) []byte {
|
||||
if len(f.Whole) == 0 {
|
||||
return nil
|
||||
}
|
||||
epoch := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
|
||||
t := epoch.Add(time.Duration(vOrig * float64(24*time.Hour)))
|
||||
t = asLocal(t)
|
||||
|
||||
raw := strconv.AppendFloat(nil, pre, 'f', -1, 64)
|
||||
op := make([]byte, 0, len(raw))
|
||||
consumed := 0
|
||||
lastIdx := 1
|
||||
lfor:
|
||||
for i := len(f.Whole) - 1; i >= 0; i-- {
|
||||
bidx := len(raw) - 1 - consumed
|
||||
ph := f.Whole[i]
|
||||
switch ph.Type {
|
||||
// '0' consumes a digit or prints a '0' if there is no digit
|
||||
case FmtTypeDigit:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
lastIdx = i
|
||||
} else {
|
||||
op = append(op, '0')
|
||||
}
|
||||
// '#' consumes a digit or prints nothing
|
||||
case FmtTypeDigitOpt:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
lastIdx = i
|
||||
} else {
|
||||
// we don't skip everything, just #/,/?. This is used so
|
||||
// that formats like (#,###) with '1' turn into '(1)' and
|
||||
// not '1)'
|
||||
for j := i; j >= 0; j-- {
|
||||
c := f.Whole[j]
|
||||
if c.Type == FmtTypeLiteral {
|
||||
op = append(op, c.Literal)
|
||||
}
|
||||
}
|
||||
break lfor
|
||||
}
|
||||
case FmtTypeDollar:
|
||||
for i := consumed; i < len(raw); i++ {
|
||||
op = append(op, raw[len(raw)-1-i])
|
||||
consumed++
|
||||
}
|
||||
op = append(op, '$')
|
||||
case FmtTypeComma:
|
||||
if !f.hasThousands {
|
||||
op = append(op, ',')
|
||||
}
|
||||
case FmtTypeLiteral:
|
||||
op = append(op, ph.Literal)
|
||||
case FmtTypeDate:
|
||||
op = append(op, reverse(dDate(t, ph.DateTime))...)
|
||||
case FmtTypeTime:
|
||||
op = append(op, reverse(dTime(t, vOrig, ph.DateTime))...)
|
||||
default:
|
||||
log.Printf("unsupported type in whole %v", ph)
|
||||
}
|
||||
}
|
||||
|
||||
buf := reverse(op)
|
||||
// didn't consume all of the number characters, so insert the rest where
|
||||
// we were last inserting
|
||||
if consumed < len(raw) && (consumed != 0 || f.seenDecimal) {
|
||||
rem := len(raw) - consumed
|
||||
o := make([]byte, len(buf)+rem)
|
||||
copy(o, buf[0:lastIdx])
|
||||
copy(o[lastIdx:], raw[0:])
|
||||
copy(o[lastIdx+rem:], buf[lastIdx:])
|
||||
buf = o
|
||||
}
|
||||
if f.hasThousands {
|
||||
b := bytes.Buffer{}
|
||||
nonTerm := 0
|
||||
for i := len(buf) - 1; i >= 0; i-- {
|
||||
if !(buf[i] >= '0' && buf[i] <= '9') {
|
||||
nonTerm++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(buf); i++ {
|
||||
idx := (len(buf) - i - nonTerm)
|
||||
if idx%3 == 0 && idx != 0 && i != 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
b.WriteByte(buf[i])
|
||||
|
||||
}
|
||||
buf = b.Bytes()
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func formatFractional(post, vOrig float64, f Format) []byte {
|
||||
if len(f.Fractional) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw := strconv.AppendFloat(nil, post, 'f', -1, 64)
|
||||
if len(raw) > 2 {
|
||||
raw = raw[2:] // skip the decimal portion (ie. '0.')
|
||||
} else {
|
||||
raw = nil
|
||||
}
|
||||
|
||||
op := make([]byte, 0, len(raw))
|
||||
op = append(op, '.')
|
||||
consumed := 0
|
||||
lforPost:
|
||||
for i := 0; i < len(f.Fractional); i++ {
|
||||
bidx := i
|
||||
ph := f.Fractional[i]
|
||||
switch ph.Type {
|
||||
// '0' consumes a digit or prints a '0' if there is no digit
|
||||
case FmtTypeDigit:
|
||||
if bidx < len(raw) {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
} else {
|
||||
op = append(op, '0')
|
||||
}
|
||||
// '#' consumes a digit or prints nothing
|
||||
case FmtTypeDigitOpt:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
} else {
|
||||
break lforPost
|
||||
}
|
||||
case FmtTypeLiteral:
|
||||
op = append(op, ph.Literal)
|
||||
default:
|
||||
log.Printf("unsupported type in fractional %v", ph)
|
||||
}
|
||||
}
|
||||
// remaining digits are truncated
|
||||
return op
|
||||
}
|
||||
|
||||
func absi64(i int64) int64 {
|
||||
if i < 0 {
|
||||
return -i
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func formatExponential(exp int64, f Format) []byte {
|
||||
if !f.IsExponential || len(f.Exponent) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw := strconv.AppendInt(nil, absi64(exp), 10)
|
||||
|
||||
op := make([]byte, 0, len(raw)+2)
|
||||
op = append(op, 'E')
|
||||
if exp >= 0 {
|
||||
op = append(op, '+')
|
||||
} else {
|
||||
op = append(op, '-')
|
||||
exp *= -1
|
||||
}
|
||||
consumed := 0
|
||||
lexfor:
|
||||
for i := len(f.Exponent) - 1; i >= 0; i-- {
|
||||
bidx := len(raw) - 1 - consumed
|
||||
ph := f.Exponent[i]
|
||||
switch ph.Type {
|
||||
// '0' consumes a digit or prints a '0' if there is no digit
|
||||
case FmtTypeDigit:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
} else {
|
||||
op = append(op, '0')
|
||||
}
|
||||
// '#' consumes a digit or prints nothing
|
||||
case FmtTypeDigitOpt:
|
||||
if bidx >= 0 {
|
||||
op = append(op, raw[bidx])
|
||||
consumed++
|
||||
} else {
|
||||
for j := i; j >= 0; j-- {
|
||||
c := f.Exponent[j]
|
||||
if c.Type == FmtTypeLiteral {
|
||||
op = append(op, c.Literal)
|
||||
}
|
||||
}
|
||||
break lexfor
|
||||
}
|
||||
case FmtTypeLiteral:
|
||||
op = append(op, ph.Literal)
|
||||
default:
|
||||
log.Printf("unsupported type in exp %v", ph)
|
||||
}
|
||||
}
|
||||
// remaining non-consumed digits in the exponent
|
||||
if consumed < len(raw) {
|
||||
op = append(op, raw[len(raw)-consumed-1:consumed-1]...)
|
||||
}
|
||||
|
||||
reverse(op[2:])
|
||||
return op
|
||||
}
|
||||
|
||||
// NumberGeneric formats the number with the generic format which attemps to
|
||||
// mimic Excel's general formatting.
|
||||
func NumberGeneric(v float64) string {
|
||||
@ -440,6 +498,7 @@ func performCarries(b []byte) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
// dDate formats a time with an Excel format date string.
|
||||
func dDate(t time.Time, f string) []byte {
|
||||
ret := []byte{}
|
||||
beg := 0
|
||||
@ -505,6 +564,7 @@ func dDate(t time.Time, f string) []byte {
|
||||
return ret
|
||||
}
|
||||
|
||||
// dTime formats a time with an Excel format time string.
|
||||
func dTime(t time.Time, v float64, f string) []byte {
|
||||
ret := []byte{}
|
||||
beg := 0
|
||||
|
@ -93,7 +93,11 @@ func TestCellFormattingNumber(t *testing.T) {
|
||||
{-4, "#,##0_);[Red](#,##0)", "(4)"},
|
||||
|
||||
// fractions
|
||||
// {1.5, `0/100`, "150/100"},
|
||||
{1.5, `0/100`, "150/100"},
|
||||
{0.5, "0/1000", "500/1000"},
|
||||
{1.25, "0 0/100", "1 25/100"},
|
||||
{0.5, "0/10", "5/10"},
|
||||
{0.25, "0/10", "3/10"},
|
||||
|
||||
// dates & times
|
||||
{42996.6996269676, "d-mmm-yy", "18-Sep-17"},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -68,28 +68,28 @@ import (
|
||||
NFTime = (NFTimeToken | ':')+;
|
||||
cond = '[' any+ ']';
|
||||
main := |*
|
||||
'#,#' => { l.fmt.AddPlaceholder(FmtTypeDigitOptThousands,nil) };
|
||||
'0' => { l.fmt.AddPlaceholder(FmtTypeDigit,nil) };
|
||||
'#' => { l.fmt.AddPlaceholder(FmtTypeDigitOpt,nil) };
|
||||
'#,#' => { l.fmt.AddToken(FmtTypeDigitOptThousands,nil) };
|
||||
'0' => { l.fmt.AddToken(FmtTypeDigit,nil) };
|
||||
'#' => { l.fmt.AddToken(FmtTypeDigitOpt,nil) };
|
||||
'?' => { }; # ignore for now
|
||||
'.' => { l.fmt.AddPlaceholder(FmtTypeDecimal,nil) };
|
||||
',' => { l.fmt.AddPlaceholder(FmtTypeComma,nil) };
|
||||
'%' => { l.fmt.AddPlaceholder(FmtTypePercent,nil) };
|
||||
'$' => { l.fmt.AddPlaceholder(FmtTypeDollar,nil) };
|
||||
'_' => { l.fmt.AddPlaceholder(FmtTypeUnderscore,nil) };
|
||||
'.' => { l.fmt.AddToken(FmtTypeDecimal,nil) };
|
||||
',' => { l.fmt.AddToken(FmtTypeComma,nil) };
|
||||
'%' => { l.fmt.AddToken(FmtTypePercent,nil) };
|
||||
'$' => { l.fmt.AddToken(FmtTypeDollar,nil) };
|
||||
'_' => { l.fmt.AddToken(FmtTypeUnderscore,nil) };
|
||||
';' => { l.nextFmt() };
|
||||
NFGeneral => { l.fmt.isGeneral = true };
|
||||
#NFFraction => {fmt.Println("FRACTION",string(data[ts:te]))};
|
||||
NFFraction => { l.fmt.AddToken(FmtTypeFraction,data[ts:te]) };
|
||||
# we have to keep date/time separate as 'mm' is both minutes and month
|
||||
NFDate => { l.fmt.AddPlaceholder(FmtTypeDate,data[ts:te]) };
|
||||
NFTime => { l.fmt.AddPlaceholder(FmtTypeTime,data[ts:te]) };
|
||||
NFAbsTimeToken => { l.fmt.AddPlaceholder(FmtTypeTime,data[ts:te]) };
|
||||
NFDate => { l.fmt.AddToken(FmtTypeDate,data[ts:te]) };
|
||||
NFTime => { l.fmt.AddToken(FmtTypeTime,data[ts:te]) };
|
||||
NFAbsTimeToken => { l.fmt.AddToken(FmtTypeTime,data[ts:te]) };
|
||||
NFPartExponential => { l.fmt.IsExponential = true };
|
||||
cond => {}; # ignoring
|
||||
# escaped
|
||||
'\\' any => { l.fmt.AddPlaceholder(FmtTypeLiteral,data[ts+1:te]) };
|
||||
any => { l.fmt.AddPlaceholder(FmtTypeLiteral,data[ts:te]) };
|
||||
dquote ( not_dquote | dquote dquote)* dquote => { l.fmt.AddPlaceholder(FmtTypeLiteral,data[ts+1:te-1])};
|
||||
'\\' any => { l.fmt.AddToken(FmtTypeLiteral,data[ts+1:te]) };
|
||||
any => { l.fmt.AddToken(FmtTypeLiteral,data[ts:te]) };
|
||||
dquote ( not_dquote | dquote dquote)* dquote => { l.fmt.AddToken(FmtTypeLiteral,data[ts+1:te-1])};
|
||||
|
||||
*|;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user