Support format cell value with fraction number format code

- Fix delete incorrect image files when picture deleting pictures
- Update the unit test and dependencies modules
This commit is contained in:
xuri 2023-10-10 00:04:10 +08:00
parent 87a00e4f7e
commit d133dc12d7
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
7 changed files with 127 additions and 33 deletions

View File

@ -741,10 +741,10 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
idxTbl := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
expected := [][]string{
{"37947.7500001", "37948", "37947.75", "37,948", "37,947.75", "3794775%", "3794775.00%", "3.79E+04", "37947.7500001", "37947.7500001", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 PM", "6:00:00 PM", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", "37,948", "$37,948", "37,947.75", "$37,947.75", "00:00", "910746:00:00", "00:00.0", "37947.7500001", "37947.7500001"},
{"-37947.7500001", "-37948", "-37947.75", "-37,948", "-37,947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", "(37,948)", "$(37,948)", "(37,947.75)", "$(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001"},
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0.007", "0.007", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "12:10 AM", "12:10:05 AM", "00:10", "00:10:05", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0", "$0", "0.01", "$0.01", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"},
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2.1", "2.1", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 AM", "2:24:00 AM", "02:24", "02:24:00", "1/1/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", "2", "$2", "2.10", "$2.10", "24:00", "50:24:00", "24:00.0", "2.1", "2.1"},
{"37947.7500001", "37948", "37947.75", "37,948", "37,947.75", "3794775%", "3794775.00%", "3.79E+04", "37947 3/4", "37947 3/4", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 PM", "6:00:00 PM", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", "37,948", "$37,948", "37,947.75", "$37,947.75", "00:00", "910746:00:00", "00:00.0", "37947.7500001", "37947.7500001"},
{"-37947.7500001", "-37948", "-37947.75", "-37,948", "-37,947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947 3/4", "-37947 3/4", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", "(37,948)", "$(37,948)", "(37,947.75)", "$(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001"},
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0 ", "0 ", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "12:10 AM", "12:10:05 AM", "00:10", "00:10:05", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0", "$0", "0.01", "$0.01", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"},
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2 1/9", "2 1/10", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 AM", "2:24:00 AM", "02:24", "02:24:00", "1/1/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", "2", "$2", "2.10", "$2.10", "24:00", "50:24:00", "24:00.0", "2.1", "2.1"},
{"String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String"},
}

6
go.mod
View File

@ -8,9 +8,9 @@ require (
github.com/richardlehane/msoleps v1.0.3 // indirect
github.com/stretchr/testify v1.8.0
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca
github.com/xuri/nfp v0.0.0-20230918160701-e5a3f5b24785
golang.org/x/crypto v0.13.0
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05
golang.org/x/crypto v0.14.0
golang.org/x/image v0.11.0
golang.org/x/net v0.15.0
golang.org/x/net v0.16.0
golang.org/x/text v0.13.0
)

16
go.sum
View File

@ -17,13 +17,13 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/nfp v0.0.0-20230918160701-e5a3f5b24785 h1:FG9hcK7lhf3w/Y2NRUKy/mopsH0Oy6P1rib1KWXAie0=
github.com/xuri/nfp v0.0.0-20230918160701-e5a3f5b24785/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4=
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -33,8 +33,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -45,12 +45,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

25
lib.go
View File

@ -18,6 +18,7 @@ import (
"encoding/xml"
"fmt"
"io"
"math"
"math/big"
"os"
"regexp"
@ -825,6 +826,30 @@ func bstrMarshal(s string) (result string) {
return result
}
// newRat converts decimals to rational fractions with the required precision.
func newRat(n float64, iterations int64, prec float64) *big.Rat {
x := int64(math.Floor(n))
y := n - float64(x)
rat := continuedFraction(y, 1, iterations, prec)
return rat.Add(rat, new(big.Rat).SetInt64(x))
}
// continuedFraction returns rational from decimal with the continued fraction
// algorithm.
func continuedFraction(n float64, i int64, limit int64, prec float64) *big.Rat {
if i >= limit || n <= prec {
return big.NewRat(0, 1)
}
inverted := 1 / n
y := int64(math.Floor(inverted))
x := inverted - float64(y)
ratY := new(big.Rat).SetInt64(y)
ratNext := continuedFraction(x, i+1, limit, prec)
res := ratY.Add(ratY, ratNext)
res = res.Inv(res)
return res
}
// Stack defined an abstract data type that serves as a collection of elements.
type Stack struct {
list *list.List

View File

@ -33,18 +33,18 @@ type languageInfo struct {
// numberFormat directly maps the number format parser runtime required
// fields.
type numberFormat struct {
opts *Options
cellType CellType
section []nfp.Section
t time.Time
sectionIdx int
date1904, isNumeric, hours, seconds, useMillisecond, useGannen bool
number float64
ap, localCode, result, value, valueSectionType string
switchArgument, currencyString string
fracHolder, fracPadding, intHolder, intPadding, expBaseLen int
percent int
useCommaSep, usePointer, usePositive, useScientificNotation bool
opts *Options
cellType CellType
section []nfp.Section
t time.Time
sectionIdx int
date1904, isNumeric, hours, seconds, useMillisecond, useGannen bool
number float64
ap, localCode, result, value, valueSectionType string
switchArgument, currencyString string
fracHolder, fracPadding, intHolder, intPadding, expBaseLen int
percent int
useCommaSep, useFraction, usePointer, usePositive, useScientificNotation bool
}
// CultureName is the type of supported language country codes types for apply
@ -688,8 +688,11 @@ var (
nfp.TokenTypeCurrencyLanguage,
nfp.TokenTypeDateTimes,
nfp.TokenTypeDecimalPoint,
nfp.TokenTypeDenominator,
nfp.TokenTypeDigitalPlaceHolder,
nfp.TokenTypeElapsedDateTimes,
nfp.TokenTypeExponential,
nfp.TokenTypeFraction,
nfp.TokenTypeGeneral,
nfp.TokenTypeHashPlaceHolder,
nfp.TokenTypeLiteral,
@ -702,7 +705,10 @@ var (
}
// supportedNumberTokenTypes list the supported number token types.
supportedNumberTokenTypes = []string{
nfp.TokenTypeDenominator,
nfp.TokenTypeDigitalPlaceHolder,
nfp.TokenTypeExponential,
nfp.TokenTypeFraction,
nfp.TokenTypeHashPlaceHolder,
nfp.TokenTypePercent,
nfp.TokenTypeZeroPlaceHolder,
@ -4775,6 +4781,9 @@ func (nf *numberFormat) getNumberFmtConf() {
if token.TType == nfp.TokenTypeDecimalPoint {
nf.usePointer = true
}
if token.TType == nfp.TokenTypeFraction {
nf.useFraction = true
}
if token.TType == nfp.TokenTypeSwitchArgument {
nf.switchArgument = token.TValue
}
@ -4795,8 +4804,11 @@ func (nf *numberFormat) getNumberFmtConf() {
// printNumberLiteral apply literal tokens for the pre-formatted text.
func (nf *numberFormat) printNumberLiteral(text string) string {
var result string
var useLiteral, usePlaceHolder bool
var (
result string
frac float64
useFraction, useLiteral, usePlaceHolder bool
)
if nf.usePositive {
result += "-"
}
@ -4822,10 +4834,41 @@ func (nf *numberFormat) printNumberLiteral(text string) string {
result += text
}
}
if token.TType == nfp.TokenTypeFraction {
_, frac = math.Modf(nf.number)
frac, useFraction = math.Abs(frac), true
}
if useFraction {
result += nf.fractionHandler(frac, token)
}
}
return nf.printSwitchArgument(result)
}
// fractionHandler handling fraction number format expression for positive and
// negative numeric.
func (nf *numberFormat) fractionHandler(frac float64, token nfp.Token) string {
var rat, result string
if token.TType == nfp.TokenTypeDigitalPlaceHolder {
fracPlaceHolder := len(token.TValue)
for i := 0; i < 5000; i++ {
if r := newRat(frac, int64(i), 0); len(r.Denom().String()) <= fracPlaceHolder {
if rat = r.String(); strings.HasPrefix(rat, "0/") {
rat = strings.Repeat(" ", 3)
}
continue
}
break
}
result += rat
}
if token.TType == nfp.TokenTypeDenominator {
denom, _ := strconv.ParseFloat(token.TValue, 64)
result += fmt.Sprintf("%d/%d", int(math.Round(frac*denom)), int(math.Round(denom)))
}
return result
}
// printCommaSep format number with thousands separator.
func printCommaSep(text string) string {
var (
@ -4929,6 +4972,9 @@ func (nf *numberFormat) numberHandler() string {
if nf.percent > 0 {
num *= math.Pow(100, float64(nf.percent))
}
if nf.useFraction {
num = math.Floor(math.Abs(num))
}
if result = fmt.Sprintf(fmtCode, math.Abs(num)); nf.useCommaSep {
result = printCommaSep(result)
}

View File

@ -3543,6 +3543,16 @@ func TestNumFmt(t *testing.T) {
{"1.234E-16", "0.000000000000000000", "0.000000000000000123"},
{"1.234E-16", "0.000000000000000000%", "0.000000000000012340%"},
{"1.234E-16", "0.000000000000000000%%%%", "0.000000000000012340%"},
{"-123.4567", "# ?/?", "-123 1/2"},
{"123.4567", "# ??/??", "123 37/81"},
{"123.4567", "#\\ ???/???", "123 58/127"},
{"123.4567", "#\\ ?/2", "123 1/2"},
{"123.4567", "#\\ ?/4", "123 2/4"},
{"123.4567", "#\\ ?/8", "123 4/8"},
{"123.4567", "#\\ ?/16", "123 7/16"},
{"123.4567", "#\\ ?/10", "123 5/10"},
{"-123.4567", "#\\ ?/100", "-123 46/100"},
{"123.4567", "#\\ ?/1000", "123 457/1000"},
{"1234.5678", "[$$-409]#,##0.00", "$1,234.57"},
// Unsupported number format
{"37947.7500001", "0.00000000E+000", "37947.7500001"},

View File

@ -231,7 +231,18 @@ func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error {
drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
mediaStr := ".." + strings.TrimPrefix(f.addMedia(pic.File, ext), "xl")
drawingRID := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType)
var drawingRID int
if rels, _ := f.relsReader(drawingRels); rels != nil {
for _, rel := range rels.Relationships {
if rel.Type == SourceRelationshipImage && rel.Target == mediaStr {
drawingRID, _ = strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
break
}
}
}
if drawingRID == 0 {
drawingRID = f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType)
}
// Add picture with hyperlink.
if options.Hyperlink != "" && options.HyperlinkType != "" {
if options.HyperlinkType == "External" {
@ -494,8 +505,8 @@ func (f *File) DeletePicture(sheet, cell string) error {
return err
}
var used bool
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/drawings/_rels/") {
checkPicRef := func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/drawings/_rels/drawing") {
r, err := f.relsReader(k.(string))
if err != nil {
return true
@ -508,7 +519,9 @@ func (f *File) DeletePicture(sheet, cell string) error {
}
}
return true
})
}
f.Relationships.Range(checkPicRef)
f.Pkg.Range(checkPicRef)
if !used {
f.Pkg.Delete(strings.Replace(rels.Target, "../", "xl/", -1))
}