diff --git a/go.mod b/go.mod index c7753c72..0a358667 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/smallfish-root/eventhorizon v1.0.1 github.com/smallfish-root/gin-http-logger v1.1.1-0.20220522073633-0e64b331f592 + github.com/xuri/excelize/v2 v2.6.0 go.mongodb.org/mongo-driver v1.7.1 google.golang.org/genproto v0.0.0-20210813162853-db860fec028c google.golang.org/grpc v1.39.1 diff --git a/go.sum b/go.sum index a3d13756..e631aa43 100644 --- a/go.sum +++ b/go.sum @@ -309,6 +309,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= @@ -347,6 +349,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -392,6 +398,12 @@ github.com/xdg/scram v1.0.3/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49 github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg/stringprep v1.0.3 h1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4= github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8 h1:3X7aE0iLKJ5j+tz58BpvIZkXNV7Yq4jC93Z/rbN2Fxk= +github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.6.0 h1:m/aXAzSAqxgt74Nfd+sNzpzVKhTGl7+S9nbG4A57mF4= +github.com/xuri/excelize/v2 v2.6.0/go.mod h1:Q1YetlHesXEKwGFfeJn7PfEZz2IvHb6wdOeYjBxVcVs= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -428,8 +440,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM= +golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -446,6 +459,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -508,8 +523,10 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210427231257-85d9c07bbe3a/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c= +golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -592,9 +609,11 @@ golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -602,8 +621,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/vendor/github.com/mohae/deepcopy/.gitignore b/vendor/github.com/mohae/deepcopy/.gitignore new file mode 100644 index 00000000..5846dd15 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/.gitignore @@ -0,0 +1,26 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*~ +*.out +*.log diff --git a/vendor/github.com/mohae/deepcopy/.travis.yml b/vendor/github.com/mohae/deepcopy/.travis.yml new file mode 100644 index 00000000..fd47a8cf --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - tip + +matrix: + allow_failures: + - go: tip + +script: + - go test ./... diff --git a/vendor/github.com/mohae/deepcopy/LICENSE b/vendor/github.com/mohae/deepcopy/LICENSE new file mode 100644 index 00000000..419673f0 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Joel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mohae/deepcopy/README.md b/vendor/github.com/mohae/deepcopy/README.md new file mode 100644 index 00000000..f8184188 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/README.md @@ -0,0 +1,8 @@ +deepCopy +======== +[![GoDoc](https://godoc.org/github.com/mohae/deepcopy?status.svg)](https://godoc.org/github.com/mohae/deepcopy)[![Build Status](https://travis-ci.org/mohae/deepcopy.png)](https://travis-ci.org/mohae/deepcopy) + +DeepCopy makes deep copies of things: unexported field values are not copied. + +## Usage + cpy := deepcopy.Copy(orig) diff --git a/vendor/github.com/mohae/deepcopy/deepcopy.go b/vendor/github.com/mohae/deepcopy/deepcopy.go new file mode 100644 index 00000000..ba763ad0 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/deepcopy.go @@ -0,0 +1,125 @@ +// deepcopy makes deep copies of things. A standard copy will copy the +// pointers: deep copy copies the values pointed to. Unexported field +// values are not copied. +// +// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved. +// License: MIT, for more details check the included LICENSE file. +package deepcopy + +import ( + "reflect" + "time" +) + +// Interface for delegating copy process to type +type Interface interface { + DeepCopy() interface{} +} + +// Iface is an alias to Copy; this exists for backwards compatibility reasons. +func Iface(iface interface{}) interface{} { + return Copy(iface) +} + +// Copy creates a deep copy of whatever is passed to it and returns the copy +// in an interface{}. The returned value will need to be asserted to the +// correct type. +func Copy(src interface{}) interface{} { + if src == nil { + return nil + } + + // Make the interface a reflect.Value + original := reflect.ValueOf(src) + + // Make a copy of the same type as the original. + cpy := reflect.New(original.Type()).Elem() + + // Recursively copy the original. + copyRecursive(original, cpy) + + // Return the copy as an interface. + return cpy.Interface() +} + +// copyRecursive does the actual copying of the interface. It currently has +// limited support for what it can handle. Add as needed. +func copyRecursive(original, cpy reflect.Value) { + // check for implement deepcopy.Interface + if original.CanInterface() { + if copier, ok := original.Interface().(Interface); ok { + cpy.Set(reflect.ValueOf(copier.DeepCopy())) + return + } + } + + // handle according to original's Kind + switch original.Kind() { + case reflect.Ptr: + // Get the actual value being pointed to. + originalValue := original.Elem() + + // if it isn't valid, return. + if !originalValue.IsValid() { + return + } + cpy.Set(reflect.New(originalValue.Type())) + copyRecursive(originalValue, cpy.Elem()) + + case reflect.Interface: + // If this is a nil, don't do anything + if original.IsNil() { + return + } + // Get the value for the interface, not the pointer. + originalValue := original.Elem() + + // Get the value by calling Elem(). + copyValue := reflect.New(originalValue.Type()).Elem() + copyRecursive(originalValue, copyValue) + cpy.Set(copyValue) + + case reflect.Struct: + t, ok := original.Interface().(time.Time) + if ok { + cpy.Set(reflect.ValueOf(t)) + return + } + // Go through each field of the struct and copy it. + for i := 0; i < original.NumField(); i++ { + // The Type's StructField for a given field is checked to see if StructField.PkgPath + // is set to determine if the field is exported or not because CanSet() returns false + // for settable fields. I'm not sure why. -mohae + if original.Type().Field(i).PkgPath != "" { + continue + } + copyRecursive(original.Field(i), cpy.Field(i)) + } + + case reflect.Slice: + if original.IsNil() { + return + } + // Make a new slice and copy each element. + cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap())) + for i := 0; i < original.Len(); i++ { + copyRecursive(original.Index(i), cpy.Index(i)) + } + + case reflect.Map: + if original.IsNil() { + return + } + cpy.Set(reflect.MakeMap(original.Type())) + for _, key := range original.MapKeys() { + originalValue := original.MapIndex(key) + copyValue := reflect.New(originalValue.Type()).Elem() + copyRecursive(originalValue, copyValue) + copyKey := Copy(key.Interface()) + cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue) + } + + default: + cpy.Set(original) + } +} diff --git a/vendor/github.com/richardlehane/mscfb/.travis.yml b/vendor/github.com/richardlehane/mscfb/.travis.yml new file mode 100644 index 00000000..c189ccf8 --- /dev/null +++ b/vendor/github.com/richardlehane/mscfb/.travis.yml @@ -0,0 +1,4 @@ +language: go +go: + - stable + - tip \ No newline at end of file diff --git a/vendor/github.com/richardlehane/mscfb/LICENSE.txt b/vendor/github.com/richardlehane/mscfb/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/richardlehane/mscfb/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/richardlehane/mscfb/README.md b/vendor/github.com/richardlehane/mscfb/README.md new file mode 100644 index 00000000..724db9d6 --- /dev/null +++ b/vendor/github.com/richardlehane/mscfb/README.md @@ -0,0 +1,24 @@ +A reader for Microsoft's Compound File Binary File Format. + +Example usage: + + file, _ := os.Open("test/test.doc") + defer file.Close() + doc, err := mscfb.New(file) + if err != nil { + log.Fatal(err) + } + for entry, err := doc.Next(); err == nil; entry, err = doc.Next() { + buf := make([]byte, 512) + i, _ := doc.Read(buf) + if i > 0 { + fmt.Println(buf[:i]) + } + fmt.Println(entry.Name) + } + +The Compound File Binary File Format is also known as the Object Linking and Embedding (OLE) or Component Object Model (COM) format and was used by early MS software such as MS Office. See [http://msdn.microsoft.com/en-us/library/dd942138.aspx](http://msdn.microsoft.com/en-us/library/dd942138.aspx) for more details + +Install with `go get github.com/richardlehane/mscfb` + +[![Build Status](https://travis-ci.org/richardlehane/mscfb.png?branch=master)](https://travis-ci.org/richardlehane/mscfb) \ No newline at end of file diff --git a/vendor/github.com/richardlehane/mscfb/file.go b/vendor/github.com/richardlehane/mscfb/file.go new file mode 100644 index 00000000..f9825a7e --- /dev/null +++ b/vendor/github.com/richardlehane/mscfb/file.go @@ -0,0 +1,534 @@ +// Copyright 2013 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mscfb + +import ( + "encoding/binary" + "io" + "os" + "time" + "unicode" + "unicode/utf16" + + "github.com/richardlehane/msoleps/types" +) + +//objectType types +const ( + unknown uint8 = 0x0 // this means unallocated - typically zeroed dir entries + storage uint8 = 0x1 // this means dir + stream uint8 = 0x2 // this means file + rootStorage uint8 = 0x5 // this means root +) + +// color flags +const ( + red uint8 = 0x0 + black uint8 = 0x1 +) + +const lenDirEntry int = 64 + 4*4 + 16 + 4 + 8*2 + 4 + 8 + +type directoryEntryFields struct { + rawName [32]uint16 //64 bytes, unicode string encoded in UTF-16. If root, "Root Entry\0" w + nameLength uint16 //2 bytes + objectType uint8 //1 byte Must be one of the types specified above + color uint8 //1 byte Must be 0x00 RED or 0x01 BLACK + leftSibID uint32 //4 bytes, Dir? Stream ID of left sibling, if none set to NOSTREAM + rightSibID uint32 //4 bytes, Dir? Stream ID of right sibling, if none set to NOSTREAM + childID uint32 //4 bytes, Dir? Stream ID of child object, if none set to NOSTREAM + clsid types.Guid // Contains an object class GUID (must be set to zeroes for stream object) + stateBits [4]byte // user-defined flags for storage object + create types.FileTime // Windows FILETIME structure + modify types.FileTime // Windows FILETIME structure + startingSectorLoc uint32 // if a stream object, first sector location. If root, first sector of ministream + streamSize [8]byte // if a stream, size of user-defined data. If root, size of ministream +} + +func makeDirEntry(b []byte) *directoryEntryFields { + d := &directoryEntryFields{} + for i := range d.rawName { + d.rawName[i] = binary.LittleEndian.Uint16(b[i*2 : i*2+2]) + } + d.nameLength = binary.LittleEndian.Uint16(b[64:66]) + d.objectType = uint8(b[66]) + d.color = uint8(b[67]) + d.leftSibID = binary.LittleEndian.Uint32(b[68:72]) + d.rightSibID = binary.LittleEndian.Uint32(b[72:76]) + d.childID = binary.LittleEndian.Uint32(b[76:80]) + d.clsid = types.MustGuid(b[80:96]) + copy(d.stateBits[:], b[96:100]) + d.create = types.MustFileTime(b[100:108]) + d.modify = types.MustFileTime(b[108:116]) + d.startingSectorLoc = binary.LittleEndian.Uint32(b[116:120]) + copy(d.streamSize[:], b[120:128]) + return d +} + +func (r *Reader) setDirEntries() error { + c := 20 + if r.header.numDirectorySectors > 0 { + c = int(r.header.numDirectorySectors) + } + de := make([]*File, 0, c) + cycles := make(map[uint32]bool) + num := int(r.sectorSize / 128) + sn := r.header.directorySectorLoc + for sn != endOfChain { + buf, err := r.readAt(fileOffset(r.sectorSize, sn), int(r.sectorSize)) + if err != nil { + return Error{ErrRead, "directory entries read error (" + err.Error() + ")", fileOffset(r.sectorSize, sn)} + } + for i := 0; i < num; i++ { + f := &File{r: r} + f.directoryEntryFields = makeDirEntry(buf[i*128:]) + fixFile(r.header.majorVersion, f) + f.curSector = f.startingSectorLoc + de = append(de, f) + } + nsn, err := r.findNext(sn, false) + if err != nil { + return Error{ErrRead, "directory entries error finding sector (" + err.Error() + ")", int64(nsn)} + } + if nsn <= sn { + if nsn == sn || cycles[nsn] { + return Error{ErrRead, "directory entries sector cycle", int64(nsn)} + } + cycles[nsn] = true + } + sn = nsn + } + r.direntries = de + return nil +} + +func fixFile(v uint16, f *File) { + fixName(f) + if f.objectType != stream { + return + } + // if the MSCFB major version is 4, then this can be a uint64 otherwise is a uint32 and the least signficant bits can contain junk + if v > 3 { + f.Size = int64(binary.LittleEndian.Uint64(f.streamSize[:])) + } else { + f.Size = int64(binary.LittleEndian.Uint32(f.streamSize[:4])) + } +} + +func fixName(f *File) { + // From the spec: + // "The length [name] MUST be a multiple of 2, and include the terminating null character in the count. + // This length MUST NOT exceed 64, the maximum size of the Directory Entry Name field." + if f.nameLength < 4 || f.nameLength > 64 { + return + } + nlen := int(f.nameLength/2 - 1) + f.Initial = f.rawName[0] + var slen int + if !unicode.IsPrint(rune(f.Initial)) { + slen = 1 + } + f.Name = string(utf16.Decode(f.rawName[slen:nlen])) +} + +func (r *Reader) traverse() error { + r.File = make([]*File, 0, len(r.direntries)) + var ( + recurse func(int, []string) + err error + counter int + ) + recurse = func(i int, path []string) { + // prevent cycles, number of recurse calls can't exceed number of directory entries + counter++ + if counter > len(r.direntries) { + err = Error{ErrTraverse, "traversal counter overflow", int64(i)} + return + } + if i < 0 || i >= len(r.direntries) { + err = Error{ErrTraverse, "illegal traversal index", int64(i)} + return + } + file := r.direntries[i] + if file.leftSibID != noStream { + recurse(int(file.leftSibID), path) + } + r.File = append(r.File, file) + file.Path = path + if file.childID != noStream { + if i > 0 { + recurse(int(file.childID), append(path, file.Name)) + } else { + recurse(int(file.childID), path) + } + } + if file.rightSibID != noStream { + recurse(int(file.rightSibID), path) + } + return + } + recurse(0, []string{}) + return err +} + +// File represents a MSCFB directory entry +type File struct { + Name string // stream or directory name + Initial uint16 // the first character in the name (identifies special streams such as MSOLEPS property sets) + Path []string // file path + Size int64 // size of stream + i int64 // bytes read + curSector uint32 // next sector for Read | Write + rem int64 // offset in current sector remaining previous Read | Write + *directoryEntryFields + r *Reader +} + +type fileInfo struct{ *File } + +func (fi fileInfo) Name() string { return fi.File.Name } +func (fi fileInfo) Size() int64 { + if fi.objectType != stream { + return 0 + } + return fi.File.Size +} +func (fi fileInfo) IsDir() bool { return fi.mode().IsDir() } +func (fi fileInfo) ModTime() time.Time { return fi.Modified() } +func (fi fileInfo) Mode() os.FileMode { return fi.File.mode() } +func (fi fileInfo) Sys() interface{} { return nil } + +func (f *File) mode() os.FileMode { + if f.objectType != stream { + return os.ModeDir | 0777 + } + return 0666 +} + +// FileInfo for this directory entry. Useful for IsDir() (whether a directory entry is a stream (file) or a storage object (dir)) +func (f *File) FileInfo() os.FileInfo { + return fileInfo{f} +} + +// ID returns this directory entry's CLSID field +func (f *File) ID() string { + return f.clsid.String() +} + +// Created returns this directory entry's created field +func (f *File) Created() time.Time { + return f.create.Time() +} + +// Created returns this directory entry's modified field +func (f *File) Modified() time.Time { + return f.modify.Time() +} + +// Read this directory entry +// Returns 0, io.EOF if no stream is available (i.e. for a storage object) +func (f *File) Read(b []byte) (int, error) { + if f.Size < 1 || f.i >= f.Size { + return 0, io.EOF + } + sz := len(b) + if int64(sz) > f.Size-f.i { + sz = int(f.Size - f.i) + } + // get sectors and lengths for reads + str, err := f.stream(sz) + if err != nil { + return 0, err + } + // now read + var idx, i int + for _, v := range str { + jdx := idx + int(v[1]) + if jdx < idx || jdx > sz { + return 0, Error{ErrRead, "bad read length", int64(jdx)} + } + j, err := f.r.ra.ReadAt(b[idx:jdx], v[0]) + i = i + j + if err != nil { + f.i += int64(i) + return i, Error{ErrRead, "underlying reader fail (" + err.Error() + ")", int64(idx)} + } + idx = jdx + } + f.i += int64(i) + if i != sz { + err = Error{ErrRead, "bytes read do not match expected read size", int64(i)} + } else if i < len(b) { + err = io.EOF + } + return i, err +} + +// Write to this directory entry +// Depends on the io.ReaderAt supplied to mscfb.New() being a WriterAt too +// Returns 0, io.EOF if no stream is available (i.e. for a storage object) +func (f *File) Write(b []byte) (int, error) { + if f.Size < 1 || f.i >= f.Size { + return 0, io.EOF + } + if f.r.wa == nil { + wa, ok := f.r.ra.(io.WriterAt) + if !ok { + return 0, Error{ErrWrite, "mscfb.New must be given ReaderAt convertible to a io.WriterAt in order to write", 0} + } + f.r.wa = wa + } + sz := len(b) + if int64(sz) > f.Size-f.i { + sz = int(f.Size - f.i) + } + // get sectors and lengths for writes + str, err := f.stream(sz) + if err != nil { + return 0, err + } + // now read + var idx, i int + for _, v := range str { + jdx := idx + int(v[1]) + if jdx < idx || jdx > sz { + return 0, Error{ErrWrite, "bad write length", int64(jdx)} + } + j, err := f.r.wa.WriteAt(b[idx:jdx], v[0]) + i = i + j + if err != nil { + f.i += int64(i) + return i, Error{ErrWrite, "underlying writer fail (" + err.Error() + ")", int64(idx)} + } + idx = jdx + } + f.i += int64(i) + if i != sz { + err = Error{ErrWrite, "bytes written do not match expected write size", int64(i)} + } else if i < len(b) { + err = io.EOF + } + return i, err +} + +// ReadAt reads p bytes at offset off from start of file. Does not affect seek place for other reads/writes. +func (f *File) ReadAt(p []byte, off int64) (n int, err error) { + // memorize place + mi, mrem, mcur := f.i, f.rem, f.curSector + _, err = f.Seek(off, 0) + if err == nil { + n, err = f.Read(p) + } + f.i, f.rem, f.curSector = mi, mrem, mcur + return n, err +} + +// WriteAt reads p bytes at offset off from start of file. Does not affect seek place for other reads/writes. +func (f *File) WriteAt(p []byte, off int64) (n int, err error) { + // memorize place + mi, mrem, mcur := f.i, f.rem, f.curSector + _, err = f.Seek(off, 0) + if err == nil { + n, err = f.Write(p) + } + f.i, f.rem, f.curSector = mi, mrem, mcur + return n, err +} + +// Seek sets the offset for the next Read or Write to offset, interpreted according to whence: 0 means relative to the +// start of the file, 1 means relative to the current offset, and 2 means relative to the end. Seek returns the new +// offset relative to the start of the file and an error, if any. +func (f *File) Seek(offset int64, whence int) (int64, error) { + var abs int64 + switch whence { + default: + return 0, Error{ErrSeek, "invalid whence", int64(whence)} + case 0: + abs = offset + case 1: + abs = f.i + offset + case 2: + abs = f.Size - offset + } + switch { + case abs < 0: + return f.i, Error{ErrSeek, "can't seek before start of File", abs} + case abs >= f.Size: + return f.i, Error{ErrSeek, "can't seek past File length", abs} + case abs == f.i: + return abs, nil + case abs > f.i: + t := f.i + f.i = abs + return f.i, f.seek(abs - t) + } + if f.rem >= f.i-abs { + f.rem = f.rem - (f.i - abs) + f.i = abs + return f.i, nil + } + f.rem = 0 + f.curSector = f.startingSectorLoc + f.i = abs + return f.i, f.seek(abs) +} + +func (f *File) seek(sz int64) error { + // calculate ministream and sector size + var mini bool + var ss int64 + if f.Size < miniStreamCutoffSize { + mini = true + ss = 64 + } else { + ss = int64(f.r.sectorSize) + } + + var j int64 + var err error + // if we have a remainder in the current sector, use it first + if f.rem > 0 { + if ss-f.rem <= sz { + f.curSector, err = f.r.findNext(f.curSector, mini) + if err != nil { + return err + } + j += ss - f.rem + f.rem = 0 + if j == sz { + return nil + } + } else { + f.rem += sz + return nil + } + if f.curSector == endOfChain { + return Error{ErrRead, "unexpected early end of chain", int64(f.curSector)} + } + } + + for { + // check if we are at the last sector + if sz-j < ss { + f.rem = sz - j + return nil + } else { + j += ss + f.curSector, err = f.r.findNext(f.curSector, mini) + if err != nil { + return err + } + // we might be at the last sector if there is no remainder, if so can return + if j == sz { + return nil + } + } + } +} + +// return offsets and lengths for read or write +func (f *File) stream(sz int) ([][2]int64, error) { + // calculate ministream, cap for sector slice, and sector size + var mini bool + var l int + var ss int64 + if f.Size < miniStreamCutoffSize { + mini = true + l = sz/64 + 2 + ss = 64 + } else { + l = sz/int(f.r.sectorSize) + 2 + ss = int64(f.r.sectorSize) + } + + sectors := make([][2]int64, 0, l) + var i, j int + + // if we have a remainder from a previous read, use it first + if f.rem > 0 { + offset, err := f.r.getOffset(f.curSector, mini) + if err != nil { + return nil, err + } + if ss-f.rem >= int64(sz) { + sectors = append(sectors, [2]int64{offset + f.rem, int64(sz)}) + } else { + sectors = append(sectors, [2]int64{offset + f.rem, ss - f.rem}) + } + if ss-f.rem <= int64(sz) { + f.curSector, err = f.r.findNext(f.curSector, mini) + if err != nil { + return nil, err + } + j += int(ss - f.rem) + f.rem = 0 + } else { + f.rem += int64(sz) + } + if sectors[0][1] == int64(sz) { + return sectors, nil + } + if f.curSector == endOfChain { + return nil, Error{ErrRead, "unexpected early end of chain", int64(f.curSector)} + } + i++ + } + + for { + // emergency brake! + if i >= cap(sectors) { + return nil, Error{ErrRead, "index overruns sector length", int64(i)} + } + // grab the next offset + offset, err := f.r.getOffset(f.curSector, mini) + if err != nil { + return nil, err + } + // check if we are at the last sector + if sz-j < int(ss) { + sectors = append(sectors, [2]int64{offset, int64(sz - j)}) + f.rem = int64(sz - j) + return compressChain(sectors), nil + } else { + sectors = append(sectors, [2]int64{offset, ss}) + j += int(ss) + f.curSector, err = f.r.findNext(f.curSector, mini) + if err != nil { + return nil, err + } + // we might be at the last sector if there is no remainder, if so can return + if j == sz { + return compressChain(sectors), nil + } + } + i++ + } +} + +func compressChain(locs [][2]int64) [][2]int64 { + l := len(locs) + for i, x := 0, 0; i < l && x+1 < len(locs); i++ { + if locs[x][0]+locs[x][1] == locs[x+1][0] { + locs[x][1] = locs[x][1] + locs[x+1][1] + for j := range locs[x+1 : len(locs)-1] { + locs[x+1+j] = locs[j+x+2] + } + locs = locs[:len(locs)-1] + } else { + x += 1 + } + } + return locs +} diff --git a/vendor/github.com/richardlehane/mscfb/fuzz.go b/vendor/github.com/richardlehane/mscfb/fuzz.go new file mode 100644 index 00000000..334b5173 --- /dev/null +++ b/vendor/github.com/richardlehane/mscfb/fuzz.go @@ -0,0 +1,33 @@ +// +build gofuzz + +// fuzzing with https://github.com/dvyukov/go-fuzz +package mscfb + +import ( + "bytes" + "io" +) + +func Fuzz(data []byte) int { + doc, err := New(bytes.NewReader(data)) + if err != nil { + if doc != nil { + panic("doc != nil on error " + err.Error()) + } + return 0 + } + buf := &bytes.Buffer{} + for entry, err := doc.Next(); ; entry, err = doc.Next() { + if err != nil { + if err == io.EOF { + return 1 + } + if entry != nil { + panic("entry != nil on error " + err.Error()) + } + } + buf.Reset() + buf.ReadFrom(entry) + } + return 1 +} diff --git a/vendor/github.com/richardlehane/mscfb/go.mod b/vendor/github.com/richardlehane/mscfb/go.mod new file mode 100644 index 00000000..866e6d7b --- /dev/null +++ b/vendor/github.com/richardlehane/mscfb/go.mod @@ -0,0 +1,3 @@ +module github.com/richardlehane/mscfb + +require github.com/richardlehane/msoleps v1.0.1 diff --git a/vendor/github.com/richardlehane/mscfb/go.sum b/vendor/github.com/richardlehane/mscfb/go.sum new file mode 100644 index 00000000..8606172d --- /dev/null +++ b/vendor/github.com/richardlehane/mscfb/go.sum @@ -0,0 +1,4 @@ +github.com/richardlehane/msoleps v1.0.0 h1:43jYNcb7QTmsVrYW7JL5juc780S/2zZcGnseW6PL09A= +github.com/richardlehane/msoleps v1.0.0/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= diff --git a/vendor/github.com/richardlehane/mscfb/mscfb.go b/vendor/github.com/richardlehane/mscfb/mscfb.go new file mode 100644 index 00000000..09e77b03 --- /dev/null +++ b/vendor/github.com/richardlehane/mscfb/mscfb.go @@ -0,0 +1,396 @@ +// Copyright 2013 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package mscfb implements a reader for Microsoft's Compound File Binary File Format (http://msdn.microsoft.com/en-us/library/dd942138.aspx). +// +// The Compound File Binary File Format is also known as the Object Linking and Embedding (OLE) or Component Object Model (COM) format and was used by many +// early MS software such as MS Office. +// +// Example: +// file, _ := os.Open("test/test.doc") +// defer file.Close() +// doc, err := mscfb.New(file) +// if err != nil { +// log.Fatal(err) +// } +// for entry, err := doc.Next(); err == nil; entry, err = doc.Next() { +// buf := make([]byte, 512) +// i, _ := entry.Read(buf) +// if i > 0 { +// fmt.Println(buf[:i]) +// } +// fmt.Println(entry.Name) +// } +package mscfb + +import ( + "encoding/binary" + "io" + "strconv" + "time" +) + +func fileOffset(ss, sn uint32) int64 { + return int64((sn + 1) * ss) +} + +const ( + signature uint64 = 0xE11AB1A1E011CFD0 + miniStreamSectorSize uint32 = 64 + miniStreamCutoffSize int64 = 4096 + dirEntrySize uint32 = 128 //128 bytes +) + +const ( + maxRegSect uint32 = 0xFFFFFFFA // Maximum regular sector number + difatSect uint32 = 0xFFFFFFFC //Specifies a DIFAT sector in the FAT + fatSect uint32 = 0xFFFFFFFD // Specifies a FAT sector in the FAT + endOfChain uint32 = 0xFFFFFFFE // End of linked chain of sectors + freeSect uint32 = 0xFFFFFFFF // Speficies unallocated sector in the FAT, Mini FAT or DIFAT + maxRegStreamID uint32 = 0xFFFFFFFA // maximum regular stream ID + noStream uint32 = 0xFFFFFFFF // empty pointer +) + +const lenHeader int = 8 + 16 + 10 + 6 + 12 + 8 + 16 + 109*4 + +type headerFields struct { + signature uint64 + _ [16]byte //CLSID - ignore, must be null + minorVersion uint16 //Version number for non-breaking changes. This field SHOULD be set to 0x003E if the major version field is either 0x0003 or 0x0004. + majorVersion uint16 //Version number for breaking changes. This field MUST be set to either 0x0003 (version 3) or 0x0004 (version 4). + _ [2]byte //byte order - ignore, must be little endian + sectorSize uint16 //This field MUST be set to 0x0009, or 0x000c, depending on the Major Version field. This field specifies the sector size of the compound file as a power of 2. If Major Version is 3, then the Sector Shift MUST be 0x0009, specifying a sector size of 512 bytes. If Major Version is 4, then the Sector Shift MUST be 0x000C, specifying a sector size of 4096 bytes. + _ [2]byte // ministream sector size - ignore, must be 64 bytes + _ [6]byte // reserved - ignore, not used + numDirectorySectors uint32 //This integer field contains the count of the number of directory sectors in the compound file. If Major Version is 3, then the Number of Directory Sectors MUST be zero. This field is not supported for version 3 compound files. + numFatSectors uint32 //This integer field contains the count of the number of FAT sectors in the compound file. + directorySectorLoc uint32 //This integer field contains the starting sector number for the directory stream. + _ [4]byte // transaction - ignore, not used + _ [4]byte // mini stream size cutooff - ignore, must be 4096 bytes + miniFatSectorLoc uint32 //This integer field contains the starting sector number for the mini FAT. + numMiniFatSectors uint32 //This integer field contains the count of the number of mini FAT sectors in the compound file. + difatSectorLoc uint32 //This integer field contains the starting sector number for the DIFAT. + numDifatSectors uint32 //This integer field contains the count of the number of DIFAT sectors in the compound file. + initialDifats [109]uint32 //The first 109 difat sectors are included in the header +} + +func makeHeader(b []byte) *headerFields { + h := &headerFields{} + h.signature = binary.LittleEndian.Uint64(b[:8]) + h.minorVersion = binary.LittleEndian.Uint16(b[24:26]) + h.majorVersion = binary.LittleEndian.Uint16(b[26:28]) + h.sectorSize = binary.LittleEndian.Uint16(b[30:32]) + h.numDirectorySectors = binary.LittleEndian.Uint32(b[40:44]) + h.numFatSectors = binary.LittleEndian.Uint32(b[44:48]) + h.directorySectorLoc = binary.LittleEndian.Uint32(b[48:52]) + h.miniFatSectorLoc = binary.LittleEndian.Uint32(b[60:64]) + h.numMiniFatSectors = binary.LittleEndian.Uint32(b[64:68]) + h.difatSectorLoc = binary.LittleEndian.Uint32(b[68:72]) + h.numDifatSectors = binary.LittleEndian.Uint32(b[72:76]) + var idx int + for i := 76; i < 512; i = i + 4 { + h.initialDifats[idx] = binary.LittleEndian.Uint32(b[i : i+4]) + idx++ + } + return h +} + +type header struct { + *headerFields + difats []uint32 + miniFatLocs []uint32 + miniStreamLocs []uint32 // chain of sectors containing the ministream +} + +func (r *Reader) setHeader() error { + buf, err := r.readAt(0, lenHeader) + if err != nil { + return err + } + r.header = &header{headerFields: makeHeader(buf)} + // sanity check - check signature + if r.header.signature != signature { + return Error{ErrFormat, "bad signature", int64(r.header.signature)} + } + // check for legal sector size + if r.header.sectorSize == 0x0009 || r.header.sectorSize == 0x000c { + r.sectorSize = uint32(1 << r.header.sectorSize) + } else { + return Error{ErrFormat, "illegal sector size", int64(r.header.sectorSize)} + } + // check for DIFAT overflow + if r.header.numDifatSectors > 0 { + sz := (r.sectorSize / 4) - 1 + if int(r.header.numDifatSectors*sz+109) < 0 { + return Error{ErrFormat, "DIFAT int overflow", int64(r.header.numDifatSectors)} + } + if r.header.numDifatSectors*sz+109 > r.header.numFatSectors+sz { + return Error{ErrFormat, "num DIFATs exceeds FAT sectors", int64(r.header.numDifatSectors)} + } + } + // check for mini FAT overflow + if r.header.numMiniFatSectors > 0 { + if int(r.sectorSize/4*r.header.numMiniFatSectors) < 0 { + return Error{ErrFormat, "mini FAT int overflow", int64(r.header.numMiniFatSectors)} + } + if r.header.numMiniFatSectors > r.header.numFatSectors*(r.sectorSize/miniStreamSectorSize) { + return Error{ErrFormat, "num mini FATs exceeds FAT sectors", int64(r.header.numFatSectors)} + } + } + return nil +} + +func (r *Reader) setDifats() error { + r.header.difats = r.header.initialDifats[:] + // return early if no extra DIFAT sectors + if r.header.numDifatSectors == 0 { + return nil + } + sz := (r.sectorSize / 4) - 1 + n := make([]uint32, 109, r.header.numDifatSectors*sz+109) + copy(n, r.header.difats) + r.header.difats = n + off := r.header.difatSectorLoc + for i := 0; i < int(r.header.numDifatSectors); i++ { + buf, err := r.readAt(fileOffset(r.sectorSize, off), int(r.sectorSize)) + if err != nil { + return Error{ErrFormat, "error setting DIFAT(" + err.Error() + ")", int64(off)} + } + for j := 0; j < int(sz); j++ { + r.header.difats = append(r.header.difats, binary.LittleEndian.Uint32(buf[j*4:j*4+4])) + } + off = binary.LittleEndian.Uint32(buf[len(buf)-4:]) + } + return nil +} + +// set the ministream FAT and sector slices in the header +func (r *Reader) setMiniStream() error { + // do nothing if there is no ministream + if r.direntries[0].startingSectorLoc == endOfChain || r.header.miniFatSectorLoc == endOfChain || r.header.numMiniFatSectors == 0 { + return nil + } + // build a slice of minifat sectors (akin to the DIFAT slice) + c := int(r.header.numMiniFatSectors) + r.header.miniFatLocs = make([]uint32, c) + r.header.miniFatLocs[0] = r.header.miniFatSectorLoc + for i := 1; i < c; i++ { + loc, err := r.findNext(r.header.miniFatLocs[i-1], false) + if err != nil { + return Error{ErrFormat, "setting mini stream (" + err.Error() + ")", int64(r.header.miniFatLocs[i-1])} + } + r.header.miniFatLocs[i] = loc + } + // build a slice of ministream sectors + c = int(r.sectorSize / 4 * r.header.numMiniFatSectors) + r.header.miniStreamLocs = make([]uint32, 0, c) + cycles := make(map[uint32]bool) + sn := r.direntries[0].startingSectorLoc + for sn != endOfChain { + r.header.miniStreamLocs = append(r.header.miniStreamLocs, sn) + nsn, err := r.findNext(sn, false) + if err != nil { + return Error{ErrFormat, "setting mini stream (" + err.Error() + ")", int64(sn)} + } + if nsn <= sn { + if nsn == sn || cycles[nsn] { + return Error{ErrRead, "cycle detected in mini stream", int64(nsn)} + } + cycles[nsn] = true + } + sn = nsn + } + return nil +} + +func (r *Reader) readAt(offset int64, length int) ([]byte, error) { + if r.slicer { + b, err := r.ra.(slicer).Slice(offset, length) + if err != nil { + return nil, Error{ErrRead, "slicer read error (" + err.Error() + ")", offset} + } + return b, nil + } + if length > len(r.buf) { + return nil, Error{ErrRead, "read length greater than read buffer", int64(length)} + } + if _, err := r.ra.ReadAt(r.buf[:length], offset); err != nil { + return nil, Error{ErrRead, err.Error(), offset} + } + return r.buf[:length], nil +} + +func (r *Reader) getOffset(sn uint32, mini bool) (int64, error) { + if mini { + num := r.sectorSize / 64 + sec := int(sn / num) + if sec >= len(r.header.miniStreamLocs) { + return 0, Error{ErrRead, "minisector number is outside minisector range", int64(sec)} + } + dif := sn % num + return int64((r.header.miniStreamLocs[sec]+1)*r.sectorSize + dif*64), nil + } + return fileOffset(r.sectorSize, sn), nil +} + +// check the FAT sector for the next sector in a chain +func (r *Reader) findNext(sn uint32, mini bool) (uint32, error) { + entries := r.sectorSize / 4 + index := int(sn / entries) // find position in DIFAT or minifat array + var sect uint32 + if mini { + if index < 0 || index >= len(r.header.miniFatLocs) { + return 0, Error{ErrRead, "minisector index is outside miniFAT range", int64(index)} + } + sect = r.header.miniFatLocs[index] + } else { + if index < 0 || index >= len(r.header.difats) { + return 0, Error{ErrRead, "FAT index is outside DIFAT range", int64(index)} + } + sect = r.header.difats[index] + } + fatIndex := sn % entries // find position within FAT or MiniFAT sector + offset := fileOffset(r.sectorSize, sect) + int64(fatIndex*4) + buf, err := r.readAt(offset, 4) + if err != nil { + return 0, Error{ErrRead, "bad read finding next sector (" + err.Error() + ")", offset} + } + return binary.LittleEndian.Uint32(buf), nil +} + +// Reader provides sequential access to the contents of a MS compound file (MSCFB) +type Reader struct { + slicer bool + sectorSize uint32 + buf []byte + header *header + File []*File // File is an ordered slice of final directory entries. + direntries []*File // unordered raw directory entries + entry int + + ra io.ReaderAt + wa io.WriterAt +} + +// New returns a MSCFB reader +func New(ra io.ReaderAt) (*Reader, error) { + r := &Reader{ra: ra} + if _, ok := ra.(slicer); ok { + r.slicer = true + } else { + r.buf = make([]byte, lenHeader) + } + if err := r.setHeader(); err != nil { + return nil, err + } + // resize the buffer to 4096 if sector size isn't 512 + if !r.slicer && int(r.sectorSize) > len(r.buf) { + r.buf = make([]byte, r.sectorSize) + } + if err := r.setDifats(); err != nil { + return nil, err + } + if err := r.setDirEntries(); err != nil { + return nil, err + } + if err := r.setMiniStream(); err != nil { + return nil, err + } + if err := r.traverse(); err != nil { + return nil, err + } + return r, nil +} + +// ID returns the CLSID (class ID) field from the root directory entry +func (r *Reader) ID() string { + return r.File[0].ID() +} + +// Created returns the created field from the root directory entry +func (r *Reader) Created() time.Time { + return r.File[0].Created() +} + +// Modified returns the last modified field from the root directory entry +func (r *Reader) Modified() time.Time { + return r.File[0].Modified() +} + +// Next iterates to the next directory entry. +// This isn't necessarily an adjacent *File within the File slice, but is based on the Left Sibling, Right Sibling and Child information in directory entries. +func (r *Reader) Next() (*File, error) { + r.entry++ + if r.entry >= len(r.File) { + return nil, io.EOF + } + return r.File[r.entry], nil +} + +// Read the current directory entry +func (r *Reader) Read(b []byte) (n int, err error) { + if r.entry >= len(r.File) { + return 0, io.EOF + } + return r.File[r.entry].Read(b) +} + +// Debug provides granular information from an mscfb file to assist with debugging +func (r *Reader) Debug() map[string][]uint32 { + ret := map[string][]uint32{ + "sector size": []uint32{r.sectorSize}, + "mini fat locs": r.header.miniFatLocs, + "mini stream locs": r.header.miniStreamLocs, + "directory sector": []uint32{r.header.directorySectorLoc}, + "mini stream start/size": []uint32{r.File[0].startingSectorLoc, binary.LittleEndian.Uint32(r.File[0].streamSize[:])}, + } + for f, err := r.Next(); err == nil; f, err = r.Next() { + ret[f.Name+" start/size"] = []uint32{f.startingSectorLoc, binary.LittleEndian.Uint32(f.streamSize[:])} + } + return ret +} + +const ( + // ErrFormat reports issues with the MSCFB's header structures + ErrFormat = iota + // ErrRead reports issues attempting to read MSCFB streams + ErrRead + // ErrSeek reports seek issues + ErrSeek + // ErrWrite reports write issues + ErrWrite + // ErrTraverse reports issues attempting to traverse the child-parent-sibling relations + // between MSCFB storage objects + ErrTraverse +) + +type Error struct { + typ int + msg string + val int64 +} + +func (e Error) Error() string { + return "mscfb: " + e.msg + "; " + strconv.FormatInt(e.val, 10) +} + +// Typ gives the type of MSCFB error +func (e Error) Typ() int { + return e.typ +} + +// Slicer interface avoids a copy by obtaining a byte slice directly from the underlying reader +type slicer interface { + Slice(offset int64, length int) ([]byte, error) +} diff --git a/vendor/github.com/richardlehane/msoleps/types/currency.go b/vendor/github.com/richardlehane/msoleps/types/currency.go new file mode 100644 index 00000000..01b9c89e --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/currency.go @@ -0,0 +1,43 @@ +// Copyright 2014 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "strconv" +) + +//The CURRENCY type specifies currency information. It is represented as an 8-byte integer, scaled by 10,000, to give a fixed-point number with 15 digits to the left of the decimal point, and four digits to the right. This representation provides a range of 922337203685477.5807 to –922337203685477.5808. For example, $5.25 is stored as the value 52500. + +type Currency int64 + +func (c Currency) String() string { + return "$" + strconv.FormatFloat(float64(c)/10000, 'f', -1, 64) +} + +func (c Currency) Type() string { + return "Currency" +} + +func (c Currency) Length() int { + return 8 +} + +func MakeCurrency(b []byte) (Type, error) { + if len(b) < 8 { + return Currency(0), ErrType + } + return Currency(binary.LittleEndian.Uint64(b[:8])), nil +} diff --git a/vendor/github.com/richardlehane/msoleps/types/date.go b/vendor/github.com/richardlehane/msoleps/types/date.go new file mode 100644 index 00000000..364e551a --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/date.go @@ -0,0 +1,49 @@ +// Copyright 2014 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "time" +) + +// http://msdn.microsoft.com/en-us/library/cc237601.aspx +type Date float64 + +func (d Date) Time() time.Time { + start := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC) + day := float64(time.Hour * 24) + dur := time.Duration(day * float64(d)) + return start.Add(dur) +} + +func (d Date) String() string { + return d.Time().String() +} + +func (d Date) Type() string { + return "Date" +} + +func (d Date) Length() int { + return 8 +} + +func MakeDate(b []byte) (Type, error) { + if len(b) < 8 { + return Date(0), ErrType + } + return Date(binary.LittleEndian.Uint64(b[:8])), nil +} diff --git a/vendor/github.com/richardlehane/msoleps/types/decimal.go b/vendor/github.com/richardlehane/msoleps/types/decimal.go new file mode 100644 index 00000000..090e5ffa --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/decimal.go @@ -0,0 +1,65 @@ +// Copyright 2014 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "math" + "math/big" +) + +// http://msdn.microsoft.com/en-us/library/cc237603.aspx +type Decimal struct { + res [2]byte + scale byte + sign byte + high32 uint32 + low64 uint64 +} + +func (d Decimal) Type() string { + return "Decimal" +} + +func (d Decimal) Length() int { + return 16 +} + +func (d Decimal) String() string { + h, l, b := new(big.Int), new(big.Int), new(big.Int) + l.SetUint64(d.low64) + h.Lsh(big.NewInt(int64(d.high32)), 64) + b.Add(h, l) + q, f, r := new(big.Rat), new(big.Rat), new(big.Rat) + q.SetFloat64(math.Pow10(int(d.scale))) + r.Quo(f.SetInt(b), q) + if d.sign == 0x80 { + r.Neg(r) + } + return r.FloatString(20) +} + +func MakeDecimal(b []byte) (Type, error) { + if len(b) < 16 { + return Decimal{}, ErrType + } + return Decimal{ + res: [2]byte{b[0], b[1]}, + scale: b[2], + sign: b[3], + high32: binary.LittleEndian.Uint32(b[4:8]), + low64: binary.LittleEndian.Uint64(b[8:16]), + }, nil +} diff --git a/vendor/github.com/richardlehane/msoleps/types/filetime.go b/vendor/github.com/richardlehane/msoleps/types/filetime.go new file mode 100644 index 00000000..c1060ee9 --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/filetime.go @@ -0,0 +1,70 @@ +// Copyright 2014 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "time" +) + +// Win FILETIME type +// http://msdn.microsoft.com/en-us/library/cc230324.aspx +type FileTime struct { + Low uint32 // Windows FILETIME structure + High uint32 // Windows FILETIME structure +} + +const ( + tick uint64 = 10000000 + gregToUnix uint64 = 11644473600 +) + +func winToUnix(low, high uint32) int64 { + gregTime := ((uint64(high) << 32) + uint64(low)) / tick + if gregTime < gregToUnix { + return 0 + } + return int64(gregTime - gregToUnix) +} + +func (f FileTime) Time() time.Time { + return time.Unix(winToUnix(f.Low, f.High), 0) +} + +func (f FileTime) String() string { + return f.Time().String() +} + +func (f FileTime) Type() string { + return "FileTime" +} + +func (f FileTime) Length() int { + return 8 +} + +func MakeFileTime(b []byte) (Type, error) { + if len(b) < 8 { + return FileTime{}, ErrType + } + return MustFileTime(b), nil +} + +func MustFileTime(b []byte) FileTime { + return FileTime{ + Low: binary.LittleEndian.Uint32(b[:4]), + High: binary.LittleEndian.Uint32(b[4:8]), + } +} diff --git a/vendor/github.com/richardlehane/msoleps/types/guid.go b/vendor/github.com/richardlehane/msoleps/types/guid.go new file mode 100644 index 00000000..86bb6449 --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/guid.go @@ -0,0 +1,209 @@ +// Copyright 2014 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "strings" +) + +// Win GUID and UUID type +// http://msdn.microsoft.com/en-us/library/cc230326.aspx +type Guid struct { + DataA uint32 + DataB uint16 + DataC uint16 + DataD [8]byte +} + +func (g Guid) String() string { + buf := make([]byte, 8) + binary.BigEndian.PutUint32(buf[:4], g.DataA) + binary.BigEndian.PutUint16(buf[4:6], g.DataB) + binary.BigEndian.PutUint16(buf[6:], g.DataC) + return strings.ToUpper("{" + + hex.EncodeToString(buf[:4]) + + "-" + + hex.EncodeToString(buf[4:6]) + + "-" + + hex.EncodeToString(buf[6:]) + + "-" + + hex.EncodeToString(g.DataD[:2]) + + "-" + + hex.EncodeToString(g.DataD[2:]) + + "}") +} + +func (g Guid) Type() string { + return "Guid" +} + +func (g Guid) Length() int { + return 16 +} + +func GuidFromString(str string) (Guid, error) { + gerr := "Invalid GUID: expecting in format {F29F85E0-4FF9-1068-AB91-08002B27B3D9}, got " + str + if len(str) != 38 { + return Guid{}, errors.New(gerr + "; bad length, should be 38 chars") + } + trimmed := strings.Trim(str, "{}") + parts := strings.Split(trimmed, "-") + if len(parts) != 5 { + return Guid{}, errors.New(gerr + "; expecting should five '-' separators") + } + buf, err := hex.DecodeString(strings.Join(parts, "")) + if err != nil { + return Guid{}, errors.New(gerr + "; error decoding hex: " + err.Error()) + } + return makeGuid(buf, binary.BigEndian), nil +} + +func MakeGuid(b []byte) (Type, error) { + if len(b) < 16 { + return Guid{}, ErrType + } + return makeGuid(b, binary.LittleEndian), nil +} + +func makeGuid(b []byte, order binary.ByteOrder) Guid { + g := Guid{ + DataA: order.Uint32(b[:4]), + DataB: order.Uint16(b[4:6]), + DataC: order.Uint16(b[6:8]), + DataD: [8]byte{}, + } + copy(g.DataD[:], b[8:]) + return g +} + +func MustGuidFromString(str string) Guid { + g, err := GuidFromString(str) + if err != nil { + panic(err) + } + return g +} + +func MustGuid(b []byte) Guid { + return makeGuid(b, binary.LittleEndian) +} + +func GuidFromName(n string) (Guid, error) { + n = strings.ToLower(n) + buf, err := charConvert([]byte(n)) + if err != nil { + return Guid{}, err + } + return makeGuid(buf, binary.LittleEndian), nil +} + +func charConvert(in []byte) ([]byte, error) { + if len(in) != 26 { + return nil, errors.New("invalid GUID: expecting 26 characters") + } + out := make([]byte, 16) + var idx, shift uint + var b byte + for _, v := range in { + this, ok := characterMapping[v] + if !ok { + return nil, errors.New("invalid Guid: invalid character") + } + b = b | this<= 3 { + out[idx] = b + idx++ + b = this >> (8 - shift) // write any remainder back to b, or 0 if shift is 3 + } + shift = shift + 5 + if shift > 7 { + shift = shift - 8 + } + } + return out, nil +} + +const ( + charA byte = iota + charB + charC + charD + charE + charF + charG + charH + charI + charJ + charK + charL + charM + charN + charO + charP + charQ + charR + charS + charT + charU + charV + charW + charX + charY + charZ + char0 + char1 + char2 + char3 + char4 + char5 +) + +var characterMapping = map[byte]byte{ + 'a': charA, + 'b': charB, + 'c': charC, + 'd': charD, + 'e': charE, + 'f': charF, + 'g': charG, + 'h': charH, + 'i': charI, + 'j': charJ, + 'k': charK, + 'l': charL, + 'm': charM, + 'n': charN, + 'o': charO, + 'p': charP, + 'q': charQ, + 'r': charR, + 's': charS, + 't': charT, + 'u': charU, + 'v': charV, + 'w': charW, + 'x': charX, + 'y': charY, + 'z': charZ, + '0': char0, + '1': char1, + '2': char2, + '3': char3, + '4': char4, + '5': char5, +} diff --git a/vendor/github.com/richardlehane/msoleps/types/numeric.go b/vendor/github.com/richardlehane/msoleps/types/numeric.go new file mode 100644 index 00000000..0ddc3875 --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/numeric.go @@ -0,0 +1,274 @@ +// Copyright 2014 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "strconv" +) + +type Null struct{} + +func (i Null) Type() string { + return "Null" +} + +func (i Null) Length() int { + return 0 +} + +func (i Null) String() string { + return "" +} + +type Bool bool + +func (i Bool) Type() string { + return "Boolean" +} + +func (i Bool) Length() int { + return 2 +} + +func (i Bool) String() string { + if i { + return "true" + } + return "false" +} + +func MakeBool(b []byte) (Type, error) { + if len(b) < 2 { + return Bool(false), ErrType + } + switch binary.LittleEndian.Uint16(b[:2]) { + case 0xFFFF: + return Bool(true), nil + case 0x0000: + return Bool(false), nil + } + return Bool(false), ErrType +} + +type I1 int8 + +func (i I1) Type() string { + return "Int8" +} + +func (i I1) String() string { + return strconv.Itoa(int(i)) +} + +func (i I1) Length() int { + return 1 +} + +func MakeI1(b []byte) (Type, error) { + if len(b) < 1 { + return I1(0), ErrType + } + return I1(b[0]), nil +} + +type I2 int16 + +func (i I2) Type() string { + return "Int16" +} + +func (i I2) Length() int { + return 2 +} + +func (i I2) String() string { + return strconv.Itoa(int(i)) +} + +func MakeI2(b []byte) (Type, error) { + if len(b) < 2 { + return I2(0), ErrType + } + return I2(binary.LittleEndian.Uint16(b[:2])), nil +} + +type I4 int32 + +func (i I4) Type() string { + return "Int32" +} + +func (i I4) Length() int { + return 4 +} + +func (i I4) String() string { + return strconv.Itoa(int(i)) +} + +func MakeI4(b []byte) (Type, error) { + if len(b) < 4 { + return I4(0), ErrType + } + return I4(binary.LittleEndian.Uint32(b[:4])), nil +} + +type I8 int64 + +func (i I8) Type() string { + return "Int64" +} + +func (i I8) Length() int { + return 8 +} + +func (i I8) String() string { + return strconv.FormatInt(int64(i), 10) +} + +func MakeI8(b []byte) (Type, error) { + if len(b) < 8 { + return I8(0), ErrType + } + return I8(binary.LittleEndian.Uint64(b[:8])), nil +} + +type UI1 uint8 + +func (i UI1) Type() string { + return "Uint8" +} + +func (i UI1) Length() int { + return 1 +} + +func (i UI1) String() string { + return strconv.Itoa(int(i)) +} + +func MakeUI1(b []byte) (Type, error) { + if len(b) < 1 { + return UI1(0), ErrType + } + return UI1(b[0]), nil +} + +type UI2 uint16 + +func (i UI2) Type() string { + return "Uint16" +} + +func (i UI2) Length() int { + return 2 +} + +func (i UI2) String() string { + return strconv.Itoa(int(i)) +} + +func MakeUI2(b []byte) (Type, error) { + if len(b) < 2 { + return UI2(0), ErrType + } + return UI2(binary.LittleEndian.Uint16(b[:2])), nil +} + +type UI4 uint32 + +func (i UI4) Type() string { + return "Uint32" +} + +func (i UI4) Length() int { + return 4 +} + +func (i UI4) String() string { + return strconv.FormatUint(uint64(i), 10) +} + +func MakeUI4(b []byte) (Type, error) { + if len(b) < 4 { + return UI4(0), ErrType + } + return UI4(binary.LittleEndian.Uint32(b[:4])), nil +} + +type UI8 uint64 + +func (i UI8) Type() string { + return "Uint64" +} + +func (i UI8) Length() int { + return 8 +} + +func (i UI8) String() string { + return strconv.FormatUint(uint64(i), 10) +} + +func MakeUI8(b []byte) (Type, error) { + if len(b) < 8 { + return UI8(0), ErrType + } + return UI8(binary.LittleEndian.Uint64(b[:8])), nil +} + +type R4 float32 + +func (r R4) Type() string { + return "Float32" +} + +func (r R4) Length() int { + return 4 +} + +func (r R4) String() string { + return strconv.FormatFloat(float64(r), 'f', -1, 32) +} + +func MakeR4(b []byte) (Type, error) { + if len(b) < 4 { + return R4(0), ErrType + } + return R4(binary.LittleEndian.Uint32(b[:4])), nil +} + +type R8 float64 + +func (r R8) Type() string { + return "Float64" +} + +func (r R8) Length() int { + return 8 +} + +func (r R8) String() string { + return strconv.FormatFloat(float64(r), 'f', -1, 64) +} + +func MakeR8(b []byte) (Type, error) { + if len(b) < 8 { + return R8(0), ErrType + } + return R8(binary.LittleEndian.Uint64(b[:8])), nil +} diff --git a/vendor/github.com/richardlehane/msoleps/types/strings.go b/vendor/github.com/richardlehane/msoleps/types/strings.go new file mode 100644 index 00000000..20ec7c01 --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/strings.go @@ -0,0 +1,270 @@ +// Copyright 2014 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "strings" + "unicode/utf16" +) + +func nullTerminated(s string) string { + return s[:strings.Index(s, "\x00")] +} + +type UnicodeString []uint16 + +func (s UnicodeString) Type() string { + return "UnicodeString" +} + +func (s UnicodeString) Length() int { + return 4 + len(s)*2 +} + +func (s UnicodeString) String() string { + if len(s) == 0 { + return "" + } + return nullTerminated(string(utf16.Decode(s))) +} + +func MakeUnicode(b []byte) (Type, error) { + if len(b) < 4 { + return UnicodeString{}, ErrType + } + l := int(binary.LittleEndian.Uint32(b[:4])) + if l == 0 { + return UnicodeString{}, nil + } + if len(b) < l*2+4 { + return UnicodeString{}, ErrType + } + s := make(UnicodeString, l) + for i := range s { + start := i*2 + 4 + s[i] = binary.LittleEndian.Uint16(b[start : start+2]) + } + return s, nil +} + +type CodeString struct { + id CodePageID + Chars []byte +} + +func (s *CodeString) SetId(i CodePageID) { + s.id = i +} + +func (s *CodeString) Encoding() string { + return CodePageIDs[s.id] +} + +func (s *CodeString) Type() string { + return "CodeString" +} + +func (s *CodeString) Length() int { + return 4 + len(s.Chars) +} + +func (s *CodeString) String() string { + if len(s.Chars) == 0 { + return "" + } + if s.id == 1200 { + chars := make([]uint16, len(s.Chars)/2) + for i := range chars { + chars[i] = binary.LittleEndian.Uint16(s.Chars[i*2 : i*2+2]) + } + return nullTerminated(string(utf16.Decode(chars))) + } + return nullTerminated(string(s.Chars)) +} + +func MakeCodeString(b []byte) (Type, error) { + if len(b) < 4 { + return &CodeString{}, ErrType + } + s := &CodeString{} + l := int(binary.LittleEndian.Uint32(b[:4])) + if l == 0 { + return s, nil + } + if len(b) < l+4 { + return s, ErrType + } + s.Chars = make([]byte, l) + copy(s.Chars, b[4:l+4]) + return s, nil +} + +type CodePageID uint16 + +var CodePageIDs map[CodePageID]string = map[CodePageID]string{ + 37: "IBM037 - IBM EBCDIC US-Canada", + 437: "IBM437 - OEM United States", + 500: "IBM500 - IBM EBCDIC International", + 708: "ASMO-708 - Arabic (ASMO 708)", + 709: "Arabic (ASMO-449+, BCON V4)", + 710: "Arabic - Transparent Arabic", + 720: "DOS-720 - Arabic (Transparent ASMO); Arabic (DOS)", + 737: "ibm737 - OEM Greek (formerly 437G); Greek (DOS)", + 775: "ibm775 - OEM Baltic; Baltic (DOS)", + 850: "ibm850 - OEM Multilingual Latin 1; Western European (DOS)", + 852: "ibm852 - OEM Latin 2; Central European (DOS)", + 855: "IBM855 - OEM Cyrillic (primarily Russian)", + 857: "ibm857 - OEM Turkish; Turkish (DOS)", + 858: "IBM00858 - OEM Multilingual Latin 1 + Euro symbol", + 860: "IBM860 - OEM Portuguese; Portuguese (DOS)", + 861: "ibm861 - OEM Icelandic; Icelandic (DOS)", + 862: "DOS-862 - OEM Hebrew; Hebrew (DOS)", + 863: "IBM863 - OEM French Canadian; French Canadian (DOS)", + 864: "IBM864 - OEM Arabic; Arabic (864)", + 865: "IBM865 - OEM Nordic; Nordic (DOS)", + 866: "cp866 - OEM Russian; Cyrillic (DOS)", + 869: "ibm869 - OEM Modern Greek; Greek, Modern (DOS)", + 870: "IBM870 - IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2", + 874: "windows-874 - ANSI/OEM Thai (ISO 8859-11); Thai (Windows)", + 875: "cp875 - IBM EBCDIC Greek Modern", + 932: "shift_jis - ANSI/OEM Japanese; Japanese (Shift-JIS)", + 936: "gb2312 - ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)", + 949: "ks_c_5601-1987 - ANSI/OEM Korean (Unified Hangul Code)", + 950: "big5 - ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)", + 1026: "IBM1026 - IBM EBCDIC Turkish (Latin 5)", + 1047: "IBM01047 - BM EBCDIC Latin 1/Open System", + 1140: "IBM01140 - IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)", + 1141: "IBM01141 - IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)", + 1142: "IBM01142 - IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)", + 1143: "IBM01143 - IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)", + 1144: "IBM01144 - IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)", + 1145: "IBM01145 - IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)", + 1146: "IBM01146 - IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)", + 1147: "IBM01147 - IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)", + 1148: "IBM01148 - IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)", + 1149: "IBM01149 - IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)", + 1200: "utf-16 - Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications", + 1201: "unicodeFFFE - Unicode UTF-16, big endian byte order; available only to managed applications", + 1250: "windows-1250 - ANSI Central European; Central European (Windows)", + 1251: "windows-1251 - ANSI Cyrillic; Cyrillic (Windows)", + 1252: "windows-1252 - ANSI Latin 1; Western European (Windows)", + 1253: "windows-1253 - ANSI Greek; Greek (Windows)", + 1254: "windows-1254 - ANSI Turkish; Turkish (Windows)", + 1255: "windows-1255 - ANSI Hebrew; Hebrew (Windows)", + 1256: "windows-1256 - ANSI Arabic; Arabic (Windows)", + 1257: "windows-1257 - ANSI Baltic; Baltic (Windows)", + 1258: "windows-1258 - ANSI/OEM Vietnamese; Vietnamese (Windows)", + 1361: "Johab - Korean (Johab)", + 10000: "macintosh - MAC Roman; Western European (Mac)", + 10001: "x-mac-japanese - Japanese (Mac)", + 10002: "x-mac-chinesetrad - MAC Traditional Chinese (Big5); Chinese Traditional (Mac)", + 10003: "x-mac-korean - Korean (Mac)", + 10004: "x-mac-arabic - Arabic (Mac)", + 10005: "x-mac-hebrew - Hebrew (Mac)", + 10006: "x-mac-greek - Greek (Mac)", + 10007: "x-mac-cyrillic - Cyrillic (Mac)", + 10008: "x-mac-chinesesimp - MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)", + 10010: "x-mac-romanian - Romanian (Mac)", + 10017: "x-mac-ukrainian - Ukrainian (Mac)", + 10021: "x-mac-thai - Thai (Mac)", + 10029: "x-mac-ce - MAC Latin 2; Central European (Mac)", + 10079: "x-mac-icelandic - Icelandic (Mac)", + 10081: "x-mac-turkish - Turkish (Mac)", + 10082: "x-mac-croatian - Croatian (Mac)", + 12000: "utf-32 - Unicode UTF-32, little endian byte order; available only to managed applications", + 12001: "utf-32BE - Unicode UTF-32, big endian byte order; available only to managed applications", + 20000: "x-Chinese_CNS - CNS Taiwan; Chinese Traditional (CNS)", + 20001: "x-cp20001 - TCA Taiwan", + 20002: "x_Chinese-Eten - Eten Taiwan; Chinese Traditional (Eten)", + 20003: "x-cp20003 - IBM5550 Taiwan", + 20004: "x-cp20004 - TeleText Taiwan", + 20005: "x-cp20005 - Wang Taiwan", + 20105: "x-IA5 - IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)", + 20106: "x-IA5-German - IA5 German (7-bit)", + 20107: "x-IA5-Swedish - IA5 Swedish (7-bit)", + 20108: "x-IA5-Norwegian - IA5 Norwegian (7-bit)", + 20127: "us-ascii - US-ASCII (7-bit)", + 20261: "x-cp20261 - T.61", + 20269: "x-cp20269 - ISO 6937 Non-Spacing Accent", + 20273: "IBM273 - IBM EBCDIC Germany", + 20277: "IBM277 - IBM EBCDIC Denmark-Norway", + 20278: "IBM278 - IBM EBCDIC Finland-Sweden", + 20280: "IBM280 - IBM EBCDIC Italy", + 20284: "IBM284 - IBM EBCDIC Latin America-Spain", + 20285: "IBM285 - IBM EBCDIC United Kingdom", + 20290: "IBM290 - IBM EBCDIC Japanese Katakana Extended", + 20297: "IBM297 - IBM EBCDIC France", + 20420: "IBM420 - IBM EBCDIC Arabic", + 20423: "IBM423 - IBM EBCDIC Greek", + 20424: "IBM424 - IBM EBCDIC Hebrew", + 20833: "x-EBCDIC-KoreanExtended - IBM EBCDIC Korean Extended", + 20838: "IBM-Thai - IBM EBCDIC Thai", + 20866: "koi8-r - Russian (KOI8-R); Cyrillic (KOI8-R)", + 20871: "IBM871 - IBM EBCDIC Icelandic", + 20880: "IBM880 - IBM EBCDIC Cyrillic Russian", + 20905: "IBM905 - IBM EBCDIC Turkish", + 20924: "IBM00924 - IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)", + 20932: "EUC-JP - Japanese (JIS 0208-1990 and 0212-1990)", + 20936: "x-cp20936 - Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)", + 20949: "x-cp20949 - Korean Wansung", + 21025: "cp1025 - IBM EBCDIC Cyrillic Serbian-Bulgarian", + 21027: "(deprecated)", + 21866: "koi8-u - Ukrainian (KOI8-U); Cyrillic (KOI8-U)", + 28591: "iso-8859-1 - ISO 8859-1 Latin 1; Western European (ISO)", + 28592: "iso-8859-2 - ISO 8859-2 Central European; Central European (ISO)", + 28593: "iso-8859-3 - ISO 8859-3 Latin 3", + 28594: "iso-8859-4 - ISO 8859-4 Baltic", + 28595: "iso-8859-5 - ISO 8859-5 Cyrillic", + 28596: "iso-8859-6 - ISO 8859-6 Arabic", + 28597: "iso-8859-7 - ISO 8859-7 Greek", + 28598: "iso-8859-8 - ISO 8859-8 Hebrew; Hebrew (ISO-Visual)", + 28599: "iso-8859-9 - ISO 8859-9 Turkish", + 28603: "iso-8859-13 - ISO 8859-13 Estonian", + 28605: "iso-8859-15 - ISO 8859-15 Latin 9", + 29001: "x-Europa - Europa 3", + 38598: "iso-8859-8-i - ISO 8859-8 Hebrew; Hebrew (ISO-Logical)", + 50220: "iso-2022-jp - ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)", + 50221: "csISO2022JP - ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)", + 50222: "iso-2022-jp - ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)", + 50225: "iso-2022-kr - ISO 2022 Korean", + 50227: "x-cp50227 - ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)", + 50229: "ISO 2022 - Traditional Chinese", + 50930: "EBCDIC - Japanese (Katakana) Extended", + 50931: "EBCDIC - US-Canada and Japanese", + 50933: "EBCDIC - Korean Extended and Korean", + 50935: "EBCDIC - Simplified Chinese Extended and Simplified Chinese", + 50936: "EBCDIC - Simplified Chinese", + 50937: "EBCDIC - US-Canada and Traditional Chinese", + 50939: "EBCDIC - Japanese (Latin) Extended and Japanese", + 51932: "euc-jp - EUC Japanese", + 51936: "EUC-CN - EUC Simplified Chinese; Chinese Simplified (EUC)", + 51949: "euc-kr - EUC Korean", + 51950: "EUC - Traditional Chinese", + 52936: "hz-gb-2312 - HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)", + 54936: "GB18030 - Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)", + 57002: "x-iscii-de - ISCII Devanagari", + 57003: "x-iscii-be - ISCII Bengali", + 57004: "x-iscii-ta - ISCII Tamil", + 57005: "x-iscii-te - ISCII Telugu", + 57006: "x-iscii-as - ISCII Assamese", + 57007: "x-iscii-or - ISCII Oriya", + 57008: "x-iscii-ka - ISCII Kannada", + 57009: "x-iscii-ma - ISCII Malayalam", + 57010: "x-iscii-gu - ISCII Gujarati", + 57011: "x-iscii-pa - ISCII Punjabi", + 65000: "utf-7 - Unicode (UTF-7)", + 65001: "utf-8 - Unicode (UTF-8)", +} diff --git a/vendor/github.com/richardlehane/msoleps/types/types.go b/vendor/github.com/richardlehane/msoleps/types/types.go new file mode 100644 index 00000000..d509be45 --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/types.go @@ -0,0 +1,125 @@ +// Copyright 2014 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" + "errors" +) + +// MakeVariant is defined in vectorArray.go. It calls Evaluate, which refers to the MakeTypes map, so must add at runtime +func init() { MakeTypes[VT_VARIANT] = MakeVariant } + +var ( + ErrType = errors.New("msoleps: error coercing byte stream to type") + ErrUnknownType = errors.New("msoleps: unknown type error") +) + +type Type interface { + String() string + Type() string + Length() int +} + +const ( + vector uint16 = iota + 1 + array +) + +func Evaluate(b []byte) (Type, error) { + if len(b) < 4 { + return I1(0), ErrType + } + id := TypeID(binary.LittleEndian.Uint16(b[:2])) + f, ok := MakeTypes[id] + if !ok { + return I1(0), ErrUnknownType + } + switch binary.LittleEndian.Uint16(b[2:4]) { + case vector: + return MakeVector(f, b[4:]) + case array: + return MakeArray(f, b[4:]) + } + return f(b[4:]) +} + +type TypeID uint16 + +const ( + VT_EMPTY TypeID = iota // 0x00 + VT_NULL + VT_I2 + VT_I4 + VT_R4 + VT_R8 + VT_CY + VT_DATE + VT_BSTR + _ + VT_ERROR + VT_BOOL + VT_VARIANT + _ + VT_DECIMAL + _ + VT_I1 + VT_U1 + VT_UI2 + VT_UI4 + VT_I8 + VT_UI8 + VT_INT + VT_UINT //0x17 + _ = iota + 5 + VT_LPSTR //0x1E + VT_LPWSTR + VT_FILETIME = iota + 0x25 // 0x40 + VT_BLOB + VT_STREAM + VT_STORAGE + VT_STREAMED_OBJECT + VT_STORED_OBJECT + VT_BLOB_OBJECT + VT_CF + VT_CLSID + VT_VERSIONED_STREAM // 0x49 +) + +type MakeType func([]byte) (Type, error) + +var MakeTypes map[TypeID]MakeType = map[TypeID]MakeType{ + VT_I2: MakeI2, + VT_I4: MakeI4, + VT_R4: MakeR4, + VT_R8: MakeR8, + VT_CY: MakeCurrency, + VT_DATE: MakeDate, + VT_BSTR: MakeCodeString, + VT_BOOL: MakeBool, + VT_DECIMAL: MakeDecimal, + VT_I1: MakeI1, + VT_U1: MakeUI1, + VT_UI2: MakeUI2, + VT_UI4: MakeUI4, + VT_I8: MakeI8, + VT_UI8: MakeUI8, + VT_INT: MakeI4, + VT_UINT: MakeUI4, + VT_LPSTR: MakeCodeString, + VT_LPWSTR: MakeUnicode, + VT_FILETIME: MakeFileTime, + VT_CLSID: MakeGuid, +} diff --git a/vendor/github.com/richardlehane/msoleps/types/vectorArray.go b/vendor/github.com/richardlehane/msoleps/types/vectorArray.go new file mode 100644 index 00000000..1c7c2037 --- /dev/null +++ b/vendor/github.com/richardlehane/msoleps/types/vectorArray.go @@ -0,0 +1,103 @@ +// Copyright 2015 Richard Lehane. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/binary" +) + +type Vector []Type + +func (v Vector) String() string { + return "" +} + +func (v Vector) Type() string { + if len(v) > 0 { + return "Vector of " + v[0].Type() + } + return "Vector (empty)" +} + +func (v Vector) Length() int { + ret := 4 + for _, t := range v { + ret += t.Length() + } + return ret +} + +func MakeVector(f MakeType, b []byte) (Type, error) { + if len(b) < 4 { + return Vector{}, ErrType + } + l := int(binary.LittleEndian.Uint32(b[:4])) + v := make(Vector, l) + place := 4 + for i := 0; i < l; i++ { + t, err := f(b[place:]) + if err != nil { + return Vector{}, ErrType + } + v[i] = t + place += t.Length() + } + return v, nil +} + +type Array [][]Type + +func (a Array) String() string { + return "" +} + +func (a Array) Type() string { + if len(a) > 0 && len(a[0]) > 0 { + return "Array of " + a[0][0].Type() + } + return "Array (empty)" +} + +func (a Array) Length() int { + return 0 +} + +func MakeArray(f MakeType, b []byte) (Type, error) { + return Array{}, nil +} + +type Variant struct { + t Type +} + +func (v Variant) String() string { + return "Typed Property Value containing " + v.t.String() +} + +func (v Variant) Type() string { + return "Typed Property Value containing " + v.t.Type() +} + +func (v Variant) Length() int { + return 4 + v.t.Length() +} + +func MakeVariant(b []byte) (Type, error) { + t, err := Evaluate(b) + if err != nil { + return Variant{}, err + } + return Variant{t}, nil +} diff --git a/vendor/github.com/xuri/efp/CODE_OF_CONDUCT.md b/vendor/github.com/xuri/efp/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..572b5612 --- /dev/null +++ b/vendor/github.com/xuri/efp/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [xuri.me](https://xuri.me). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct][version] + +[homepage]: https://www.contributor-covenant.org +[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct diff --git a/vendor/github.com/xuri/efp/LICENSE b/vendor/github.com/xuri/efp/LICENSE new file mode 100644 index 00000000..b01e42ce --- /dev/null +++ b/vendor/github.com/xuri/efp/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2017 - 2022 Ri Xu All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of efp nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/github.com/xuri/efp/PULL_REQUEST_TEMPLATE.md b/vendor/github.com/xuri/efp/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..d2ac755e --- /dev/null +++ b/vendor/github.com/xuri/efp/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,45 @@ +# PR Details + + + +## Description + + + +## Related Issue + + + + + + +## Motivation and Context + + + +## How Has This Been Tested + + + + + +## Types of changes + + + +- [ ] Docs change / refactoring / dependency upgrade +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +## Checklist + + + + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have read the **CONTRIBUTING** document. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. diff --git a/vendor/github.com/xuri/efp/README.md b/vendor/github.com/xuri/efp/README.md new file mode 100644 index 00000000..2be88213 --- /dev/null +++ b/vendor/github.com/xuri/efp/README.md @@ -0,0 +1,58 @@ +# EFP (Excel Formula Parser) + +[![Build Status](https://github.com/xuri/efp/workflows/Go/badge.svg)](https://github.com/xuri/efp/actions?workflow=Go) +[![Code Coverage](https://codecov.io/gh/xuri/efp/branch/master/graph/badge.svg)](https://codecov.io/gh/xuri/efp) +[![Go Report Card](https://goreportcard.com/badge/github.com/xuri/efp)](https://goreportcard.com/report/github.com/xuri/efp) +[![go.dev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/xuri/efp) +[![Licenses](https://img.shields.io/badge/license-bsd-orange.svg)](https://opensource.org/licenses/BSD-3-Clause) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fxuri%2Fefp.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fxuri%2Fefp?ref=badge_shield) + +Using EFP (Excel Formula Parser) you can get an Abstract Syntax Tree (AST) from Excel formula. + +## Installation + +```bash +go get github.com/xuri/efp +``` + +## Example + +```go +package main + +import "github.com/xuri/efp" + +func main() { + ps := efp.ExcelParser() + ps.Parse("=SUM(A3+B9*2)/2") + println(ps.PrettyPrint()) +} +``` + +Get AST + +```text +SUM + A3 + + + B9 + * + 2 + +/ +2 +``` + +## Contributing + +Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. + +## Credits + +EFP (Excel Formula Parser) is a Golang port of [E. W. Bachtal's](https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html) Excel formula parser. + +## Licenses + +This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause). + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fxuri%2Fefp.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fxuri%2Fefp?ref=badge_large) diff --git a/vendor/github.com/xuri/efp/SECURITY.md b/vendor/github.com/xuri/efp/SECURITY.md new file mode 100644 index 00000000..ad780aa5 --- /dev/null +++ b/vendor/github.com/xuri/efp/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Supported Versions + +We will dive into any security-related issue as long as your efp version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information. + +## Reporting a Vulnerability + +Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you! diff --git a/vendor/github.com/xuri/efp/efp.go b/vendor/github.com/xuri/efp/efp.go new file mode 100644 index 00000000..559858a0 --- /dev/null +++ b/vendor/github.com/xuri/efp/efp.go @@ -0,0 +1,671 @@ +// Package efp (Excel Formula Parser) tokenize an Excel formula using an +// implementation of E. W. Bachtal's algorithm, found here: +// https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html +// +// Go language version by Ri Xu: https://xuri.me +package efp + +import ( + "regexp" + "strconv" + "strings" +) + +// QuoteDouble, QuoteSingle and other's constants are token definitions. +const ( + // Character constants + QuoteDouble = "\"" + QuoteSingle = "'" + BracketClose = "]" + BracketOpen = "[" + BraceOpen = "{" + BraceClose = "}" + ParenOpen = "(" + ParenClose = ")" + Semicolon = ";" + Whitespace = " " + Comma = "," + ErrorStart = "#" + + OperatorsSN = "+-" + OperatorsInfix = "+-*/^&=><" + OperatorsPostfix = "%" + + // Token type + TokenTypeNoop = "Noop" + TokenTypeOperand = "Operand" + TokenTypeFunction = "Function" + TokenTypeSubexpression = "Subexpression" + TokenTypeArgument = "Argument" + TokenTypeOperatorPrefix = "OperatorPrefix" + TokenTypeOperatorInfix = "OperatorInfix" + TokenTypeOperatorPostfix = "OperatorPostfix" + TokenTypeWhitespace = "Whitespace" + TokenTypeUnknown = "Unknown" + + // Token subtypes + TokenSubTypeStart = "Start" + TokenSubTypeStop = "Stop" + TokenSubTypeText = "Text" + TokenSubTypeNumber = "Number" + TokenSubTypeLogical = "Logical" + TokenSubTypeError = "Error" + TokenSubTypeRange = "Range" + TokenSubTypeMath = "Math" + TokenSubTypeConcatenation = "Concatenation" + TokenSubTypeIntersection = "Intersection" + TokenSubTypeUnion = "Union" +) + +// Token encapsulate a formula token. +type Token struct { + TValue string + TType string + TSubType string +} + +// Tokens directly maps the ordered list of tokens. +// Attributes: +// +// items - Ordered list +// index - Current position in the list +// +type Tokens struct { + Index int + Items []Token +} + +// Parser inheritable container. TokenStack directly maps a LIFO stack of +// tokens. +type Parser struct { + Formula string + Tokens Tokens + TokenStack Tokens + Offset int + Token string + InString bool + InPath bool + InRange bool + InError bool +} + +// fToken provides function to encapsulate a formula token. +func fToken(value, tokenType, subType string) Token { + return Token{ + TValue: value, + TType: tokenType, + TSubType: subType, + } +} + +// fTokens provides function to handle an ordered list of tokens. +func fTokens() Tokens { + return Tokens{ + Index: -1, + } +} + +// add provides function to add a token to the end of the list. +func (tk *Tokens) add(value, tokenType, subType string) Token { + token := fToken(value, tokenType, subType) + tk.addRef(token) + return token +} + +// addRef provides function to add a token to the end of the list. +func (tk *Tokens) addRef(token Token) { + tk.Items = append(tk.Items, token) +} + +// reset provides function to reset the index to -1. +func (tk *Tokens) reset() { + tk.Index = -1 +} + +// BOF provides function to check whether beginning of list. +func (tk *Tokens) BOF() bool { + return tk.Index <= 0 +} + +// EOF provides function to check whether end of list. +func (tk *Tokens) EOF() bool { + return tk.Index >= (len(tk.Items) - 1) +} + +// moveNext provides function to move the index along one. +func (tk *Tokens) moveNext() bool { + if tk.EOF() { + return false + } + tk.Index++ + return true +} + +// current return the current token. +func (tk *Tokens) current() *Token { + if tk.Index == -1 { + return nil + } + return &tk.Items[tk.Index] +} + +// next return the next token (leave the index unchanged). +func (tk *Tokens) next() *Token { + if tk.EOF() { + return nil + } + return &tk.Items[tk.Index+1] +} + +// previous return the previous token (leave the index unchanged). +func (tk *Tokens) previous() *Token { + if tk.Index < 1 { + return nil + } + return &tk.Items[tk.Index-1] +} + +// push provides function to push a token onto the stack. +func (tk *Tokens) push(token Token) { + tk.Items = append(tk.Items, token) +} + +// pop provides function to pop a token off the stack. +func (tk *Tokens) pop() Token { + if len(tk.Items) == 0 { + return Token{ + TType: TokenTypeFunction, + TSubType: TokenSubTypeStop, + } + } + t := tk.Items[len(tk.Items)-1] + tk.Items = tk.Items[:len(tk.Items)-1] + return fToken("", t.TType, TokenSubTypeStop) +} + +// token provides function to non-destructively return the top item on the +// stack. +func (tk *Tokens) token() *Token { + if len(tk.Items) > 0 { + return &tk.Items[len(tk.Items)-1] + } + return nil +} + +// value return the top token's value. +func (tk *Tokens) value() string { + if tk.token() == nil { + return "" + } + return tk.token().TValue +} + +// tp return the top token's type. +func (tk *Tokens) tp() string { + if tk.token() == nil { + return "" + } + return tk.token().TType +} + +// subtype return the top token's subtype. +func (tk *Tokens) subtype() string { + if tk.token() == nil { + return "" + } + return tk.token().TSubType +} + +// ExcelParser provides function to parse an Excel formula into a stream of +// tokens. +func ExcelParser() Parser { + return Parser{} +} + +// getTokens return a token stream (list). +func (ps *Parser) getTokens() Tokens { + ps.Formula = strings.TrimSpace(ps.Formula) + f := []rune(ps.Formula) + if len(f) > 0 { + if string(f[0]) != "=" { + ps.Formula = "=" + ps.Formula + } + } + + // state-dependent character evaluation (order is important) + for !ps.EOF() { + + // double-quoted strings + // embeds are doubled + // end marks token + if ps.InString { + if ps.currentChar() == QuoteDouble { + if ps.nextChar() == QuoteDouble { + ps.Token += QuoteDouble + ps.Offset++ + } else { + ps.InString = false + ps.Tokens.add(ps.Token, TokenTypeOperand, TokenSubTypeText) + ps.Token = "" + } + } else { + ps.Token += ps.currentChar() + } + ps.Offset++ + continue + } + + // single-quoted strings (links) + // embeds are double + // end does not mark a token + if ps.InPath { + if ps.currentChar() == QuoteSingle { + if ps.nextChar() == QuoteSingle { + ps.Token += QuoteSingle + ps.Offset++ + } else { + ps.InPath = false + } + } else { + ps.Token += ps.currentChar() + } + ps.Offset++ + continue + } + + // bracketed strings (range offset or linked workbook name) + // no embeds (changed to "()" by Excel) + // end does not mark a token + if ps.InRange { + if ps.currentChar() == BracketClose { + ps.InRange = false + } + ps.Token += ps.currentChar() + ps.Offset++ + continue + } + + // error values + // end marks a token, determined from absolute list of values + if ps.InError { + ps.Token += ps.currentChar() + ps.Offset++ + if inStrSlice([]string{",#NULL!,", ",#DIV/0!,", ",#VALUE!,", ",#REF!,", ",#NAME?,", ",#NUM!,", ",#N/A,"}, Comma+ps.Token+Comma) != -1 { + ps.InError = false + ps.Tokens.add(ps.Token, TokenTypeOperand, TokenSubTypeError) + ps.Token = "" + } + continue + } + + // scientific notation check + if strings.ContainsAny(ps.currentChar(), OperatorsSN) && len(ps.Token) > 1 { + r, _ := regexp.Compile(`^[1-9]{1}(\.[0-9]+)?E{1}$`) + if r.MatchString(ps.Token) { + ps.Token += ps.currentChar() + ps.Offset++ + continue + } + } + + // independent character evaluation (order not important) + // establish state-dependent character evaluations + if ps.currentChar() == QuoteDouble { + if len(ps.Token) > 0 { + // not expected + ps.Tokens.add(ps.Token, TokenTypeUnknown, "") + ps.Token = "" + } + ps.InString = true + ps.Offset++ + continue + } + + if ps.currentChar() == QuoteSingle { + if len(ps.Token) > 0 { + // not expected + ps.Tokens.add(ps.Token, TokenTypeUnknown, "") + ps.Token = "" + } + ps.InPath = true + ps.Offset++ + continue + } + + if ps.currentChar() == BracketOpen { + ps.InRange = true + ps.Token += ps.currentChar() + ps.Offset++ + continue + } + + if ps.currentChar() == ErrorStart { + if len(ps.Token) > 0 { + // not expected + ps.Tokens.add(ps.Token, TokenTypeUnknown, "") + ps.Token = "" + } + ps.InError = true + ps.Token += ps.currentChar() + ps.Offset++ + continue + } + + // mark start and end of arrays and array rows + if ps.currentChar() == BraceOpen { + if len(ps.Token) > 0 { + // not expected + ps.Tokens.add(ps.Token, TokenTypeUnknown, "") + ps.Token = "" + } + ps.TokenStack.push(ps.Tokens.add("ARRAY", TokenTypeFunction, TokenSubTypeStart)) + ps.TokenStack.push(ps.Tokens.add("ARRAYROW", TokenTypeFunction, TokenSubTypeStart)) + ps.Offset++ + continue + } + + if ps.currentChar() == Semicolon { + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + ps.Token = "" + } + ps.Tokens.addRef(ps.TokenStack.pop()) + ps.Tokens.add(Comma, TokenTypeArgument, "") + ps.TokenStack.push(ps.Tokens.add("ARRAYROW", TokenTypeFunction, TokenSubTypeStart)) + ps.Offset++ + continue + } + + if ps.currentChar() == BraceClose { + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + ps.Token = "" + } + ps.Tokens.addRef(ps.TokenStack.pop()) + ps.Tokens.addRef(ps.TokenStack.pop()) + ps.Offset++ + continue + } + + // trim white-space + if ps.currentChar() == Whitespace { + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + ps.Token = "" + } + ps.Tokens.add("", TokenTypeWhitespace, "") + ps.Offset++ + for (ps.currentChar() == Whitespace) && (!ps.EOF()) { + ps.Offset++ + } + continue + } + + // multi-character comparators + if inStrSlice([]string{",>=,", ",<=,", ",<>,"}, Comma+ps.doubleChar()+Comma) != -1 { + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + ps.Token = "" + } + ps.Tokens.add(ps.doubleChar(), TokenTypeOperatorInfix, TokenSubTypeLogical) + ps.Offset += 2 + continue + } + + // standard infix operators + if strings.ContainsAny(OperatorsInfix, ps.currentChar()) { + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + ps.Token = "" + } + ps.Tokens.add(ps.currentChar(), TokenTypeOperatorInfix, "") + ps.Offset++ + continue + } + + // standard postfix operators + if ps.currentChar() == OperatorsPostfix { + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + ps.Token = "" + } + ps.Tokens.add(ps.currentChar(), TokenTypeOperatorPostfix, "") + ps.Offset++ + continue + } + + // start subexpression or function + if ps.currentChar() == ParenOpen { + if len(ps.Token) > 0 { + ps.TokenStack.push(ps.Tokens.add(ps.Token, TokenTypeFunction, TokenSubTypeStart)) + ps.Token = "" + } else { + ps.TokenStack.push(ps.Tokens.add("", TokenTypeSubexpression, TokenSubTypeStart)) + } + ps.Offset++ + continue + } + + // function, subexpression, array parameters + if ps.currentChar() == Comma { + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + ps.Token = "" + } + if ps.TokenStack.tp() != TokenTypeFunction { + ps.Tokens.add(ps.currentChar(), TokenTypeOperatorInfix, TokenSubTypeUnion) + } else { + ps.Tokens.add(ps.currentChar(), TokenTypeArgument, "") + } + ps.Offset++ + continue + } + + // stop subexpression + if ps.currentChar() == ParenClose { + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + ps.Token = "" + } + ps.Tokens.addRef(ps.TokenStack.pop()) + ps.Offset++ + continue + } + + // token accumulation + ps.Token += ps.currentChar() + ps.Offset++ + } + + // dump remaining accumulation + if len(ps.Token) > 0 { + ps.Tokens.add(ps.Token, TokenTypeOperand, "") + } + + // move all tokens to a new collection, excluding all unnecessary white-space tokens + tokens2 := fTokens() + + for ps.Tokens.moveNext() { + token := ps.Tokens.current() + + if token.TType == TokenTypeWhitespace { + if ps.Tokens.BOF() || ps.Tokens.EOF() { + } else if !(((ps.Tokens.previous().TType == TokenTypeFunction) && (ps.Tokens.previous().TSubType == TokenSubTypeStop)) || ((ps.Tokens.previous().TType == TokenTypeSubexpression) && (ps.Tokens.previous().TSubType == TokenSubTypeStop)) || (ps.Tokens.previous().TType == TokenTypeOperand)) { + } else if !(((ps.Tokens.next().TType == TokenTypeFunction) && (ps.Tokens.next().TSubType == TokenSubTypeStart)) || ((ps.Tokens.next().TType == TokenTypeSubexpression) && (ps.Tokens.next().TSubType == TokenSubTypeStart)) || (ps.Tokens.next().TType == TokenTypeOperand)) { + } else { + tokens2.add(token.TValue, TokenTypeOperatorInfix, TokenSubTypeIntersection) + } + continue + } + + tokens2.addRef(Token{ + TValue: token.TValue, + TType: token.TType, + TSubType: token.TSubType, + }) + } + + // switch infix "-" operator to prefix when appropriate, switch infix "+" + // operator to noop when appropriate, identify operand and infix-operator + // subtypes, pull "@" from in front of function names + for tokens2.moveNext() { + token := tokens2.current() + if (token.TType == TokenTypeOperatorInfix) && (token.TValue == "-") { + if tokens2.BOF() { + token.TType = TokenTypeOperatorPrefix + } else if ((tokens2.previous().TType == TokenTypeFunction) && (tokens2.previous().TSubType == TokenSubTypeStop)) || ((tokens2.previous().TType == TokenTypeSubexpression) && (tokens2.previous().TSubType == TokenSubTypeStop)) || (tokens2.previous().TType == TokenTypeOperatorPostfix) || (tokens2.previous().TType == TokenTypeOperand) { + token.TSubType = TokenSubTypeMath + } else { + token.TType = TokenTypeOperatorPrefix + } + continue + } + + if (token.TType == TokenTypeOperatorInfix) && (token.TValue == "+") { + if tokens2.BOF() { + token.TType = TokenTypeNoop + } else if (tokens2.previous().TType == TokenTypeFunction) && (tokens2.previous().TSubType == TokenSubTypeStop) || ((tokens2.previous().TType == TokenTypeSubexpression) && (tokens2.previous().TSubType == TokenSubTypeStop) || (tokens2.previous().TType == TokenTypeOperatorPostfix) || (tokens2.previous().TType == TokenTypeOperand)) { + token.TSubType = TokenSubTypeMath + } else { + token.TType = TokenTypeNoop + } + continue + } + + if (token.TType == TokenTypeOperatorInfix) && (len(token.TSubType) == 0) { + if strings.ContainsAny(token.TValue[0:1], "<>=") { + token.TSubType = TokenSubTypeLogical + } else if token.TValue == "&" { + token.TSubType = TokenSubTypeConcatenation + } else { + token.TSubType = TokenSubTypeMath + } + continue + } + + if (token.TType == TokenTypeOperand) && (len(token.TSubType) == 0) { + if _, err := strconv.ParseFloat(token.TValue, 64); err != nil { + if (token.TValue == "TRUE") || (token.TValue == "FALSE") { + token.TSubType = TokenSubTypeLogical + } else { + token.TSubType = TokenSubTypeRange + } + } else { + token.TSubType = TokenSubTypeNumber + } + continue + } + + if token.TType == TokenTypeFunction { + if (len(token.TValue) > 0) && token.TValue[0:1] == "@" { + token.TValue = token.TValue[1:] + } + continue + } + } + + tokens2.reset() + + // move all tokens to a new collection, excluding all no-ops + tokens := fTokens() + for tokens2.moveNext() { + if tokens2.current().TType != TokenTypeNoop { + tokens.addRef(Token{ + TValue: tokens2.current().TValue, + TType: tokens2.current().TType, + TSubType: tokens2.current().TSubType, + }) + } + } + + tokens.reset() + return tokens +} + +// doubleChar provides function to get two characters after the current +// position. +func (ps *Parser) doubleChar() string { + if len([]rune(ps.Formula)) >= ps.Offset+2 { + return string([]rune(ps.Formula)[ps.Offset : ps.Offset+2]) + } + return "" +} + +// currentChar provides function to get the character of the current position. +func (ps *Parser) currentChar() string { + return string([]rune(ps.Formula)[ps.Offset]) +} + +// nextChar provides function to get the next character of the current position. +func (ps *Parser) nextChar() string { + if len([]rune(ps.Formula)) >= ps.Offset+2 { + return string([]rune(ps.Formula)[ps.Offset+1 : ps.Offset+2]) + } + return "" +} + +// EOF provides function to check whether end of tokens stack. +func (ps *Parser) EOF() bool { + return ps.Offset >= len([]rune(ps.Formula)) +} + +// Parse provides function to parse formula as a token stream (list). +func (ps *Parser) Parse(formula string) []Token { + ps.Formula = formula + ps.Tokens = ps.getTokens() + return ps.Tokens.Items +} + +// PrettyPrint provides function to pretty the parsed result with the indented +// format. +func (ps *Parser) PrettyPrint() string { + indent := 0 + output := "" + for _, t := range ps.Tokens.Items { + if t.TSubType == TokenSubTypeStop { + indent-- + } + for i := 0; i < indent; i++ { + output += "\t" + } + output += t.TValue + " <" + t.TType + "> <" + t.TSubType + ">" + "\n" + if t.TSubType == TokenSubTypeStart { + indent++ + } + } + return output +} + +// Render provides function to get formatted formula after parsed. +func (ps *Parser) Render() string { + output := "" + for _, t := range ps.Tokens.Items { + if t.TType == TokenTypeFunction && t.TSubType == TokenSubTypeStart { + output += t.TValue + ParenOpen + } else if t.TType == TokenTypeFunction && t.TSubType == TokenSubTypeStop { + output += ParenClose + } else if t.TType == TokenTypeSubexpression && t.TSubType == TokenSubTypeStart { + output += ParenOpen + } else if t.TType == TokenTypeSubexpression && t.TSubType == TokenSubTypeStop { + output += ParenClose + } else if t.TType == TokenTypeOperand && t.TSubType == TokenSubTypeText { + output += QuoteDouble + t.TValue + QuoteDouble + } else if t.TType == TokenTypeOperatorInfix && t.TSubType == TokenSubTypeIntersection { + output += Whitespace + } else { + output += t.TValue + } + } + return output +} + +// inStrSlice provides a method to check if an element is present in an array, +// and return the index of its location, otherwise return -1. +func inStrSlice(a []string, x string) int { + for idx, n := range a { + if x == n { + return idx + } + } + return -1 +} diff --git a/vendor/github.com/xuri/efp/go.mod b/vendor/github.com/xuri/efp/go.mod new file mode 100644 index 00000000..c4e6e6f2 --- /dev/null +++ b/vendor/github.com/xuri/efp/go.mod @@ -0,0 +1,3 @@ +module github.com/xuri/efp + +go 1.11 diff --git a/vendor/github.com/xuri/excelize/v2/.gitignore b/vendor/github.com/xuri/excelize/v2/.gitignore new file mode 100644 index 00000000..4dce7680 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/.gitignore @@ -0,0 +1,14 @@ +~$*.xlsx +test/Test*.xlam +test/Test*.xlsm +test/Test*.xlsx +test/Test*.xltm +test/Test*.xltx +# generated files +test/BadEncrypt.xlsx +test/BadWorkbook.SaveAsEmptyStruct.xlsx +test/*.png +test/excelize-* +*.out +*.test +.idea diff --git a/vendor/github.com/xuri/excelize/v2/CODE_OF_CONDUCT.md b/vendor/github.com/xuri/excelize/v2/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..572b5612 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [xuri.me](https://xuri.me). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct][version] + +[homepage]: https://www.contributor-covenant.org +[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct diff --git a/vendor/github.com/xuri/excelize/v2/CONTRIBUTING.md b/vendor/github.com/xuri/excelize/v2/CONTRIBUTING.md new file mode 100644 index 00000000..89bc60ed --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/CONTRIBUTING.md @@ -0,0 +1,465 @@ +# Contributing to excelize + +Want to hack on excelize? Awesome! This page contains information about reporting issues as well as some tips and +guidelines useful to experienced open source contributors. Finally, make sure +you read our [community guidelines](#community-guidelines) before you +start participating. + +## Topics + +* [Reporting Security Issues](#reporting-security-issues) +* [Design and Cleanup Proposals](#design-and-cleanup-proposals) +* [Reporting Issues](#reporting-other-issues) +* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines) +* [Community Guidelines](#community-guidelines) + +## Reporting security issues + +The excelize maintainers take security seriously. If you discover a security +issue, please bring it to their attention right away! + +Please **DO NOT** file a public issue, instead send your report privately to +[xuri.me](https://xuri.me). + +Security reports are greatly appreciated and we will publicly thank you for it. +We currently do not offer a paid security bounty program, but are not +ruling it out in the future. + +## Reporting other issues + +A great way to contribute to the project is to send a detailed report when you +encounter an issue. We always appreciate a well-written, thorough bug report, +and will thank you for it! + +Check that [our issue database](https://github.com/xuri/excelize/issues) +doesn't already include that problem or suggestion before submitting an issue. +If you find a match, you can use the "subscribe" button to get notified on +updates. Do *not* leave random "+1" or "I have this too" comments, as they +only clutter the discussion, and don't help resolving it. However, if you +have ways to reproduce the issue or have additional information that may help +resolving the issue, please leave a comment. + +When reporting issues, always include the output of `go env`. + +Also include the steps required to reproduce the problem if possible and +applicable. This information will help us review and fix your issue faster. +When sending lengthy log-files, consider posting them as a gist [https://gist.github.com](https://gist.github.com). +Don't forget to remove sensitive data from your logfiles before posting (you can +replace those parts with "REDACTED"). + +## Quick contribution tips and guidelines + +This section gives the experienced contributor some tips and guidelines. + +### Pull requests are always welcome + +Not sure if that typo is worth a pull request? Found a bug and know how to fix +it? Do it! We will appreciate it. Any significant improvement should be +documented as [a GitHub issue](https://github.com/xuri/excelize/issues) before +anybody starts working on it. + +We are always thrilled to receive pull requests. We do our best to process them +quickly. If your pull request is not accepted on the first try, +don't get discouraged! + +### Design and cleanup proposals + +You can propose new designs for existing excelize features. You can also design +entirely new features. We really appreciate contributors who want to refactor or +otherwise cleanup our project. + +We try hard to keep excelize lean and focused. Excelize can't do everything for +everybody. This means that we might decide against incorporating a new feature. +However, there might be a way to implement that feature *on top of* excelize. + +### Conventions + +Fork the repository and make changes on your fork in a feature branch: + +* If it's a bug fix branch, name it XXXX-something where XXXX is the number of + the issue. +* If it's a feature branch, create an enhancement issue to announce + your intentions, and name it XXXX-something where XXXX is the number of the + issue. + +Submit unit tests for your changes. Go has a great test framework built in; use +it! Take a look at existing tests for inspiration. Run the full test on your branch before +submitting a pull request. + +Update the documentation when creating or modifying features. Test your +documentation changes for clarity, concision, and correctness, as well as a +clean documentation build. + +Write clean code. Universally formatted code promotes ease of writing, reading, +and maintenance. Always run `gofmt -s -w file.go` on each changed file before +committing your changes. Most editors have plug-ins that do this automatically. + +Pull request descriptions should be as clear as possible and include a reference +to all the issues that they address. + +### Successful Changes + +Before contributing large or high impact changes, make the effort to coordinate +with the maintainers of the project before submitting a pull request. This +prevents you from doing extra work that may or may not be merged. + +Large PRs that are just submitted without any prior communication are unlikely +to be successful. + +While pull requests are the methodology for submitting changes to code, changes +are much more likely to be accepted if they are accompanied by additional +engineering work. While we don't define this explicitly, most of these goals +are accomplished through communication of the design goals and subsequent +solutions. Often times, it helps to first state the problem before presenting +solutions. + +Typically, the best methods of accomplishing this are to submit an issue, +stating the problem. This issue can include a problem statement and a +checklist with requirements. If solutions are proposed, alternatives should be +listed and eliminated. Even if the criteria for elimination of a solution is +frivolous, say so. + +Larger changes typically work best with design documents. These are focused on +providing context to the design at the time the feature was conceived and can +inform future documentation contributions. + +### Commit Messages + +Commit messages must start with a capitalized and short summary +written in the imperative, followed by an optional, more detailed explanatory +text which is separated from the summary by an empty line. + +Commit messages should follow best practices, including explaining the context +of the problem and how it was solved, including in caveats or follow up changes +required. They should tell the story of the change and provide readers +understanding of what led to it. + +In practice, the best approach to maintaining a nice commit message is to +leverage a `git add -p` and `git commit --amend` to formulate a solid +changeset. This allows one to piece together a change, as information becomes +available. + +If you squash a series of commits, don't just submit that. Re-write the commit +message, as if the series of commits was a single stroke of brilliance. + +That said, there is no requirement to have a single commit for a PR, as long as +each commit tells the story. For example, if there is a feature that requires a +package, it might make sense to have the package in a separate commit then have +a subsequent commit that uses it. + +Remember, you're telling part of the story with the commit message. Don't make +your chapter weird. + +### Review + +Code review comments may be added to your pull request. Discuss, then make the +suggested modifications and push additional commits to your feature branch. Post +a comment after pushing. New commits show up in the pull request automatically, +but the reviewers are notified only when you comment. + +Pull requests must be cleanly rebased on top of master without multiple branches +mixed into the PR. + +**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your +feature branch to update your pull request rather than `merge master`. + +Before you make a pull request, squash your commits into logical units of work +using `git rebase -i` and `git push -f`. A logical unit of work is a consistent +set of patches that should be reviewed together: for example, upgrading the +version of a vendored dependency and taking advantage of its now available new +feature constitute two separate units of work. Implementing a new function and +calling it in another file constitute a single logical unit of work. The very +high majority of submissions should have a single commit, so if in doubt: squash +down to one. + +After every commit, make sure the test passes. Include documentation +changes in the same pull request so that a revert would remove all traces of +the feature or fix. + +Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that +close an issue. Including references automatically closes the issue on a merge. + +Please see the [Coding Style](#coding-style) for further guidelines. + +### Merge approval + +The excelize maintainers use LGTM (Looks Good To Me) in comments on the code review to +indicate acceptance. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +```text +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + +```text +Signed-off-by: Ri Xu https://xuri.me +``` + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. + +### How can I become a maintainer + +First, all maintainers have 3 things + +* They share responsibility in the project's success. +* They have made a long-term, recurring time investment to improve the project. +* They spend that time doing whatever needs to be done, not necessarily what + is the most interesting or fun. + +Maintainers are often under-appreciated, because their work is harder to appreciate. +It's easy to appreciate a really cool and technically advanced feature. It's harder +to appreciate the absence of bugs, the slow but steady improvement in stability, +or the reliability of a release process. But those things distinguish a good +project from a great one. + +Don't forget: being a maintainer is a time investment. Make sure you +will have time to make yourself available. You don't have to be a +maintainer to make a difference on the project! + +If you want to become a meintainer, contact [xuri.me](https://xuri.me) and given a introduction of you. + +## Community guidelines + +We want to keep the community awesome, growing and collaborative. We need +your help to keep it that way. To help with this we've come up with some general +guidelines for the community as a whole: + +* Be nice: Be courteous, respectful and polite to fellow community members: + no regional, racial, gender, or other abuse will be tolerated. We like + nice people way better than mean ones! + +* Encourage diversity and participation: Make everyone in our community feel + welcome, regardless of their background and the extent of their + contributions, and do everything possible to encourage participation in + our community. + +* Keep it legal: Basically, don't get us in trouble. Share only content that + you own, do not share private or sensitive information, and don't break + the law. + +* Stay on topic: Make sure that you are posting to the correct channel and + avoid off-topic discussions. Remember when you update an issue or respond + to an email you are potentially sending to a large number of people. Please + consider this before you update. Also remember that nobody likes spam. + +* Don't send email to the maintainers: There's no need to send email to the + maintainers to ask them to investigate an issue or to take a look at a + pull request. Instead of sending an email, GitHub mentions should be + used to ping maintainers to review a pull request, a proposal or an + issue. + +### Guideline violations — 3 strikes method + +The point of this section is not to find opportunities to punish people, but we +do need a fair way to deal with people who are making our community suck. + +1. First occurrence: We'll give you a friendly, but public reminder that the + behavior is inappropriate according to our guidelines. + +2. Second occurrence: We will send you a private message with a warning that + any additional violations will result in removal from the community. + +3. Third occurrence: Depending on the violation, we may need to delete or ban + your account. + +**Notes:** + +* Obvious spammers are banned on first occurrence. If we don't do this, we'll + have spam all over the place. + +* Violations are forgiven after 6 months of good behavior, and we won't hold a + grudge. + +* People who commit minor infractions will get some education, rather than + hammering them in the 3 strikes process. + +* The rules apply equally to everyone in the community, no matter how much + you've contributed. + +* Extreme violations of a threatening, abusive, destructive or illegal nature + will be addressed immediately and are not subject to 3 strikes or forgiveness. + +* Contact [xuri.me](https://xuri.me) to report abuse or appeal violations. In the case of + appeals, we know that mistakes happen, and we'll work with you to come up with a + fair solution if there has been a misunderstanding. + +## Coding Style + +Unless explicitly stated, we follow all coding guidelines from the Go +community. While some of these standards may seem arbitrary, they somehow seem +to result in a solid, consistent codebase. + +It is possible that the code base does not currently comply with these +guidelines. We are not looking for a massive PR that fixes this, since that +goes against the spirit of the guidelines. All new contributions should make a +best effort to clean up and make the code base better than they left it. +Obviously, apply your best judgement. Remember, the goal here is to make the +code base easier for humans to navigate and understand. Always keep that in +mind when nudging others to comply. + +The rules: + +1. All code should be formatted with `gofmt -s`. +2. All code should pass the default levels of + [`golint`](https://github.com/golang/lint). +3. All code should follow the guidelines covered in [Effective + Go](http://golang.org/doc/effective_go.html) and [Go Code Review + Comments](https://github.com/golang/go/wiki/CodeReviewComments). +4. Comment the code. Tell us the why, the history and the context. +5. Document _all_ declarations and methods, even private ones. Declare + expectations, caveats and anything else that may be important. If a type + gets exported, having the comments already there will ensure it's ready. +6. Variable name length should be proportional to its context and no longer. + `noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`. + In practice, short methods will have short variable names and globals will + have longer names. +7. No underscores in package names. If you need a compound name, step back, + and re-examine why you need a compound name. If you still think you need a + compound name, lose the underscore. +8. No utils or helpers packages. If a function is not general enough to + warrant its own package, it has not been written generally enough to be a + part of a util package. Just leave it unexported and well-documented. +9. All tests should run with `go test` and outside tooling should not be + required. No, we don't need another unit testing framework. Assertion + packages are acceptable if they provide _real_ incremental value. +10. Even though we call these "rules" above, they are actually just + guidelines. Since you've read all the rules, you now know that. + +If you are having trouble getting into the mood of idiomatic Go, we recommend +reading through [Effective Go](https://golang.org/doc/effective_go.html). The +[Go Blog](https://blog.golang.org) is also a great resource. Drinking the +kool-aid is a lot easier than going thirsty. + +## Code Review Comments and Effective Go Guidelines + +[CodeLingo](https://codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://golang.org/doc/effective_go.html) and [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments). + +### Package Comment + +Every package should have a package comment, a block comment preceding the package clause. +For multi-file packages, the package comment only needs to be present in one file, and any one will do. +The package comment should introduce the package and provide information relevant to the package as a +whole. It will appear first on the godoc page and should set up the detailed documentation that follows. + +### Single Method Interface Name + +By convention, one-method interfaces are named by the method name plus an -er suffix +or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc. + +There are a number of such names and it's productive to honor them and the function names they capture. +Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion, +don't give your method one of those names unless it has the same signature and meaning. Conversely, +if your type implements a method with the same meaning as a method on a well-known type, give it the +same name and signature; call your string-converter method String not ToString. + +### Avoid Annotations in Comments + +Comments do not need extra formatting such as banners of stars. The generated output +may not even be presented in a fixed-width font, so don't depend on spacing for alignment—godoc, +like gofmt, takes care of that. The comments are uninterpreted plain text, so HTML and other +annotations such as _this_ will reproduce verbatim and should not be used. One adjustment godoc +does do is to display indented text in a fixed-width font, suitable for program snippets. +The package comment for the fmt package uses this to good effect. + +### Comment First Word as Subject + +Doc comments work best as complete sentences, which allow a wide variety of automated presentations. +The first sentence should be a one-sentence summary that starts with the name being declared. + +### Good Package Name + +It's helpful if everyone using the package can use the same name +to refer to its contents, which implies that the package name should +be good: short, concise, evocative. By convention, packages are +given lower case, single-word names; there should be no need for +underscores or mixedCaps. Err on the side of brevity, since everyone +using your package will be typing that name. And don't worry about +collisions a priori. The package name is only the default name for +imports; it need not be unique across all source code, and in the +rare case of a collision the importing package can choose a different +name to use locally. In any case, confusion is rare because the file +name in the import determines just which package is being used. + +### Avoid Renaming Imports + +Avoid renaming imports except to avoid a name collision; good package names +should not require renaming. In the event of collision, prefer to rename the +most local or project-specific import. + +### Context as First Argument + +Values of the context.Context type carry security credentials, tracing information, +deadlines, and cancellation signals across API and process boundaries. Go programs +pass Contexts explicitly along the entire function call chain from incoming RPCs +and HTTP requests to outgoing requests. + +Most functions that use a Context should accept it as their first parameter. + +### Do Not Discard Errors + +Do not discard errors using _ variables. If a function returns an error, +check it to make sure the function succeeded. Handle the error, return it, or, +in truly exceptional situations, panic. + +### Go Error Format + +Error strings should not be capitalized (unless beginning with proper nouns +or acronyms) or end with punctuation, since they are usually printed following +other context. That is, use fmt.Errorf("something bad") not fmt.Errorf("Something bad"), +so that log.Printf("Reading %s: %v", filename, err) formats without a spurious +capital letter mid-message. This does not apply to logging, which is implicitly +line-oriented and not combined inside other messages. + +### Use Crypto Rand + +Do not use package math/rand to generate keys, even +throwaway ones. Unseeded, the generator is completely predictable. +Seeded with time.Nanoseconds(), there are just a few bits of entropy. +Instead, use crypto/rand's Reader, and if you need text, print to +hexadecimal or base64. diff --git a/vendor/github.com/xuri/excelize/v2/LICENSE b/vendor/github.com/xuri/excelize/v2/LICENSE new file mode 100644 index 00000000..10897e7d --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2016-2022 The excelize Authors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/github.com/xuri/excelize/v2/PULL_REQUEST_TEMPLATE.md b/vendor/github.com/xuri/excelize/v2/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..d2ac755e --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,45 @@ +# PR Details + + + +## Description + + + +## Related Issue + + + + + + +## Motivation and Context + + + +## How Has This Been Tested + + + + + +## Types of changes + + + +- [ ] Docs change / refactoring / dependency upgrade +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +## Checklist + + + + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have read the **CONTRIBUTING** document. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. diff --git a/vendor/github.com/xuri/excelize/v2/README.md b/vendor/github.com/xuri/excelize/v2/README.md new file mode 100644 index 00000000..8e16a88b --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/README.md @@ -0,0 +1,231 @@ +

Excelize logo

+ +

+ Build Status + Code Coverage + Go Report Card + go.dev + Licenses + Donate +

+ +# Excelize + +## Introduction + +Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports complex components by high compatibility, and provided streaming API for generating or reading data from a worksheet with huge amounts of data. This library needs Go version 1.15 or later. The full API docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) and [docs reference](https://xuri.me/excelize/). + +## Basic Usage + +### Installation + +```bash +go get github.com/xuri/excelize +``` + +- If your packages are managed using [Go Modules](https://go.dev/blog/using-go-modules), please install with following command. + +```bash +go get github.com/xuri/excelize/v2 +``` + +### Create spreadsheet + +Here is a minimal example usage that will create spreadsheet file. + +```go +package main + +import ( + "fmt" + + "github.com/xuri/excelize/v2" +) + +func main() { + f := excelize.NewFile() + // Create a new sheet. + index := f.NewSheet("Sheet2") + // Set value of a cell. + f.SetCellValue("Sheet2", "A2", "Hello world.") + f.SetCellValue("Sheet1", "B2", 100) + // Set active sheet of the workbook. + f.SetActiveSheet(index) + // Save spreadsheet by the given path. + if err := f.SaveAs("Book1.xlsx"); err != nil { + fmt.Println(err) + } +} +``` + +### Reading spreadsheet + +The following constitutes the bare to read a spreadsheet document. + +```go +package main + +import ( + "fmt" + + "github.com/xuri/excelize/v2" +) + +func main() { + f, err := excelize.OpenFile("Book1.xlsx") + if err != nil { + fmt.Println(err) + return + } + defer func() { + // Close the spreadsheet. + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() + // Get value from cell by given worksheet name and axis. + cell, err := f.GetCellValue("Sheet1", "B2") + if err != nil { + fmt.Println(err) + return + } + fmt.Println(cell) + // Get all the rows in the Sheet1. + rows, err := f.GetRows("Sheet1") + if err != nil { + fmt.Println(err) + return + } + for _, row := range rows { + for _, colCell := range row { + fmt.Print(colCell, "\t") + } + fmt.Println() + } +} +``` + +### Add chart to spreadsheet file + +With Excelize chart generation and management is as easy as a few lines of code. You can build charts based on data in your worksheet or generate charts without any data in your worksheet at all. + +

Excelize

+ +```go +package main + +import ( + "fmt" + + "github.com/xuri/excelize/v2" +) + +func main() { + categories := map[string]string{ + "A2": "Small", "A3": "Normal", "A4": "Large", + "B1": "Apple", "C1": "Orange", "D1": "Pear"} + values := map[string]int{ + "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} + f := excelize.NewFile() + for k, v := range categories { + f.SetCellValue("Sheet1", k, v) + } + for k, v := range values { + f.SetCellValue("Sheet1", k, v) + } + if err := f.AddChart("Sheet1", "E1", `{ + "type": "col3DClustered", + "series": [ + { + "name": "Sheet1!$A$2", + "categories": "Sheet1!$B$1:$D$1", + "values": "Sheet1!$B$2:$D$2" + }, + { + "name": "Sheet1!$A$3", + "categories": "Sheet1!$B$1:$D$1", + "values": "Sheet1!$B$3:$D$3" + }, + { + "name": "Sheet1!$A$4", + "categories": "Sheet1!$B$1:$D$1", + "values": "Sheet1!$B$4:$D$4" + }], + "title": + { + "name": "Fruit 3D Clustered Column Chart" + } + }`); err != nil { + fmt.Println(err) + return + } + // Save spreadsheet by the given path. + if err := f.SaveAs("Book1.xlsx"); err != nil { + fmt.Println(err) + } +} +``` + +### Add picture to spreadsheet file + +```go +package main + +import ( + "fmt" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + + "github.com/xuri/excelize/v2" +) + +func main() { + f, err := excelize.OpenFile("Book1.xlsx") + if err != nil { + fmt.Println(err) + return + } + defer func() { + // Close the spreadsheet. + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() + // Insert a picture. + if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil { + fmt.Println(err) + } + // Insert a picture to worksheet with scaling. + if err := f.AddPicture("Sheet1", "D2", "image.jpg", + `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil { + fmt.Println(err) + } + // Insert a picture offset in the cell with printing support. + if err := f.AddPicture("Sheet1", "H2", "image.gif", `{ + "x_offset": 15, + "y_offset": 10, + "print_obj": true, + "lock_aspect_ratio": false, + "locked": false + }`); err != nil { + fmt.Println(err) + } + // Save the spreadsheet with the origin path. + if err = f.Save(); err != nil { + fmt.Println(err) + } +} +``` + +## Contributing + +Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. XML is compliant with [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/). + +## Licenses + +This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause). + +The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation. + +gopher.{ai,svg,png} was created by [Takuya Ueda](https://twitter.com/tenntenn). Licensed under the [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/). diff --git a/vendor/github.com/xuri/excelize/v2/README_zh.md b/vendor/github.com/xuri/excelize/v2/README_zh.md new file mode 100644 index 00000000..dafdd93f --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/README_zh.md @@ -0,0 +1,231 @@ +

Excelize logo

+ +

+ Build Status + Code Coverage + Go Report Card + go.dev + Licenses + Donate +

+ +# Excelize + +## 简介 + +Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写 API,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.15 或更高版本,完整的 API 使用文档请访问 [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) 或查看 [参考文档](https://xuri.me/excelize/)。 + +## 快速上手 + +### 安装 + +```bash +go get github.com/xuri/excelize +``` + +- 如果您使用 [Go Modules](https://go.dev/blog/using-go-modules) 管理软件包,请使用下面的命令来安装最新版本。 + +```bash +go get github.com/xuri/excelize/v2 +``` + +### 创建 Excel 文档 + +下面是一个创建 Excel 文档的简单例子: + +```go +package main + +import ( + "fmt" + + "github.com/xuri/excelize/v2" +) + +func main() { + f := excelize.NewFile() + // 创建一个工作表 + index := f.NewSheet("Sheet2") + // 设置单元格的值 + f.SetCellValue("Sheet2", "A2", "Hello world.") + f.SetCellValue("Sheet1", "B2", 100) + // 设置工作簿的默认工作表 + f.SetActiveSheet(index) + // 根据指定路径保存文件 + if err := f.SaveAs("Book1.xlsx"); err != nil { + fmt.Println(err) + } +} +``` + +### 读取 Excel 文档 + +下面是读取 Excel 文档的例子: + +```go +package main + +import ( + "fmt" + + "github.com/xuri/excelize/v2" +) + +func main() { + f, err := excelize.OpenFile("Book1.xlsx") + if err != nil { + fmt.Println(err) + return + } + defer func() { + // 关闭工作簿 + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() + // 获取工作表中指定单元格的值 + cell, err := f.GetCellValue("Sheet1", "B2") + if err != nil { + fmt.Println(err) + return + } + fmt.Println(cell) + // 获取 Sheet1 上所有单元格 + rows, err := f.GetRows("Sheet1") + if err != nil { + fmt.Println(err) + return + } + for _, row := range rows { + for _, colCell := range row { + fmt.Print(colCell, "\t") + } + fmt.Println() + } +} +``` + +### 在 Excel 文档中创建图表 + +使用 Excelize 生成图表十分简单,仅需几行代码。您可以根据工作表中的已有数据构建图表,或向工作表中添加数据并创建图表。 + +

使用 Excelize 在 Excel 电子表格文档中创建图表

+ +```go +package main + +import ( + "fmt" + + "github.com/xuri/excelize/v2" +) + +func main() { + categories := map[string]string{ + "A2": "Small", "A3": "Normal", "A4": "Large", + "B1": "Apple", "C1": "Orange", "D1": "Pear"} + values := map[string]int{ + "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} + f := excelize.NewFile() + for k, v := range categories { + f.SetCellValue("Sheet1", k, v) + } + for k, v := range values { + f.SetCellValue("Sheet1", k, v) + } + if err := f.AddChart("Sheet1", "E1", `{ + "type": "col3DClustered", + "series": [ + { + "name": "Sheet1!$A$2", + "categories": "Sheet1!$B$1:$D$1", + "values": "Sheet1!$B$2:$D$2" + }, + { + "name": "Sheet1!$A$3", + "categories": "Sheet1!$B$1:$D$1", + "values": "Sheet1!$B$3:$D$3" + }, + { + "name": "Sheet1!$A$4", + "categories": "Sheet1!$B$1:$D$1", + "values": "Sheet1!$B$4:$D$4" + }], + "title": + { + "name": "Fruit 3D Clustered Column Chart" + } + }`); err != nil { + fmt.Println(err) + return + } + // 根据指定路径保存文件 + if err := f.SaveAs("Book1.xlsx"); err != nil { + fmt.Println(err) + } +} +``` + +### 向 Excel 文档中插入图片 + +```go +package main + +import ( + "fmt" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + + "github.com/xuri/excelize/v2" +) + +func main() { + f, err := excelize.OpenFile("Book1.xlsx") + if err != nil { + fmt.Println(err) + return + } + defer func() { + // 关闭工作簿 + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() + // 插入图片 + if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil { + fmt.Println(err) + } + // 在工作表中插入图片,并设置图片的缩放比例 + if err := f.AddPicture("Sheet1", "D2", "image.jpg", + `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil { + fmt.Println(err) + } + // 在工作表中插入图片,并设置图片的打印属性 + if err := f.AddPicture("Sheet1", "H2", "image.gif", `{ + "x_offset": 15, + "y_offset": 10, + "print_obj": true, + "lock_aspect_ratio": false, + "locked": false + }`); err != nil { + fmt.Println(err) + } + // 保存工作簿 + if err = f.Save(); err != nil { + fmt.Println(err) + } +} +``` + +## 社区合作 + +欢迎您为此项目贡献代码,提出建议或问题、修复 Bug 以及参与讨论对新功能的想法。 XML 符合标准: [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/)。 + +## 开源许可 + +本项目遵循 BSD 3-Clause 开源许可协议,访问 [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) 查看许可协议文件。 + +Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。 + +gopher.{ai,svg,png} 由 [Takuya Ueda](https://twitter.com/tenntenn) 创作,遵循 [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/) 创作共用授权条款。 diff --git a/vendor/github.com/xuri/excelize/v2/SECURITY.md b/vendor/github.com/xuri/excelize/v2/SECURITY.md new file mode 100644 index 00000000..9d032ded --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Supported Versions + +We will dive into any security-related issue as long as your Excelize version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information. + +## Reporting a Vulnerability + +Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you! diff --git a/vendor/github.com/xuri/excelize/v2/adjust.go b/vendor/github.com/xuri/excelize/v2/adjust.go new file mode 100644 index 00000000..e1c0e15e --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/adjust.go @@ -0,0 +1,300 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +type adjustDirection bool + +const ( + columns adjustDirection = false + rows adjustDirection = true +) + +// adjustHelper provides a function to adjust rows and columns dimensions, +// hyperlinks, merged cells and auto filter when inserting or deleting rows or +// columns. +// +// sheet: Worksheet name that we're editing +// column: Index number of the column we're inserting/deleting before +// row: Index number of the row we're inserting/deleting before +// offset: Number of rows/column to insert/delete negative values indicate deletion +// +// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells +// +func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + sheetID := f.getSheetID(sheet) + if dir == rows { + f.adjustRowDimensions(ws, num, offset) + } else { + f.adjustColDimensions(ws, num, offset) + } + f.adjustHyperlinks(ws, sheet, dir, num, offset) + if err = f.adjustMergeCells(ws, dir, num, offset); err != nil { + return err + } + if err = f.adjustAutoFilter(ws, dir, num, offset); err != nil { + return err + } + if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil { + return err + } + checkSheet(ws) + _ = checkRow(ws) + + if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 { + ws.MergeCells = nil + } + + return nil +} + +// adjustColDimensions provides a function to update column dimensions when +// inserting or deleting rows or columns. +func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) { + for rowIdx := range ws.SheetData.Row { + for colIdx, v := range ws.SheetData.Row[rowIdx].C { + cellCol, cellRow, _ := CellNameToCoordinates(v.R) + if col <= cellCol { + if newCol := cellCol + offset; newCol > 0 { + ws.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow) + } + } + } + } +} + +// adjustRowDimensions provides a function to update row dimensions when +// inserting or deleting rows or columns. +func (f *File) adjustRowDimensions(ws *xlsxWorksheet, row, offset int) { + for i := range ws.SheetData.Row { + r := &ws.SheetData.Row[i] + if newRow := r.R + offset; r.R >= row && newRow > 0 { + f.adjustSingleRowDimensions(r, newRow) + } + } +} + +// adjustSingleRowDimensions provides a function to adjust single row dimensions. +func (f *File) adjustSingleRowDimensions(r *xlsxRow, num int) { + r.R = num + for i, col := range r.C { + colName, _, _ := SplitCellName(col.R) + r.C[i].R, _ = JoinCellName(colName, num) + } +} + +// adjustHyperlinks provides a function to update hyperlinks when inserting or +// deleting rows or columns. +func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) { + // short path + if ws.Hyperlinks == nil || len(ws.Hyperlinks.Hyperlink) == 0 { + return + } + + // order is important + if offset < 0 { + for i := len(ws.Hyperlinks.Hyperlink) - 1; i >= 0; i-- { + linkData := ws.Hyperlinks.Hyperlink[i] + colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref) + + if (dir == rows && num == rowNum) || (dir == columns && num == colNum) { + f.deleteSheetRelationships(sheet, linkData.RID) + if len(ws.Hyperlinks.Hyperlink) > 1 { + ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:i], + ws.Hyperlinks.Hyperlink[i+1:]...) + } else { + ws.Hyperlinks = nil + } + } + } + } + if ws.Hyperlinks == nil { + return + } + for i := range ws.Hyperlinks.Hyperlink { + link := &ws.Hyperlinks.Hyperlink[i] // get reference + colNum, rowNum, _ := CellNameToCoordinates(link.Ref) + if dir == rows { + if rowNum >= num { + link.Ref, _ = CoordinatesToCellName(colNum, rowNum+offset) + } + } else { + if colNum >= num { + link.Ref, _ = CoordinatesToCellName(colNum+offset, rowNum) + } + } + } +} + +// adjustAutoFilter provides a function to update the auto filter when +// inserting or deleting rows or columns. +func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error { + if ws.AutoFilter == nil { + return nil + } + + coordinates, err := areaRefToCoordinates(ws.AutoFilter.Ref) + if err != nil { + return err + } + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] + + if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) { + ws.AutoFilter = nil + for rowIdx := range ws.SheetData.Row { + rowData := &ws.SheetData.Row[rowIdx] + if rowData.R > y1 && rowData.R <= y2 { + rowData.Hidden = false + } + } + return nil + } + + coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset) + x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3] + + if ws.AutoFilter.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil { + return err + } + return nil +} + +// adjustAutoFilterHelper provides a function for adjusting auto filter to +// compare and calculate cell axis by the given adjust direction, operation +// axis and offset. +func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int { + if dir == rows { + if coordinates[1] >= num { + coordinates[1] += offset + } + if coordinates[3] >= num { + coordinates[3] += offset + } + } else { + if coordinates[2] >= num { + coordinates[2] += offset + } + } + return coordinates +} + +// adjustMergeCells provides a function to update merged cells when inserting +// or deleting rows or columns. +func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error { + if ws.MergeCells == nil { + return nil + } + + for i := 0; i < len(ws.MergeCells.Cells); i++ { + areaData := ws.MergeCells.Cells[i] + coordinates, err := areaRefToCoordinates(areaData.Ref) + if err != nil { + return err + } + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] + if dir == rows { + if y1 == num && y2 == num && offset < 0 { + f.deleteMergeCell(ws, i) + i-- + continue + } + + y1, y2 = f.adjustMergeCellsHelper(y1, y2, num, offset) + } else { + if x1 == num && x2 == num && offset < 0 { + f.deleteMergeCell(ws, i) + i-- + continue + } + + x1, x2 = f.adjustMergeCellsHelper(x1, x2, num, offset) + } + if x1 == x2 && y1 == y2 { + f.deleteMergeCell(ws, i) + i-- + continue + } + areaData.rect = []int{x1, y1, x2, y2} + if areaData.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil { + return err + } + } + return nil +} + +// adjustMergeCellsHelper provides a function for adjusting merge cells to +// compare and calculate cell axis by the given pivot, operation axis and +// offset. +func (f *File) adjustMergeCellsHelper(p1, p2, num, offset int) (int, int) { + if p2 < p1 { + p1, p2 = p2, p1 + } + + if offset >= 0 { + if num <= p1 { + p1 += offset + p2 += offset + } else if num <= p2 { + p2 += offset + } + return p1, p2 + } + if num < p1 || (num == p1 && num == p2) { + p1 += offset + p2 += offset + } else if num <= p2 { + p2 += offset + } + return p1, p2 +} + +// deleteMergeCell provides a function to delete merged cell by given index. +func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) { + if idx < 0 { + return + } + if len(ws.MergeCells.Cells) > idx { + ws.MergeCells.Cells = append(ws.MergeCells.Cells[:idx], ws.MergeCells.Cells[idx+1:]...) + ws.MergeCells.Count = len(ws.MergeCells.Cells) + } +} + +// adjustCalcChain provides a function to update the calculation chain when +// inserting or deleting rows or columns. +func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) error { + if f.CalcChain == nil { + return nil + } + for index, c := range f.CalcChain.C { + if c.I != sheetID { + continue + } + colNum, rowNum, err := CellNameToCoordinates(c.R) + if err != nil { + return err + } + if dir == rows && num <= rowNum { + if newRow := rowNum + offset; newRow > 0 { + f.CalcChain.C[index].R, _ = CoordinatesToCellName(colNum, newRow) + } + } + if dir == columns && num <= colNum { + if newCol := colNum + offset; newCol > 0 { + f.CalcChain.C[index].R, _ = CoordinatesToCellName(newCol, rowNum) + } + } + } + return nil +} diff --git a/vendor/github.com/xuri/excelize/v2/calc.go b/vendor/github.com/xuri/excelize/v2/calc.go new file mode 100644 index 00000000..57b2cda8 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/calc.go @@ -0,0 +1,15824 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "container/list" + "errors" + "fmt" + "math" + "math/big" + "math/cmplx" + "math/rand" + "net/url" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" + "unicode" + "unsafe" + + "github.com/xuri/efp" + "golang.org/x/text/language" + "golang.org/x/text/message" +) + +const ( + // Excel formula errors + formulaErrorDIV = "#DIV/0!" + formulaErrorNAME = "#NAME?" + formulaErrorNA = "#N/A" + formulaErrorNUM = "#NUM!" + formulaErrorVALUE = "#VALUE!" + formulaErrorREF = "#REF!" + formulaErrorNULL = "#NULL!" + formulaErrorSPILL = "#SPILL!" + formulaErrorCALC = "#CALC!" + formulaErrorGETTINGDATA = "#GETTING_DATA" + // formula criteria condition enumeration. + _ byte = iota + criteriaEq + criteriaLe + criteriaGe + criteriaNe + criteriaL + criteriaG + criteriaErr + criteriaRegexp + + matchModeExact = 0 + matchModeMinGreater = 1 + matchModeMaxLess = -1 + matchModeWildcard = 2 + + searchModeLinear = 1 + searchModeReverseLinear = -1 + searchModeAscBinary = 2 + searchModeDescBinary = -2 + + maxFinancialIterations = 128 + financialPrecision = 1.0e-08 + // Date and time format regular expressions + monthRe = `((jan|january)|(feb|february)|(mar|march)|(apr|april)|(may)|(jun|june)|(jul|july)|(aug|august)|(sep|september)|(oct|october)|(nov|november)|(dec|december))` + df1 = `(([0-9])+)/(([0-9])+)/(([0-9])+)` + df2 = monthRe + ` (([0-9])+), (([0-9])+)` + df3 = `(([0-9])+)-(([0-9])+)-(([0-9])+)` + df4 = `(([0-9])+)-` + monthRe + `-(([0-9])+)` + datePrefix = `^((` + df1 + `|` + df2 + `|` + df3 + `|` + df4 + `) )?` + tfhh = `(([0-9])+) (am|pm)` + tfhhmm = `(([0-9])+):(([0-9])+)( (am|pm))?` + tfmmss = `(([0-9])+):(([0-9])+\.([0-9])+)( (am|pm))?` + tfhhmmss = `(([0-9])+):(([0-9])+):(([0-9])+(\.([0-9])+)?)( (am|pm))?` + timeSuffix = `( (` + tfhh + `|` + tfhhmm + `|` + tfmmss + `|` + tfhhmmss + `))?$` +) + +var ( + // tokenPriority defined basic arithmetic operator priority. + tokenPriority = map[string]int{ + "^": 5, + "*": 4, + "/": 4, + "+": 3, + "-": 3, + "=": 2, + "<>": 2, + "<": 2, + "<=": 2, + ">": 2, + ">=": 2, + "&": 1, + } + month2num = map[string]int{ + "january": 1, + "february": 2, + "march": 3, + "april": 4, + "may": 5, + "june": 6, + "july": 7, + "august": 8, + "september": 9, + "october": 10, + "november": 11, + "december": 12, + "jan": 1, + "feb": 2, + "mar": 3, + "apr": 4, + "jun": 6, + "jul": 7, + "aug": 8, + "sep": 9, + "oct": 10, + "nov": 11, + "dec": 12, + } + dateFormats = map[string]*regexp.Regexp{ + "mm/dd/yy": regexp.MustCompile(`^` + df1 + timeSuffix), + "mm dd, yy": regexp.MustCompile(`^` + df2 + timeSuffix), + "yy-mm-dd": regexp.MustCompile(`^` + df3 + timeSuffix), + "yy-mmStr-dd": regexp.MustCompile(`^` + df4 + timeSuffix), + } + timeFormats = map[string]*regexp.Regexp{ + "hh": regexp.MustCompile(datePrefix + tfhh + `$`), + "hh:mm": regexp.MustCompile(datePrefix + tfhhmm + `$`), + "mm:ss": regexp.MustCompile(datePrefix + tfmmss + `$`), + "hh:mm:ss": regexp.MustCompile(datePrefix + tfhhmmss + `$`), + } + dateOnlyFormats = []*regexp.Regexp{ + regexp.MustCompile(`^` + df1 + `$`), + regexp.MustCompile(`^` + df2 + `$`), + regexp.MustCompile(`^` + df3 + `$`), + regexp.MustCompile(`^` + df4 + `$`), + } + addressFmtMaps = map[string]func(col, row int) (string, error){ + "1_TRUE": func(col, row int) (string, error) { + return CoordinatesToCellName(col, row, true) + }, + "1_FALSE": func(col, row int) (string, error) { + return fmt.Sprintf("R%dC%d", row, col), nil + }, + "2_TRUE": func(col, row int) (string, error) { + column, err := ColumnNumberToName(col) + if err != nil { + return "", err + } + return fmt.Sprintf("%s$%d", column, row), nil + }, + "2_FALSE": func(col, row int) (string, error) { + return fmt.Sprintf("R%dC[%d]", row, col), nil + }, + "3_TRUE": func(col, row int) (string, error) { + column, err := ColumnNumberToName(col) + if err != nil { + return "", err + } + return fmt.Sprintf("$%s%d", column, row), nil + }, + "3_FALSE": func(col, row int) (string, error) { + return fmt.Sprintf("R[%d]C%d", row, col), nil + }, + "4_TRUE": func(col, row int) (string, error) { + return CoordinatesToCellName(col, row, false) + }, + "4_FALSE": func(col, row int) (string, error) { + return fmt.Sprintf("R[%d]C[%d]", row, col), nil + }, + } +) + +// cellRef defines the structure of a cell reference. +type cellRef struct { + Col int + Row int + Sheet string +} + +// cellRef defines the structure of a cell range. +type cellRange struct { + From cellRef + To cellRef +} + +// formulaCriteria defined formula criteria parser result. +type formulaCriteria struct { + Type byte + Condition string +} + +// ArgType is the type of formula argument type. +type ArgType byte + +// Formula argument types enumeration. +const ( + ArgUnknown ArgType = iota + ArgNumber + ArgString + ArgList + ArgMatrix + ArgError + ArgEmpty +) + +// formulaArg is the argument of a formula or function. +type formulaArg struct { + SheetName string + Number float64 + String string + List []formulaArg + Matrix [][]formulaArg + Boolean bool + Error string + Type ArgType + cellRefs, cellRanges *list.List +} + +// Value returns a string data type of the formula argument. +func (fa formulaArg) Value() (value string) { + switch fa.Type { + case ArgNumber: + if fa.Boolean { + if fa.Number == 0 { + return "FALSE" + } + return "TRUE" + } + return fmt.Sprintf("%g", fa.Number) + case ArgString: + return fa.String + case ArgError: + return fa.Error + } + return +} + +// ToNumber returns a formula argument with number data type. +func (fa formulaArg) ToNumber() formulaArg { + var n float64 + var err error + switch fa.Type { + case ArgString: + n, err = strconv.ParseFloat(fa.String, 64) + if err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + case ArgNumber: + n = fa.Number + } + return newNumberFormulaArg(n) +} + +// ToBool returns a formula argument with boolean data type. +func (fa formulaArg) ToBool() formulaArg { + var b bool + var err error + switch fa.Type { + case ArgString: + b, err = strconv.ParseBool(fa.String) + if err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + case ArgNumber: + if fa.Boolean && fa.Number == 1 { + b = true + } + } + return newBoolFormulaArg(b) +} + +// ToList returns a formula argument with array data type. +func (fa formulaArg) ToList() []formulaArg { + switch fa.Type { + case ArgMatrix: + var args []formulaArg + for _, row := range fa.Matrix { + args = append(args, row...) + } + return args + case ArgList: + return fa.List + case ArgNumber, ArgString, ArgError, ArgUnknown: + return []formulaArg{fa} + } + return nil +} + +// formulaFuncs is the type of the formula functions. +type formulaFuncs struct { + f *File + sheet, cell string +} + +// CalcCellValue provides a function to get calculated cell value. This +// feature is currently in working processing. Array formula, table formula +// and some other formulas are not supported currently. +// +// Supported formula functions: +// +// ABS +// ACCRINT +// ACCRINTM +// ACOS +// ACOSH +// ACOT +// ACOTH +// ADDRESS +// AMORDEGRC +// AMORLINC +// AND +// ARABIC +// ASIN +// ASINH +// ATAN +// ATAN2 +// ATANH +// AVEDEV +// AVERAGE +// AVERAGEA +// AVERAGEIF +// AVERAGEIFS +// BASE +// BESSELI +// BESSELJ +// BESSELK +// BESSELY +// BETADIST +// BETA.DIST +// BETAINV +// BETA.INV +// BIN2DEC +// BIN2HEX +// BIN2OCT +// BINOMDIST +// BINOM.DIST +// BINOM.DIST.RANGE +// BINOM.INV +// BITAND +// BITLSHIFT +// BITOR +// BITRSHIFT +// BITXOR +// CEILING +// CEILING.MATH +// CEILING.PRECISE +// CHAR +// CHIDIST +// CHIINV +// CHITEST +// CHISQ.DIST +// CHISQ.DIST.RT +// CHISQ.INV +// CHISQ.INV.RT +// CHISQ.TEST +// CHOOSE +// CLEAN +// CODE +// COLUMN +// COLUMNS +// COMBIN +// COMBINA +// COMPLEX +// CONCAT +// CONCATENATE +// CONFIDENCE +// CONFIDENCE.NORM +// CONFIDENCE.T +// CORREL +// COS +// COSH +// COT +// COTH +// COUNT +// COUNTA +// COUNTBLANK +// COUNTIF +// COUNTIFS +// COUPDAYBS +// COUPDAYS +// COUPDAYSNC +// COUPNCD +// COUPNUM +// COUPPCD +// COVAR +// COVARIANCE.P +// CRITBINOM +// CSC +// CSCH +// CUMIPMT +// CUMPRINC +// DATE +// DATEDIF +// DATEVALUE +// DAY +// DAYS +// DB +// DDB +// DEC2BIN +// DEC2HEX +// DEC2OCT +// DECIMAL +// DEGREES +// DELTA +// DEVSQ +// DISC +// DOLLARDE +// DOLLARFR +// DURATION +// EFFECT +// ENCODEURL +// ERF +// ERF.PRECISE +// ERFC +// ERFC.PRECISE +// ERROR.TYPE +// EVEN +// EXACT +// EXP +// EXPON.DIST +// EXPONDIST +// FACT +// FACTDOUBLE +// FALSE +// F.DIST +// F.DIST.RT +// FDIST +// FIND +// FINDB +// F.INV +// F.INV.RT +// FINV +// FISHER +// FISHERINV +// FIXED +// FLOOR +// FLOOR.MATH +// FLOOR.PRECISE +// FORMULATEXT +// F.TEST +// FTEST +// FV +// FVSCHEDULE +// GAMMA +// GAMMA.DIST +// GAMMADIST +// GAMMA.INV +// GAMMAINV +// GAMMALN +// GAMMALN.PRECISE +// GAUSS +// GCD +// GEOMEAN +// GESTEP +// HARMEAN +// HEX2BIN +// HEX2DEC +// HEX2OCT +// HLOOKUP +// HOUR +// HYPGEOM.DIST +// HYPGEOMDIST +// IF +// IFERROR +// IFNA +// IFS +// IMABS +// IMAGINARY +// IMARGUMENT +// IMCONJUGATE +// IMCOS +// IMCOSH +// IMCOT +// IMCSC +// IMCSCH +// IMDIV +// IMEXP +// IMLN +// IMLOG10 +// IMLOG2 +// IMPOWER +// IMPRODUCT +// IMREAL +// IMSEC +// IMSECH +// IMSIN +// IMSINH +// IMSQRT +// IMSUB +// IMSUM +// IMTAN +// INDEX +// INDIRECT +// INT +// INTRATE +// IPMT +// IRR +// ISBLANK +// ISERR +// ISERROR +// ISEVEN +// ISFORMULA +// ISLOGICAL +// ISNA +// ISNONTEXT +// ISNUMBER +// ISODD +// ISREF +// ISTEXT +// ISO.CEILING +// ISOWEEKNUM +// ISPMT +// KURT +// LARGE +// LCM +// LEFT +// LEFTB +// LEN +// LENB +// LN +// LOG +// LOG10 +// LOGINV +// LOGNORM.DIST +// LOGNORMDIST +// LOGNORM.INV +// LOOKUP +// LOWER +// MATCH +// MAX +// MAXA +// MAXIFS +// MDETERM +// MDURATION +// MEDIAN +// MID +// MIDB +// MIN +// MINA +// MINIFS +// MINUTE +// MIRR +// MOD +// MODE +// MODE.MULT +// MODE.SNGL +// MONTH +// MROUND +// MULTINOMIAL +// MUNIT +// N +// NA +// NEGBINOM.DIST +// NEGBINOMDIST +// NOMINAL +// NORM.DIST +// NORMDIST +// NORM.INV +// NORMINV +// NORM.S.DIST +// NORMSDIST +// NORM.S.INV +// NORMSINV +// NOT +// NOW +// NPER +// NPV +// OCT2BIN +// OCT2DEC +// OCT2HEX +// ODD +// ODDFPRICE +// OR +// PDURATION +// PERCENTILE.EXC +// PERCENTILE.INC +// PERCENTILE +// PERCENTRANK.EXC +// PERCENTRANK.INC +// PERCENTRANK +// PERMUT +// PERMUTATIONA +// PHI +// PI +// PMT +// POISSON.DIST +// POISSON +// POWER +// PPMT +// PRICE +// PRICEDISC +// PRICEMAT +// PRODUCT +// PROPER +// PV +// QUARTILE +// QUARTILE.EXC +// QUARTILE.INC +// QUOTIENT +// RADIANS +// RAND +// RANDBETWEEN +// RANK +// RANK.EQ +// RATE +// RECEIVED +// REPLACE +// REPLACEB +// REPT +// RIGHT +// RIGHTB +// ROMAN +// ROUND +// ROUNDDOWN +// ROUNDUP +// ROW +// ROWS +// RRI +// SEC +// SECH +// SECOND +// SERIESSUM +// SHEET +// SHEETS +// SIGN +// SIN +// SINH +// SKEW +// SLN +// SMALL +// SQRT +// SQRTPI +// STANDARDIZE +// STDEV +// STDEV.P +// STDEV.S +// STDEVA +// STDEVP +// SUBSTITUTE +// SUM +// SUMIF +// SUMIFS +// SUMPRODUCT +// SUMSQ +// SUMX2MY2 +// SUMX2PY2 +// SUMXMY2 +// SWITCH +// SYD +// T +// TAN +// TANH +// TBILLEQ +// TBILLPRICE +// TBILLYIELD +// T.DIST +// T.DIST.2T +// T.DIST.RT +// TDIST +// TEXTJOIN +// TIME +// TIMEVALUE +// T.INV +// T.INV.2T +// TINV +// TODAY +// TRANSPOSE +// TRIM +// TRIMMEAN +// TRUE +// TRUNC +// T.TEST +// TTEST +// TYPE +// UNICHAR +// UNICODE +// UPPER +// VALUE +// VAR +// VAR.P +// VAR.S +// VARA +// VARP +// VARPA +// VDB +// VLOOKUP +// WEEKDAY +// WEIBULL +// WEIBULL.DIST +// XIRR +// XLOOKUP +// XNPV +// XOR +// YEAR +// YEARFRAC +// YIELD +// YIELDDISC +// YIELDMAT +// Z.TEST +// ZTEST +// +func (f *File) CalcCellValue(sheet, cell string) (result string, err error) { + var ( + formula string + token efp.Token + ) + if formula, err = f.GetCellFormula(sheet, cell); err != nil { + return + } + ps := efp.ExcelParser() + tokens := ps.Parse(formula) + if tokens == nil { + return + } + if token, err = f.evalInfixExp(sheet, cell, tokens); err != nil { + return + } + result = token.TValue + isNum, precision := isNumeric(result) + if isNum && (precision > 15 || precision == 0) { + num := roundPrecision(result, -1) + result = strings.ToUpper(num) + } + return +} + +// getPriority calculate arithmetic operator priority. +func getPriority(token efp.Token) (pri int) { + pri = tokenPriority[token.TValue] + if token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix { + pri = 6 + } + if isBeginParenthesesToken(token) { // ( + pri = 0 + } + return +} + +// newNumberFormulaArg constructs a number formula argument. +func newNumberFormulaArg(n float64) formulaArg { + if math.IsNaN(n) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return formulaArg{Type: ArgNumber, Number: n} +} + +// newStringFormulaArg constructs a string formula argument. +func newStringFormulaArg(s string) formulaArg { + return formulaArg{Type: ArgString, String: s} +} + +// newMatrixFormulaArg constructs a matrix formula argument. +func newMatrixFormulaArg(m [][]formulaArg) formulaArg { + return formulaArg{Type: ArgMatrix, Matrix: m} +} + +// newListFormulaArg create a list formula argument. +func newListFormulaArg(l []formulaArg) formulaArg { + return formulaArg{Type: ArgList, List: l} +} + +// newBoolFormulaArg constructs a boolean formula argument. +func newBoolFormulaArg(b bool) formulaArg { + var n float64 + if b { + n = 1 + } + return formulaArg{Type: ArgNumber, Number: n, Boolean: true} +} + +// newErrorFormulaArg create an error formula argument of a given type with a +// specified error message. +func newErrorFormulaArg(formulaError, msg string) formulaArg { + return formulaArg{Type: ArgError, String: formulaError, Error: msg} +} + +// newEmptyFormulaArg create an empty formula argument. +func newEmptyFormulaArg() formulaArg { + return formulaArg{Type: ArgEmpty} +} + +// evalInfixExp evaluate syntax analysis by given infix expression after +// lexical analysis. Evaluate an infix expression containing formulas by +// stacks: +// +// opd - Operand +// opt - Operator +// opf - Operation formula +// opfd - Operand of the operation formula +// opft - Operator of the operation formula +// args - Arguments list of the operation formula +// +// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union +// +func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, error) { + var err error + opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack() + for i := 0; i < len(tokens); i++ { + token := tokens[i] + + // out of function stack + if opfStack.Len() == 0 { + if err = f.parseToken(sheet, token, opdStack, optStack); err != nil { + return efp.Token{}, err + } + } + + // function start + if isFunctionStartToken(token) { + opfStack.Push(token) + argsStack.Push(list.New().Init()) + opftStack.Push(token) // to know which operators belong to a function use the function as a separator + continue + } + + // in function stack, walk 2 token at once + if opfStack.Len() > 0 { + var nextToken efp.Token + if i+1 < len(tokens) { + nextToken = tokens[i+1] + } + + // current token is args or range, skip next token, order required: parse reference first + if token.TSubType == efp.TokenSubTypeRange { + if opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) { + refTo := f.getDefinedNameRefTo(token.TValue, sheet) + if refTo != "" { + token.TValue = refTo + } + // parse reference: must reference at here + result, err := f.parseReference(sheet, token.TValue) + if err != nil { + return efp.Token{TValue: formulaErrorNAME}, err + } + if result.Type != ArgString { + return efp.Token{}, errors.New(formulaErrorVALUE) + } + opfdStack.Push(efp.Token{ + TType: efp.TokenTypeOperand, + TSubType: efp.TokenSubTypeNumber, + TValue: result.String, + }) + continue + } + if nextToken.TType == efp.TokenTypeArgument || nextToken.TType == efp.TokenTypeFunction { + // parse reference: reference or range at here + refTo := f.getDefinedNameRefTo(token.TValue, sheet) + if refTo != "" { + token.TValue = refTo + } + result, err := f.parseReference(sheet, token.TValue) + if err != nil { + return efp.Token{TValue: formulaErrorNAME}, err + } + if result.Type == ArgUnknown { + return efp.Token{}, errors.New(formulaErrorVALUE) + } + argsStack.Peek().(*list.List).PushBack(result) + continue + } + } + + if isEndParenthesesToken(token) && isBeginParenthesesToken(opftStack.Peek().(efp.Token)) { + if arg := argsStack.Peek().(*list.List).Back(); arg != nil { + opfdStack.Push(efp.Token{ + TType: efp.TokenTypeOperand, + TSubType: efp.TokenSubTypeNumber, + TValue: arg.Value.(formulaArg).Value(), + }) + argsStack.Peek().(*list.List).Remove(arg) + } + } + + // check current token is opft + if err = f.parseToken(sheet, token, opfdStack, opftStack); err != nil { + return efp.Token{}, err + } + + // current token is arg + if token.TType == efp.TokenTypeArgument { + for opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) { + // calculate trigger + topOpt := opftStack.Peek().(efp.Token) + if err := calculate(opfdStack, topOpt); err != nil { + argsStack.Peek().(*list.List).PushFront(newErrorFormulaArg(formulaErrorVALUE, err.Error())) + } + opftStack.Pop() + } + if !opfdStack.Empty() { + argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue)) + } + continue + } + + // current token is logical + if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeLogical { + argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue)) + } + + if err = f.evalInfixExpFunc(sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil { + return efp.Token{}, err + } + } + } + for optStack.Len() != 0 { + topOpt := optStack.Peek().(efp.Token) + if err = calculate(opdStack, topOpt); err != nil { + return efp.Token{}, err + } + optStack.Pop() + } + if opdStack.Len() == 0 { + return efp.Token{}, ErrInvalidFormula + } + return opdStack.Peek().(efp.Token), err +} + +// evalInfixExpFunc evaluate formula function in the infix expression. +func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error { + if !isFunctionStopToken(token) { + return nil + } + prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack) + // call formula function to evaluate + arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell}, strings.NewReplacer( + "_xlfn.", "", ".", "dot").Replace(opfStack.Peek().(efp.Token).TValue), + []reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))}) + if arg.Type == ArgError && opfStack.Len() == 1 { + return errors.New(arg.Value()) + } + argsStack.Pop() + opftStack.Pop() // remove current function separator + opfStack.Pop() + if opfStack.Len() > 0 { // still in function stack + if nextToken.TType == efp.TokenTypeOperatorInfix || (opftStack.Len() > 1 && opfdStack.Len() > 0) { + // mathematics calculate in formula function + if arg.Type == ArgError { + opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeError}) + } else { + opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + } else { + argsStack.Peek().(*list.List).PushBack(arg) + } + } else { + opdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + return nil +} + +// prepareEvalInfixExp check the token and stack state for formula function +// evaluate. +func prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack *Stack) { + // current token is function stop + for opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) { + // calculate trigger + topOpt := opftStack.Peek().(efp.Token) + if err := calculate(opfdStack, topOpt); err != nil { + argsStack.Peek().(*list.List).PushBack(newErrorFormulaArg(err.Error(), err.Error())) + opftStack.Pop() + continue + } + opftStack.Pop() + } + argument := true + if opftStack.Len() > 2 && opfdStack.Len() == 1 { + topOpt := opftStack.Pop() + if opftStack.Peek().(efp.Token).TType == efp.TokenTypeOperatorInfix { + argument = false + } + opftStack.Push(topOpt) + } + // push opfd to args + if argument && opfdStack.Len() > 0 { + argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue)) + } +} + +// calcPow evaluate exponentiation arithmetic operations. +func calcPow(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) + if err != nil { + return err + } + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) + if err != nil { + return err + } + result := math.Pow(lOpdVal, rOpdVal) + opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + return nil +} + +// calcEq evaluate equal arithmetic operations. +func calcEq(rOpd, lOpd efp.Token, opdStack *Stack) error { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd.TValue == lOpd.TValue)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + return nil +} + +// calcNEq evaluate not equal arithmetic operations. +func calcNEq(rOpd, lOpd efp.Token, opdStack *Stack) error { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd.TValue != lOpd.TValue)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + return nil +} + +// calcL evaluate less than arithmetic operations. +func calcL(rOpd, lOpd efp.Token, opdStack *Stack) error { + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber { + lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64) + rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64) + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal < rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) == -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + return nil +} + +// calcLe evaluate less than or equal arithmetic operations. +func calcLe(rOpd, lOpd efp.Token, opdStack *Stack) error { + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber { + lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64) + rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64) + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal <= rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) != 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + return nil +} + +// calcG evaluate greater than or equal arithmetic operations. +func calcG(rOpd, lOpd efp.Token, opdStack *Stack) error { + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber { + lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64) + rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64) + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal > rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) == 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + return nil +} + +// calcGe evaluate greater than or equal arithmetic operations. +func calcGe(rOpd, lOpd efp.Token, opdStack *Stack) error { + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber { + lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64) + rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64) + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal >= rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) != -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + return nil +} + +// calcSplice evaluate splice '&' operations. +func calcSplice(rOpd, lOpd efp.Token, opdStack *Stack) error { + opdStack.Push(efp.Token{TValue: lOpd.TValue + rOpd.TValue, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + return nil +} + +// calcAdd evaluate addition arithmetic operations. +func calcAdd(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) + if err != nil { + return err + } + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) + if err != nil { + return err + } + result := lOpdVal + rOpdVal + opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + return nil +} + +// calcSubtract evaluate subtraction arithmetic operations. +func calcSubtract(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) + if err != nil { + return err + } + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) + if err != nil { + return err + } + result := lOpdVal - rOpdVal + opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + return nil +} + +// calcMultiply evaluate multiplication arithmetic operations. +func calcMultiply(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) + if err != nil { + return err + } + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) + if err != nil { + return err + } + result := lOpdVal * rOpdVal + opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + return nil +} + +// calcDiv evaluate division arithmetic operations. +func calcDiv(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) + if err != nil { + return err + } + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) + if err != nil { + return err + } + result := lOpdVal / rOpdVal + if rOpdVal == 0 { + return errors.New(formulaErrorDIV) + } + opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + return nil +} + +// calculate evaluate basic arithmetic operations. +func calculate(opdStack *Stack, opt efp.Token) error { + if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorPrefix { + if opdStack.Len() < 1 { + return ErrInvalidFormula + } + opd := opdStack.Pop().(efp.Token) + opdVal, err := strconv.ParseFloat(opd.TValue, 64) + if err != nil { + return err + } + result := 0 - opdVal + opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix { + if opdStack.Len() < 2 { + return ErrInvalidFormula + } + rOpd := opdStack.Pop().(efp.Token) + lOpd := opdStack.Pop().(efp.Token) + if err := calcSubtract(rOpd, lOpd, opdStack); err != nil { + return err + } + } + tokenCalcFunc := map[string]func(rOpd, lOpd efp.Token, opdStack *Stack) error{ + "^": calcPow, + "*": calcMultiply, + "/": calcDiv, + "+": calcAdd, + "=": calcEq, + "<>": calcNEq, + "<": calcL, + "<=": calcLe, + ">": calcG, + ">=": calcGe, + "&": calcSplice, + } + fn, ok := tokenCalcFunc[opt.TValue] + if ok { + if opdStack.Len() < 2 { + return ErrInvalidFormula + } + rOpd := opdStack.Pop().(efp.Token) + lOpd := opdStack.Pop().(efp.Token) + if rOpd.TSubType == efp.TokenSubTypeError { + return errors.New(rOpd.TValue) + } + if lOpd.TSubType == efp.TokenSubTypeError { + return errors.New(lOpd.TValue) + } + if err := fn(rOpd, lOpd, opdStack); err != nil { + return err + } + } + return nil +} + +// parseOperatorPrefixToken parse operator prefix token. +func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Token) (err error) { + if optStack.Len() == 0 { + optStack.Push(token) + } else { + tokenPriority := getPriority(token) + topOpt := optStack.Peek().(efp.Token) + topOptPriority := getPriority(topOpt) + if tokenPriority > topOptPriority { + optStack.Push(token) + } else { + for tokenPriority <= topOptPriority { + optStack.Pop() + if err = calculate(opdStack, topOpt); err != nil { + return + } + if optStack.Len() > 0 { + topOpt = optStack.Peek().(efp.Token) + topOptPriority = getPriority(topOpt) + continue + } + break + } + optStack.Push(token) + } + } + return +} + +// isFunctionStartToken determine if the token is function start. +func isFunctionStartToken(token efp.Token) bool { + return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart +} + +// isFunctionStopToken determine if the token is function stop. +func isFunctionStopToken(token efp.Token) bool { + return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop +} + +// isBeginParenthesesToken determine if the token is begin parentheses: (. +func isBeginParenthesesToken(token efp.Token) bool { + return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStart +} + +// isEndParenthesesToken determine if the token is end parentheses: ). +func isEndParenthesesToken(token efp.Token) bool { + return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStop +} + +// isOperatorPrefixToken determine if the token is parse operator prefix +// token. +func isOperatorPrefixToken(token efp.Token) bool { + _, ok := tokenPriority[token.TValue] + return (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || (ok && token.TType == efp.TokenTypeOperatorInfix) +} + +// isOperand determine if the token is parse operand. +func isOperand(token efp.Token) bool { + return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText) +} + +// parseToken parse basic arithmetic operator priority and evaluate based on +// operators and operands. +func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Stack) error { + // parse reference: must reference at here + if token.TSubType == efp.TokenSubTypeRange { + refTo := f.getDefinedNameRefTo(token.TValue, sheet) + if refTo != "" { + token.TValue = refTo + } + result, err := f.parseReference(sheet, token.TValue) + if err != nil { + return errors.New(formulaErrorNAME) + } + if result.Type != ArgString { + return errors.New(formulaErrorVALUE) + } + token.TValue = result.String + token.TType = efp.TokenTypeOperand + token.TSubType = efp.TokenSubTypeNumber + } + if isOperatorPrefixToken(token) { + if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil { + return err + } + } + if isBeginParenthesesToken(token) { // ( + optStack.Push(token) + } + if isEndParenthesesToken(token) { // ) + for !isBeginParenthesesToken(optStack.Peek().(efp.Token)) { // != ( + topOpt := optStack.Peek().(efp.Token) + if err := calculate(opdStack, topOpt); err != nil { + return err + } + optStack.Pop() + } + optStack.Pop() + } + if token.TType == efp.TokenTypeOperatorPostfix && !opdStack.Empty() { + topOpd := opdStack.Pop().(efp.Token) + opd, err := strconv.ParseFloat(topOpd.TValue, 64) + topOpd.TValue = strconv.FormatFloat(opd/100, 'f', -1, 64) + opdStack.Push(topOpd) + return err + } + // opd + if isOperand(token) { + opdStack.Push(token) + } + return nil +} + +// parseReference parse reference and extract values by given reference +// characters and default sheet name. +func (f *File) parseReference(sheet, reference string) (arg formulaArg, err error) { + reference = strings.Replace(reference, "$", "", -1) + refs, cellRanges, cellRefs := list.New(), list.New(), list.New() + for _, ref := range strings.Split(reference, ":") { + tokens := strings.Split(ref, "!") + cr := cellRef{} + if len(tokens) == 2 { // have a worksheet name + cr.Sheet = tokens[0] + // cast to cell coordinates + if cr.Col, cr.Row, err = CellNameToCoordinates(tokens[1]); err != nil { + // cast to column + if cr.Col, err = ColumnNameToNumber(tokens[1]); err != nil { + // cast to row + if cr.Row, err = strconv.Atoi(tokens[1]); err != nil { + err = newInvalidColumnNameError(tokens[1]) + return + } + cr.Col = TotalColumns + } + } + if refs.Len() > 0 { + e := refs.Back() + cellRefs.PushBack(e.Value.(cellRef)) + refs.Remove(e) + } + refs.PushBack(cr) + continue + } + // cast to cell coordinates + if cr.Col, cr.Row, err = CellNameToCoordinates(tokens[0]); err != nil { + // cast to column + if cr.Col, err = ColumnNameToNumber(tokens[0]); err != nil { + // cast to row + if cr.Row, err = strconv.Atoi(tokens[0]); err != nil { + err = newInvalidColumnNameError(tokens[0]) + return + } + cr.Col = TotalColumns + } + cellRanges.PushBack(cellRange{ + From: cellRef{Sheet: sheet, Col: cr.Col, Row: 1}, + To: cellRef{Sheet: sheet, Col: cr.Col, Row: TotalRows}, + }) + cellRefs.Init() + arg, err = f.rangeResolver(cellRefs, cellRanges) + return + } + e := refs.Back() + if e == nil { + cr.Sheet = sheet + refs.PushBack(cr) + continue + } + cellRanges.PushBack(cellRange{ + From: e.Value.(cellRef), + To: cr, + }) + refs.Remove(e) + } + if refs.Len() > 0 { + e := refs.Back() + cellRefs.PushBack(e.Value.(cellRef)) + refs.Remove(e) + } + arg, err = f.rangeResolver(cellRefs, cellRanges) + return +} + +// prepareValueRange prepare value range. +func prepareValueRange(cr cellRange, valueRange []int) { + if cr.From.Row < valueRange[0] || valueRange[0] == 0 { + valueRange[0] = cr.From.Row + } + if cr.From.Col < valueRange[2] || valueRange[2] == 0 { + valueRange[2] = cr.From.Col + } + if cr.To.Row > valueRange[1] || valueRange[1] == 0 { + valueRange[1] = cr.To.Row + } + if cr.To.Col > valueRange[3] || valueRange[3] == 0 { + valueRange[3] = cr.To.Col + } +} + +// prepareValueRef prepare value reference. +func prepareValueRef(cr cellRef, valueRange []int) { + if cr.Row < valueRange[0] || valueRange[0] == 0 { + valueRange[0] = cr.Row + } + if cr.Col < valueRange[2] || valueRange[2] == 0 { + valueRange[2] = cr.Col + } + if cr.Row > valueRange[1] || valueRange[1] == 0 { + valueRange[1] = cr.Row + } + if cr.Col > valueRange[3] || valueRange[3] == 0 { + valueRange[3] = cr.Col + } +} + +// rangeResolver extract value as string from given reference and range list. +// This function will not ignore the empty cell. For example, A1:A2:A2:B3 will +// be reference A1:B3. +func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, err error) { + arg.cellRefs, arg.cellRanges = cellRefs, cellRanges + // value range order: from row, to row, from column, to column + valueRange := []int{0, 0, 0, 0} + var sheet string + // prepare value range + for temp := cellRanges.Front(); temp != nil; temp = temp.Next() { + cr := temp.Value.(cellRange) + if cr.From.Sheet != cr.To.Sheet { + err = errors.New(formulaErrorVALUE) + } + rng := []int{cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row} + _ = sortCoordinates(rng) + cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row = rng[0], rng[1], rng[2], rng[3] + prepareValueRange(cr, valueRange) + if cr.From.Sheet != "" { + sheet = cr.From.Sheet + } + } + for temp := cellRefs.Front(); temp != nil; temp = temp.Next() { + cr := temp.Value.(cellRef) + if cr.Sheet != "" { + sheet = cr.Sheet + } + prepareValueRef(cr, valueRange) + } + // extract value from ranges + if cellRanges.Len() > 0 { + arg.Type = ArgMatrix + for row := valueRange[0]; row <= valueRange[1]; row++ { + var matrixRow []formulaArg + for col := valueRange[2]; col <= valueRange[3]; col++ { + var cell, value string + if cell, err = CoordinatesToCellName(col, row); err != nil { + return + } + if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true}); err != nil { + return + } + matrixRow = append(matrixRow, formulaArg{ + String: value, + Type: ArgString, + }) + } + arg.Matrix = append(arg.Matrix, matrixRow) + } + return + } + // extract value from references + for temp := cellRefs.Front(); temp != nil; temp = temp.Next() { + cr := temp.Value.(cellRef) + var cell string + if cell, err = CoordinatesToCellName(cr.Col, cr.Row); err != nil { + return + } + if arg.String, err = f.GetCellValue(cr.Sheet, cell, Options{RawCellValue: true}); err != nil { + return + } + arg.Type = ArgString + } + return +} + +// callFuncByName calls the no error or only error return function with +// reflect by given receiver, name and parameters. +func callFuncByName(receiver interface{}, name string, params []reflect.Value) (arg formulaArg) { + function := reflect.ValueOf(receiver).MethodByName(name) + if function.IsValid() { + rt := function.Call(params) + if len(rt) == 0 { + return + } + arg = rt[0].Interface().(formulaArg) + return + } + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("not support %s function", name)) +} + +// formulaCriteriaParser parse formula criteria. +func formulaCriteriaParser(exp string) (fc *formulaCriteria) { + fc = &formulaCriteria{} + if exp == "" { + return + } + if match := regexp.MustCompile(`^([0-9]+)$`).FindStringSubmatch(exp); len(match) > 1 { + fc.Type, fc.Condition = criteriaEq, match[1] + return + } + if match := regexp.MustCompile(`^=(.*)$`).FindStringSubmatch(exp); len(match) > 1 { + fc.Type, fc.Condition = criteriaEq, match[1] + return + } + if match := regexp.MustCompile(`^<>(.*)$`).FindStringSubmatch(exp); len(match) > 1 { + fc.Type, fc.Condition = criteriaNe, match[1] + return + } + if match := regexp.MustCompile(`^<=(.*)$`).FindStringSubmatch(exp); len(match) > 1 { + fc.Type, fc.Condition = criteriaLe, match[1] + return + } + if match := regexp.MustCompile(`^>=(.*)$`).FindStringSubmatch(exp); len(match) > 1 { + fc.Type, fc.Condition = criteriaGe, match[1] + return + } + if match := regexp.MustCompile(`^<(.*)$`).FindStringSubmatch(exp); len(match) > 1 { + fc.Type, fc.Condition = criteriaL, match[1] + return + } + if match := regexp.MustCompile(`^>(.*)$`).FindStringSubmatch(exp); len(match) > 1 { + fc.Type, fc.Condition = criteriaG, match[1] + return + } + if strings.Contains(exp, "?") { + exp = strings.ReplaceAll(exp, "?", ".") + } + if strings.Contains(exp, "*") { + exp = strings.ReplaceAll(exp, "*", ".*") + } + fc.Type, fc.Condition = criteriaRegexp, exp + return +} + +// formulaCriteriaEval evaluate formula criteria expression. +func formulaCriteriaEval(val string, criteria *formulaCriteria) (result bool, err error) { + var value, expected float64 + var e error + prepareValue := func(val, cond string) (value float64, expected float64, err error) { + if value, err = strconv.ParseFloat(val, 64); err != nil { + return + } + if expected, err = strconv.ParseFloat(criteria.Condition, 64); err != nil { + return + } + return + } + switch criteria.Type { + case criteriaEq: + return val == criteria.Condition, err + case criteriaLe: + value, expected, e = prepareValue(val, criteria.Condition) + return value <= expected && e == nil, err + case criteriaGe: + value, expected, e = prepareValue(val, criteria.Condition) + return value >= expected && e == nil, err + case criteriaNe: + return val != criteria.Condition, err + case criteriaL: + value, expected, e = prepareValue(val, criteria.Condition) + return value < expected && e == nil, err + case criteriaG: + value, expected, e = prepareValue(val, criteria.Condition) + return value > expected && e == nil, err + case criteriaRegexp: + return regexp.MatchString(criteria.Condition, val) + } + return +} + +// Engineering Functions + +// BESSELI function the modified Bessel function, which is equivalent to the +// Bessel function evaluated for purely imaginary arguments. The syntax of +// the Besseli function is: +// +// BESSELI(x,n) +// +func (fn *formulaFuncs) BESSELI(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "BESSELI requires 2 numeric arguments") + } + return fn.bassel(argsList, true) +} + +// BESSELJ function returns the Bessel function, Jn(x), for a specified order +// and value of x. The syntax of the function is: +// +// BESSELJ(x,n) +// +func (fn *formulaFuncs) BESSELJ(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "BESSELJ requires 2 numeric arguments") + } + return fn.bassel(argsList, false) +} + +// bassel is an implementation of the formula functions BESSELI and BESSELJ. +func (fn *formulaFuncs) bassel(argsList *list.List, modfied bool) formulaArg { + x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + if n.Type != ArgNumber { + return n + } + max, x1 := 100, x.Number*0.5 + x2 := x1 * x1 + x1 = math.Pow(x1, n.Number) + n1, n2, n3, n4, add := fact(n.Number), 1.0, 0.0, n.Number, false + result := x1 / n1 + t := result * 0.9 + for result != t && max != 0 { + x1 *= x2 + n3++ + n1 *= n3 + n4++ + n2 *= n4 + t = result + r := x1 / n1 / n2 + if modfied || add { + result += r + } else { + result -= r + } + max-- + add = !add + } + return newNumberFormulaArg(result) +} + +// BESSELK function calculates the modified Bessel functions, Kn(x), which are +// also known as the hyperbolic Bessel Functions. These are the equivalent of +// the Bessel functions, evaluated for purely imaginary arguments. The syntax +// of the function is: +// +// BESSELK(x,n) +// +func (fn *formulaFuncs) BESSELK(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "BESSELK requires 2 numeric arguments") + } + x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + if n.Type != ArgNumber { + return n + } + if x.Number <= 0 || n.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + var result float64 + switch math.Floor(n.Number) { + case 0: + result = fn.besselK0(x) + case 1: + result = fn.besselK1(x) + default: + result = fn.besselK2(x, n) + } + return newNumberFormulaArg(result) +} + +// besselK0 is an implementation of the formula function BESSELK. +func (fn *formulaFuncs) besselK0(x formulaArg) float64 { + var y float64 + if x.Number <= 2 { + n2 := x.Number * 0.5 + y = n2 * n2 + args := list.New() + args.PushBack(x) + args.PushBack(newNumberFormulaArg(0)) + return -math.Log(n2)*fn.BESSELI(args).Number + + (-0.57721566 + y*(0.42278420+y*(0.23069756+y*(0.3488590e-1+y*(0.262698e-2+y* + (0.10750e-3+y*0.74e-5)))))) + } + y = 2 / x.Number + return math.Exp(-x.Number) / math.Sqrt(x.Number) * + (1.25331414 + y*(-0.7832358e-1+y*(0.2189568e-1+y*(-0.1062446e-1+y* + (0.587872e-2+y*(-0.251540e-2+y*0.53208e-3)))))) +} + +// besselK1 is an implementation of the formula function BESSELK. +func (fn *formulaFuncs) besselK1(x formulaArg) float64 { + var n2, y float64 + if x.Number <= 2 { + n2 = x.Number * 0.5 + y = n2 * n2 + args := list.New() + args.PushBack(x) + args.PushBack(newNumberFormulaArg(1)) + return math.Log(n2)*fn.BESSELI(args).Number + + (1+y*(0.15443144+y*(-0.67278579+y*(-0.18156897+y*(-0.1919402e-1+y*(-0.110404e-2+y*(-0.4686e-4)))))))/x.Number + } + y = 2 / x.Number + return math.Exp(-x.Number) / math.Sqrt(x.Number) * + (1.25331414 + y*(0.23498619+y*(-0.3655620e-1+y*(0.1504268e-1+y*(-0.780353e-2+y* + (0.325614e-2+y*(-0.68245e-3))))))) +} + +// besselK2 is an implementation of the formula function BESSELK. +func (fn *formulaFuncs) besselK2(x, n formulaArg) float64 { + tox, bkm, bk, bkp := 2/x.Number, fn.besselK0(x), fn.besselK1(x), 0.0 + for i := 1.0; i < n.Number; i++ { + bkp = bkm + i*tox*bk + bkm = bk + bk = bkp + } + return bk +} + +// BESSELY function returns the Bessel function, Yn(x), (also known as the +// Weber function or the Neumann function), for a specified order and value +// of x. The syntax of the function is: +// +// BESSELY(x,n) +// +func (fn *formulaFuncs) BESSELY(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "BESSELY requires 2 numeric arguments") + } + x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + if n.Type != ArgNumber { + return n + } + if x.Number <= 0 || n.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + var result float64 + switch math.Floor(n.Number) { + case 0: + result = fn.besselY0(x) + case 1: + result = fn.besselY1(x) + default: + result = fn.besselY2(x, n) + } + return newNumberFormulaArg(result) +} + +// besselY0 is an implementation of the formula function BESSELY. +func (fn *formulaFuncs) besselY0(x formulaArg) float64 { + var y float64 + if x.Number < 8 { + y = x.Number * x.Number + f1 := -2957821389.0 + y*(7062834065.0+y*(-512359803.6+y*(10879881.29+y* + (-86327.92757+y*228.4622733)))) + f2 := 40076544269.0 + y*(745249964.8+y*(7189466.438+y* + (47447.26470+y*(226.1030244+y)))) + args := list.New() + args.PushBack(x) + args.PushBack(newNumberFormulaArg(0)) + return f1/f2 + 0.636619772*fn.BESSELJ(args).Number*math.Log(x.Number) + } + z := 8.0 / x.Number + y = z * z + xx := x.Number - 0.785398164 + f1 := 1 + y*(-0.1098628627e-2+y*(0.2734510407e-4+y*(-0.2073370639e-5+y*0.2093887211e-6))) + f2 := -0.1562499995e-1 + y*(0.1430488765e-3+y*(-0.6911147651e-5+y*(0.7621095161e-6+y* + (-0.934945152e-7)))) + return math.Sqrt(0.636619772/x.Number) * (math.Sin(xx)*f1 + z*math.Cos(xx)*f2) +} + +// besselY1 is an implementation of the formula function BESSELY. +func (fn *formulaFuncs) besselY1(x formulaArg) float64 { + if x.Number < 8 { + y := x.Number * x.Number + f1 := x.Number * (-0.4900604943e13 + y*(0.1275274390e13+y*(-0.5153438139e11+y* + (0.7349264551e9+y*(-0.4237922726e7+y*0.8511937935e4))))) + f2 := 0.2499580570e14 + y*(0.4244419664e12+y*(0.3733650367e10+y*(0.2245904002e8+y* + (0.1020426050e6+y*(0.3549632885e3+y))))) + args := list.New() + args.PushBack(x) + args.PushBack(newNumberFormulaArg(1)) + return f1/f2 + 0.636619772*(fn.BESSELJ(args).Number*math.Log(x.Number)-1/x.Number) + } + return math.Sqrt(0.636619772/x.Number) * math.Sin(x.Number-2.356194491) +} + +// besselY2 is an implementation of the formula function BESSELY. +func (fn *formulaFuncs) besselY2(x, n formulaArg) float64 { + tox, bym, by, byp := 2/x.Number, fn.besselY0(x), fn.besselY1(x), 0.0 + for i := 1.0; i < n.Number; i++ { + byp = i*tox*by - bym + bym = by + by = byp + } + return by +} + +// BIN2DEC function converts a Binary (a base-2 number) into a decimal number. +// The syntax of the function is: +// +// BIN2DEC(number) +// +func (fn *formulaFuncs) BIN2DEC(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "BIN2DEC requires 1 numeric argument") + } + token := argsList.Front().Value.(formulaArg) + number := token.ToNumber() + if number.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, number.Error) + } + return fn.bin2dec(token.Value()) +} + +// BIN2HEX function converts a Binary (Base 2) number into a Hexadecimal +// (Base 16) number. The syntax of the function is: +// +// BIN2HEX(number,[places]) +// +func (fn *formulaFuncs) BIN2HEX(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "BIN2HEX requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "BIN2HEX allows at most 2 arguments") + } + token := argsList.Front().Value.(formulaArg) + number := token.ToNumber() + if number.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, number.Error) + } + decimal, newList := fn.bin2dec(token.Value()), list.New() + if decimal.Type != ArgNumber { + return decimal + } + newList.PushBack(decimal) + if argsList.Len() == 2 { + newList.PushBack(argsList.Back().Value.(formulaArg)) + } + return fn.dec2x("BIN2HEX", newList) +} + +// BIN2OCT function converts a Binary (Base 2) number into an Octal (Base 8) +// number. The syntax of the function is: +// +// BIN2OCT(number,[places]) +// +func (fn *formulaFuncs) BIN2OCT(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "BIN2OCT requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "BIN2OCT allows at most 2 arguments") + } + token := argsList.Front().Value.(formulaArg) + number := token.ToNumber() + if number.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, number.Error) + } + decimal, newList := fn.bin2dec(token.Value()), list.New() + if decimal.Type != ArgNumber { + return decimal + } + newList.PushBack(decimal) + if argsList.Len() == 2 { + newList.PushBack(argsList.Back().Value.(formulaArg)) + } + return fn.dec2x("BIN2OCT", newList) +} + +// bin2dec is an implementation of the formula function BIN2DEC. +func (fn *formulaFuncs) bin2dec(number string) formulaArg { + decimal, length := 0.0, len(number) + for i := length; i > 0; i-- { + s := string(number[length-i]) + if i == 10 && s == "1" { + decimal += math.Pow(-2.0, float64(i-1)) + continue + } + if s == "1" { + decimal += math.Pow(2.0, float64(i-1)) + continue + } + if s != "0" { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + return newNumberFormulaArg(decimal) +} + +// BITAND function returns the bitwise 'AND' for two supplied integers. The +// syntax of the function is: +// +// BITAND(number1,number2) +// +func (fn *formulaFuncs) BITAND(argsList *list.List) formulaArg { + return fn.bitwise("BITAND", argsList) +} + +// BITLSHIFT function returns a supplied integer, shifted left by a specified +// number of bits. The syntax of the function is: +// +// BITLSHIFT(number1,shift_amount) +// +func (fn *formulaFuncs) BITLSHIFT(argsList *list.List) formulaArg { + return fn.bitwise("BITLSHIFT", argsList) +} + +// BITOR function returns the bitwise 'OR' for two supplied integers. The +// syntax of the function is: +// +// BITOR(number1,number2) +// +func (fn *formulaFuncs) BITOR(argsList *list.List) formulaArg { + return fn.bitwise("BITOR", argsList) +} + +// BITRSHIFT function returns a supplied integer, shifted right by a specified +// number of bits. The syntax of the function is: +// +// BITRSHIFT(number1,shift_amount) +// +func (fn *formulaFuncs) BITRSHIFT(argsList *list.List) formulaArg { + return fn.bitwise("BITRSHIFT", argsList) +} + +// BITXOR function returns the bitwise 'XOR' (exclusive 'OR') for two supplied +// integers. The syntax of the function is: +// +// BITXOR(number1,number2) +// +func (fn *formulaFuncs) BITXOR(argsList *list.List) formulaArg { + return fn.bitwise("BITXOR", argsList) +} + +// bitwise is an implementation of the formula functions BITAND, BITLSHIFT, +// BITOR, BITRSHIFT and BITXOR. +func (fn *formulaFuncs) bitwise(name string, argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 numeric arguments", name)) + } + num1, num2 := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber() + if num1.Type != ArgNumber || num2.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + max := math.Pow(2, 48) - 1 + if num1.Number < 0 || num1.Number > max || num2.Number < 0 || num2.Number > max { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + bitwiseFuncMap := map[string]func(a, b int) int{ + "BITAND": func(a, b int) int { return a & b }, + "BITLSHIFT": func(a, b int) int { return a << uint(b) }, + "BITOR": func(a, b int) int { return a | b }, + "BITRSHIFT": func(a, b int) int { return a >> uint(b) }, + "BITXOR": func(a, b int) int { return a ^ b }, + } + bitwiseFunc := bitwiseFuncMap[name] + return newNumberFormulaArg(float64(bitwiseFunc(int(num1.Number), int(num2.Number)))) +} + +// COMPLEX function takes two arguments, representing the real and the +// imaginary coefficients of a complex number, and from these, creates a +// complex number. The syntax of the function is: +// +// COMPLEX(real_num,i_num,[suffix]) +// +func (fn *formulaFuncs) COMPLEX(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "COMPLEX requires at least 2 arguments") + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "COMPLEX allows at most 3 arguments") + } + realNum, i, suffix := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber(), "i" + if realNum.Type != ArgNumber { + return realNum + } + if i.Type != ArgNumber { + return i + } + if argsList.Len() == 3 { + if suffix = strings.ToLower(argsList.Back().Value.(formulaArg).Value()); suffix != "i" && suffix != "j" { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + return newStringFormulaArg(cmplx2str(complex(realNum.Number, i.Number), suffix)) +} + +// cmplx2str replace complex number string characters. +func cmplx2str(num complex128, suffix string) string { + c := fmt.Sprint(num) + realPart, imagPart := fmt.Sprint(real(num)), fmt.Sprint(imag(num)) + isNum, i := isNumeric(realPart) + if isNum && i > 15 { + realPart = roundPrecision(realPart, -1) + } + isNum, i = isNumeric(imagPart) + if isNum && i > 15 { + imagPart = roundPrecision(imagPart, -1) + } + c = realPart + if imag(num) > 0 { + c += "+" + } + if imag(num) != 0 { + c += imagPart + "i" + } + c = strings.TrimPrefix(c, "(") + c = strings.TrimPrefix(c, "+0+") + c = strings.TrimPrefix(c, "-0+") + c = strings.TrimSuffix(c, ")") + c = strings.TrimPrefix(c, "0+") + if strings.HasPrefix(c, "0-") { + c = "-" + strings.TrimPrefix(c, "0-") + } + c = strings.TrimPrefix(c, "0+") + c = strings.TrimSuffix(c, "+0i") + c = strings.TrimSuffix(c, "-0i") + c = strings.NewReplacer("+1i", "+i", "-1i", "-i").Replace(c) + c = strings.Replace(c, "i", suffix, -1) + return c +} + +// str2cmplx convert complex number string characters. +func str2cmplx(c string) string { + c = strings.Replace(c, "j", "i", -1) + if c == "i" { + c = "1i" + } + c = strings.NewReplacer("+i", "+1i", "-i", "-1i").Replace(c) + return c +} + +// DEC2BIN function converts a decimal number into a Binary (Base 2) number. +// The syntax of the function is: +// +// DEC2BIN(number,[places]) +// +func (fn *formulaFuncs) DEC2BIN(argsList *list.List) formulaArg { + return fn.dec2x("DEC2BIN", argsList) +} + +// DEC2HEX function converts a decimal number into a Hexadecimal (Base 16) +// number. The syntax of the function is: +// +// DEC2HEX(number,[places]) +// +func (fn *formulaFuncs) DEC2HEX(argsList *list.List) formulaArg { + return fn.dec2x("DEC2HEX", argsList) +} + +// DEC2OCT function converts a decimal number into an Octal (Base 8) number. +// The syntax of the function is: +// +// DEC2OCT(number,[places]) +// +func (fn *formulaFuncs) DEC2OCT(argsList *list.List) formulaArg { + return fn.dec2x("DEC2OCT", argsList) +} + +// dec2x is an implementation of the formula functions DEC2BIN, DEC2HEX and +// DEC2OCT. +func (fn *formulaFuncs) dec2x(name string, argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name)) + } + decimal := argsList.Front().Value.(formulaArg).ToNumber() + if decimal.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, decimal.Error) + } + maxLimitMap := map[string]float64{ + "DEC2BIN": 511, + "HEX2BIN": 511, + "OCT2BIN": 511, + "BIN2HEX": 549755813887, + "DEC2HEX": 549755813887, + "OCT2HEX": 549755813887, + "BIN2OCT": 536870911, + "DEC2OCT": 536870911, + "HEX2OCT": 536870911, + } + minLimitMap := map[string]float64{ + "DEC2BIN": -512, + "HEX2BIN": -512, + "OCT2BIN": -512, + "BIN2HEX": -549755813888, + "DEC2HEX": -549755813888, + "OCT2HEX": -549755813888, + "BIN2OCT": -536870912, + "DEC2OCT": -536870912, + "HEX2OCT": -536870912, + } + baseMap := map[string]int{ + "DEC2BIN": 2, + "HEX2BIN": 2, + "OCT2BIN": 2, + "BIN2HEX": 16, + "DEC2HEX": 16, + "OCT2HEX": 16, + "BIN2OCT": 8, + "DEC2OCT": 8, + "HEX2OCT": 8, + } + maxLimit, minLimit := maxLimitMap[name], minLimitMap[name] + base := baseMap[name] + if decimal.Number < minLimit || decimal.Number > maxLimit { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + n := int64(decimal.Number) + binary := strconv.FormatUint(*(*uint64)(unsafe.Pointer(&n)), base) + if argsList.Len() == 2 { + places := argsList.Back().Value.(formulaArg).ToNumber() + if places.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, places.Error) + } + binaryPlaces := len(binary) + if places.Number < 0 || places.Number > 10 || binaryPlaces > int(places.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%s%s", strings.Repeat("0", int(places.Number)-binaryPlaces), binary))) + } + if decimal.Number < 0 && len(binary) > 10 { + return newStringFormulaArg(strings.ToUpper(binary[len(binary)-10:])) + } + return newStringFormulaArg(strings.ToUpper(binary)) +} + +// DELTA function tests two numbers for equality and returns the Kronecker +// Delta. i.e. the function returns 1 if the two supplied numbers are equal +// and 0 otherwise. The syntax of the function is: +// +// DELTA(number1,[number2]) +// +func (fn *formulaFuncs) DELTA(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DELTA requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "DELTA allows at most 2 arguments") + } + number1 := argsList.Front().Value.(formulaArg).ToNumber() + if number1.Type != ArgNumber { + return number1 + } + number2 := newNumberFormulaArg(0) + if argsList.Len() == 2 { + if number2 = argsList.Back().Value.(formulaArg).ToNumber(); number2.Type != ArgNumber { + return number2 + } + } + return newBoolFormulaArg(number1.Number == number2.Number).ToNumber() +} + +// ERF function calculates the Error Function, integrated between two supplied +// limits. The syntax of the function is: +// +// ERF(lower_limit,[upper_limit]) +// +func (fn *formulaFuncs) ERF(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ERF requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ERF allows at most 2 arguments") + } + lower := argsList.Front().Value.(formulaArg).ToNumber() + if lower.Type != ArgNumber { + return lower + } + if argsList.Len() == 2 { + upper := argsList.Back().Value.(formulaArg).ToNumber() + if upper.Type != ArgNumber { + return upper + } + return newNumberFormulaArg(math.Erf(upper.Number) - math.Erf(lower.Number)) + } + return newNumberFormulaArg(math.Erf(lower.Number)) +} + +// ERFdotPRECISE function calculates the Error Function, integrated between a +// supplied lower or upper limit and 0. The syntax of the function is: +// +// ERF.PRECISE(x) +// +func (fn *formulaFuncs) ERFdotPRECISE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ERF.PRECISE requires 1 argument") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + return newNumberFormulaArg(math.Erf(x.Number)) +} + +// erfc is an implementation of the formula functions ERFC and ERFC.PRECISE. +func (fn *formulaFuncs) erfc(name string, argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name)) + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + return newNumberFormulaArg(math.Erfc(x.Number)) +} + +// ERFC function calculates the Complementary Error Function, integrated +// between a supplied lower limit and infinity. The syntax of the function +// is: +// +// ERFC(x) +// +func (fn *formulaFuncs) ERFC(argsList *list.List) formulaArg { + return fn.erfc("ERFC", argsList) +} + +// ERFCdotPRECISE function calculates the Complementary Error Function, +// integrated between a supplied lower limit and infinity. The syntax of the +// function is: +// +// ERFC(x) +// +func (fn *formulaFuncs) ERFCdotPRECISE(argsList *list.List) formulaArg { + return fn.erfc("ERFC.PRECISE", argsList) +} + +// GESTEP unction tests whether a supplied number is greater than a supplied +// step size and returns. The syntax of the function is: +// +// GESTEP(number,[step]) +// +func (fn *formulaFuncs) GESTEP(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "GESTEP requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "GESTEP allows at most 2 arguments") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type != ArgNumber { + return number + } + step := newNumberFormulaArg(0) + if argsList.Len() == 2 { + if step = argsList.Back().Value.(formulaArg).ToNumber(); step.Type != ArgNumber { + return step + } + } + return newBoolFormulaArg(number.Number >= step.Number).ToNumber() +} + +// HEX2BIN function converts a Hexadecimal (Base 16) number into a Binary +// (Base 2) number. The syntax of the function is: +// +// HEX2BIN(number,[places]) +// +func (fn *formulaFuncs) HEX2BIN(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "HEX2BIN requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "HEX2BIN allows at most 2 arguments") + } + decimal, newList := fn.hex2dec(argsList.Front().Value.(formulaArg).Value()), list.New() + if decimal.Type != ArgNumber { + return decimal + } + newList.PushBack(decimal) + if argsList.Len() == 2 { + newList.PushBack(argsList.Back().Value.(formulaArg)) + } + return fn.dec2x("HEX2BIN", newList) +} + +// HEX2DEC function converts a hexadecimal (a base-16 number) into a decimal +// number. The syntax of the function is: +// +// HEX2DEC(number) +// +func (fn *formulaFuncs) HEX2DEC(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "HEX2DEC requires 1 numeric argument") + } + return fn.hex2dec(argsList.Front().Value.(formulaArg).Value()) +} + +// HEX2OCT function converts a Hexadecimal (Base 16) number into an Octal +// (Base 8) number. The syntax of the function is: +// +// HEX2OCT(number,[places]) +// +func (fn *formulaFuncs) HEX2OCT(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "HEX2OCT requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "HEX2OCT allows at most 2 arguments") + } + decimal, newList := fn.hex2dec(argsList.Front().Value.(formulaArg).Value()), list.New() + if decimal.Type != ArgNumber { + return decimal + } + newList.PushBack(decimal) + if argsList.Len() == 2 { + newList.PushBack(argsList.Back().Value.(formulaArg)) + } + return fn.dec2x("HEX2OCT", newList) +} + +// hex2dec is an implementation of the formula function HEX2DEC. +func (fn *formulaFuncs) hex2dec(number string) formulaArg { + decimal, length := 0.0, len(number) + for i := length; i > 0; i-- { + num, err := strconv.ParseInt(string(number[length-i]), 16, 64) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + if i == 10 && string(number[length-i]) == "F" { + decimal += math.Pow(-16.0, float64(i-1)) + continue + } + decimal += float64(num) * math.Pow(16.0, float64(i-1)) + } + return newNumberFormulaArg(decimal) +} + +// IMABS function returns the absolute value (the modulus) of a complex +// number. The syntax of the function is: +// +// IMABS(inumber) +// +func (fn *formulaFuncs) IMABS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMABS requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newNumberFormulaArg(cmplx.Abs(inumber)) +} + +// IMAGINARY function returns the imaginary coefficient of a supplied complex +// number. The syntax of the function is: +// +// IMAGINARY(inumber) +// +func (fn *formulaFuncs) IMAGINARY(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMAGINARY requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newNumberFormulaArg(imag(inumber)) +} + +// IMARGUMENT function returns the phase (also called the argument) of a +// supplied complex number. The syntax of the function is: +// +// IMARGUMENT(inumber) +// +func (fn *formulaFuncs) IMARGUMENT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMARGUMENT requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newNumberFormulaArg(cmplx.Phase(inumber)) +} + +// IMCONJUGATE function returns the complex conjugate of a supplied complex +// number. The syntax of the function is: +// +// IMCONJUGATE(inumber) +// +func (fn *formulaFuncs) IMCONJUGATE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMCONJUGATE requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Conj(inumber), value[len(value)-1:])) +} + +// IMCOS function returns the cosine of a supplied complex number. The syntax +// of the function is: +// +// IMCOS(inumber) +// +func (fn *formulaFuncs) IMCOS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMCOS requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Cos(inumber), value[len(value)-1:])) +} + +// IMCOSH function returns the hyperbolic cosine of a supplied complex number. The syntax +// of the function is: +// +// IMCOSH(inumber) +// +func (fn *formulaFuncs) IMCOSH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMCOSH requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Cosh(inumber), value[len(value)-1:])) +} + +// IMCOT function returns the cotangent of a supplied complex number. The syntax +// of the function is: +// +// IMCOT(inumber) +// +func (fn *formulaFuncs) IMCOT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMCOT requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Cot(inumber), value[len(value)-1:])) +} + +// IMCSC function returns the cosecant of a supplied complex number. The syntax +// of the function is: +// +// IMCSC(inumber) +// +func (fn *formulaFuncs) IMCSC(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMCSC requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + num := 1 / cmplx.Sin(inumber) + if cmplx.IsInf(num) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) +} + +// IMCSCH function returns the hyperbolic cosecant of a supplied complex +// number. The syntax of the function is: +// +// IMCSCH(inumber) +// +func (fn *formulaFuncs) IMCSCH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMCSCH requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + num := 1 / cmplx.Sinh(inumber) + if cmplx.IsInf(num) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) +} + +// IMDIV function calculates the quotient of two complex numbers (i.e. divides +// one complex number by another). The syntax of the function is: +// +// IMDIV(inumber1,inumber2) +// +func (fn *formulaFuncs) IMDIV(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IMDIV requires 2 arguments") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber1, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + inumber2, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + num := inumber1 / inumber2 + if cmplx.IsInf(num) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) +} + +// IMEXP function returns the exponential of a supplied complex number. The +// syntax of the function is: +// +// IMEXP(inumber) +// +func (fn *formulaFuncs) IMEXP(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMEXP requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Exp(inumber), value[len(value)-1:])) +} + +// IMLN function returns the natural logarithm of a supplied complex number. +// The syntax of the function is: +// +// IMLN(inumber) +// +func (fn *formulaFuncs) IMLN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMLN requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + num := cmplx.Log(inumber) + if cmplx.IsInf(num) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) +} + +// IMLOG10 function returns the common (base 10) logarithm of a supplied +// complex number. The syntax of the function is: +// +// IMLOG10(inumber) +// +func (fn *formulaFuncs) IMLOG10(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMLOG10 requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + num := cmplx.Log10(inumber) + if cmplx.IsInf(num) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) +} + +// IMLOG2 function calculates the base 2 logarithm of a supplied complex +// number. The syntax of the function is: +// +// IMLOG2(inumber) +// +func (fn *formulaFuncs) IMLOG2(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMLOG2 requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + num := cmplx.Log(inumber) + if cmplx.IsInf(num) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(cmplx2str(num/cmplx.Log(2), value[len(value)-1:])) +} + +// IMPOWER function returns a supplied complex number, raised to a given +// power. The syntax of the function is: +// +// IMPOWER(inumber,number) +// +func (fn *formulaFuncs) IMPOWER(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IMPOWER requires 2 arguments") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + number, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + if inumber == 0 && number == 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + num := cmplx.Pow(inumber, number) + if cmplx.IsInf(num) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(cmplx2str(num, value[len(value)-1:])) +} + +// IMPRODUCT function calculates the product of two or more complex numbers. +// The syntax of the function is: +// +// IMPRODUCT(number1,[number2],...) +// +func (fn *formulaFuncs) IMPRODUCT(argsList *list.List) formulaArg { + product := complex128(1) + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgString: + if token.Value() == "" { + continue + } + val, err := strconv.ParseComplex(str2cmplx(token.Value()), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + product = product * val + case ArgNumber: + product = product * complex(token.Number, 0) + case ArgMatrix: + for _, row := range token.Matrix { + for _, value := range row { + if value.Value() == "" { + continue + } + val, err := strconv.ParseComplex(str2cmplx(value.Value()), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + product = product * val + } + } + } + } + return newStringFormulaArg(cmplx2str(product, "i")) +} + +// IMREAL function returns the real coefficient of a supplied complex number. +// The syntax of the function is: +// +// IMREAL(inumber) +// +func (fn *formulaFuncs) IMREAL(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMREAL requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(fmt.Sprint(real(inumber))) +} + +// IMSEC function returns the secant of a supplied complex number. The syntax +// of the function is: +// +// IMSEC(inumber) +// +func (fn *formulaFuncs) IMSEC(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMSEC requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(1/cmplx.Cos(inumber), value[len(value)-1:])) +} + +// IMSECH function returns the hyperbolic secant of a supplied complex number. +// The syntax of the function is: +// +// IMSECH(inumber) +// +func (fn *formulaFuncs) IMSECH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMSECH requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(1/cmplx.Cosh(inumber), value[len(value)-1:])) +} + +// IMSIN function returns the Sine of a supplied complex number. The syntax of +// the function is: +// +// IMSIN(inumber) +// +func (fn *formulaFuncs) IMSIN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMSIN requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Sin(inumber), value[len(value)-1:])) +} + +// IMSINH function returns the hyperbolic sine of a supplied complex number. +// The syntax of the function is: +// +// IMSINH(inumber) +// +func (fn *formulaFuncs) IMSINH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMSINH requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Sinh(inumber), value[len(value)-1:])) +} + +// IMSQRT function returns the square root of a supplied complex number. The +// syntax of the function is: +// +// IMSQRT(inumber) +// +func (fn *formulaFuncs) IMSQRT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMSQRT requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Sqrt(inumber), value[len(value)-1:])) +} + +// IMSUB function calculates the difference between two complex numbers +// (i.e. subtracts one complex number from another). The syntax of the +// function is: +// +// IMSUB(inumber1,inumber2) +// +func (fn *formulaFuncs) IMSUB(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IMSUB requires 2 arguments") + } + i1, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + i2, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(i1-i2, "i")) +} + +// IMSUM function calculates the sum of two or more complex numbers. The +// syntax of the function is: +// +// IMSUM(inumber1,inumber2,...) +// +func (fn *formulaFuncs) IMSUM(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMSUM requires at least 1 argument") + } + var result complex128 + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + num, err := strconv.ParseComplex(str2cmplx(token.Value()), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + result += num + } + return newStringFormulaArg(cmplx2str(result, "i")) +} + +// IMTAN function returns the tangent of a supplied complex number. The syntax +// of the function is: +// +// IMTAN(inumber) +// +func (fn *formulaFuncs) IMTAN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IMTAN requires 1 argument") + } + value := argsList.Front().Value.(formulaArg).Value() + inumber, err := strconv.ParseComplex(str2cmplx(value), 128) + if err != nil { + return newErrorFormulaArg(formulaErrorNUM, err.Error()) + } + return newStringFormulaArg(cmplx2str(cmplx.Tan(inumber), value[len(value)-1:])) +} + +// OCT2BIN function converts an Octal (Base 8) number into a Binary (Base 2) +// number. The syntax of the function is: +// +// OCT2BIN(number,[places]) +// +func (fn *formulaFuncs) OCT2BIN(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "OCT2BIN requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "OCT2BIN allows at most 2 arguments") + } + token := argsList.Front().Value.(formulaArg) + number := token.ToNumber() + if number.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, number.Error) + } + decimal, newList := fn.oct2dec(token.Value()), list.New() + newList.PushBack(decimal) + if argsList.Len() == 2 { + newList.PushBack(argsList.Back().Value.(formulaArg)) + } + return fn.dec2x("OCT2BIN", newList) +} + +// OCT2DEC function converts an Octal (a base-8 number) into a decimal number. +// The syntax of the function is: +// +// OCT2DEC(number) +// +func (fn *formulaFuncs) OCT2DEC(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "OCT2DEC requires 1 numeric argument") + } + token := argsList.Front().Value.(formulaArg) + number := token.ToNumber() + if number.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, number.Error) + } + return fn.oct2dec(token.Value()) +} + +// OCT2HEX function converts an Octal (Base 8) number into a Hexadecimal +// (Base 16) number. The syntax of the function is: +// +// OCT2HEX(number,[places]) +// +func (fn *formulaFuncs) OCT2HEX(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "OCT2HEX requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "OCT2HEX allows at most 2 arguments") + } + token := argsList.Front().Value.(formulaArg) + number := token.ToNumber() + if number.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, number.Error) + } + decimal, newList := fn.oct2dec(token.Value()), list.New() + newList.PushBack(decimal) + if argsList.Len() == 2 { + newList.PushBack(argsList.Back().Value.(formulaArg)) + } + return fn.dec2x("OCT2HEX", newList) +} + +// oct2dec is an implementation of the formula function OCT2DEC. +func (fn *formulaFuncs) oct2dec(number string) formulaArg { + decimal, length := 0.0, len(number) + for i := length; i > 0; i-- { + num, _ := strconv.Atoi(string(number[length-i])) + if i == 10 && string(number[length-i]) == "7" { + decimal += math.Pow(-8.0, float64(i-1)) + continue + } + decimal += float64(num) * math.Pow(8.0, float64(i-1)) + } + return newNumberFormulaArg(decimal) +} + +// Math and Trigonometric Functions + +// ABS function returns the absolute value of any supplied number. The syntax +// of the function is: +// +// ABS(number) +// +func (fn *formulaFuncs) ABS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ABS requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Abs(arg.Number)) +} + +// ACOS function calculates the arccosine (i.e. the inverse cosine) of a given +// number, and returns an angle, in radians, between 0 and π. The syntax of +// the function is: +// +// ACOS(number) +// +func (fn *formulaFuncs) ACOS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ACOS requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Acos(arg.Number)) +} + +// ACOSH function calculates the inverse hyperbolic cosine of a supplied number. +// of the function is: +// +// ACOSH(number) +// +func (fn *formulaFuncs) ACOSH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ACOSH requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Acosh(arg.Number)) +} + +// ACOT function calculates the arccotangent (i.e. the inverse cotangent) of a +// given number, and returns an angle, in radians, between 0 and π. The syntax +// of the function is: +// +// ACOT(number) +// +func (fn *formulaFuncs) ACOT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ACOT requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Pi/2 - math.Atan(arg.Number)) +} + +// ACOTH function calculates the hyperbolic arccotangent (coth) of a supplied +// value. The syntax of the function is: +// +// ACOTH(number) +// +func (fn *formulaFuncs) ACOTH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ACOTH requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Atanh(1 / arg.Number)) +} + +// ARABIC function converts a Roman numeral into an Arabic numeral. The syntax +// of the function is: +// +// ARABIC(text) +// +func (fn *formulaFuncs) ARABIC(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ARABIC requires 1 numeric argument") + } + text := argsList.Front().Value.(formulaArg).Value() + if len(text) > MaxFieldLength { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + text = strings.ToUpper(text) + number, actualStart, index, isNegative := 0, 0, len(text)-1, false + startIndex, subtractNumber, currentPartValue, currentCharValue, prevCharValue := 0, 0, 0, 0, -1 + for index >= 0 && text[index] == ' ' { + index-- + } + for actualStart <= index && text[actualStart] == ' ' { + actualStart++ + } + if actualStart <= index && text[actualStart] == '-' { + isNegative = true + actualStart++ + } + charMap := map[rune]int{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000} + for index >= actualStart { + startIndex = index + startChar := text[startIndex] + index-- + for index >= actualStart && (text[index]|' ') == startChar { + index-- + } + currentCharValue = charMap[rune(startChar)] + currentPartValue = (startIndex - index) * currentCharValue + if currentCharValue >= prevCharValue { + number += currentPartValue - subtractNumber + prevCharValue = currentCharValue + subtractNumber = 0 + continue + } + subtractNumber += currentPartValue + } + if subtractNumber != 0 { + number -= subtractNumber + } + if isNegative { + number = -number + } + return newNumberFormulaArg(float64(number)) +} + +// ASIN function calculates the arcsine (i.e. the inverse sine) of a given +// number, and returns an angle, in radians, between -π/2 and π/2. The syntax +// of the function is: +// +// ASIN(number) +// +func (fn *formulaFuncs) ASIN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ASIN requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Asin(arg.Number)) +} + +// ASINH function calculates the inverse hyperbolic sine of a supplied number. +// The syntax of the function is: +// +// ASINH(number) +// +func (fn *formulaFuncs) ASINH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ASINH requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Asinh(arg.Number)) +} + +// ATAN function calculates the arctangent (i.e. the inverse tangent) of a +// given number, and returns an angle, in radians, between -π/2 and +π/2. The +// syntax of the function is: +// +// ATAN(number) +// +func (fn *formulaFuncs) ATAN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ATAN requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Atan(arg.Number)) +} + +// ATANH function calculates the inverse hyperbolic tangent of a supplied +// number. The syntax of the function is: +// +// ATANH(number) +// +func (fn *formulaFuncs) ATANH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ATANH requires 1 numeric argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type == ArgError { + return arg + } + return newNumberFormulaArg(math.Atanh(arg.Number)) +} + +// ATAN2 function calculates the arctangent (i.e. the inverse tangent) of a +// given set of x and y coordinates, and returns an angle, in radians, between +// -π/2 and +π/2. The syntax of the function is: +// +// ATAN2(x_num,y_num) +// +func (fn *formulaFuncs) ATAN2(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ATAN2 requires 2 numeric arguments") + } + x := argsList.Back().Value.(formulaArg).ToNumber() + if x.Type == ArgError { + return x + } + y := argsList.Front().Value.(formulaArg).ToNumber() + if y.Type == ArgError { + return y + } + return newNumberFormulaArg(math.Atan2(x.Number, y.Number)) +} + +// BASE function converts a number into a supplied base (radix), and returns a +// text representation of the calculated value. The syntax of the function is: +// +// BASE(number,radix,[min_length]) +// +func (fn *formulaFuncs) BASE(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "BASE requires at least 2 arguments") + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "BASE allows at most 3 arguments") + } + var minLength int + var err error + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + radix := argsList.Front().Next().Value.(formulaArg).ToNumber() + if radix.Type == ArgError { + return radix + } + if int(radix.Number) < 2 || int(radix.Number) > 36 { + return newErrorFormulaArg(formulaErrorVALUE, "radix must be an integer >= 2 and <= 36") + } + if argsList.Len() > 2 { + if minLength, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + } + result := strconv.FormatInt(int64(number.Number), int(radix.Number)) + if len(result) < minLength { + result = strings.Repeat("0", minLength-len(result)) + result + } + return newStringFormulaArg(strings.ToUpper(result)) +} + +// CEILING function rounds a supplied number away from zero, to the nearest +// multiple of a given number. The syntax of the function is: +// +// CEILING(number,significance) +// +func (fn *formulaFuncs) CEILING(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "CEILING requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CEILING allows at most 2 arguments") + } + number, significance, res := 0.0, 1.0, 0.0 + n := argsList.Front().Value.(formulaArg).ToNumber() + if n.Type == ArgError { + return n + } + number = n.Number + if number < 0 { + significance = -1 + } + if argsList.Len() > 1 { + s := argsList.Back().Value.(formulaArg).ToNumber() + if s.Type == ArgError { + return s + } + significance = s.Number + } + if significance < 0 && number > 0 { + return newErrorFormulaArg(formulaErrorVALUE, "negative sig to CEILING invalid") + } + if argsList.Len() == 1 { + return newNumberFormulaArg(math.Ceil(number)) + } + number, res = math.Modf(number / significance) + if res > 0 { + number++ + } + return newNumberFormulaArg(number * significance) +} + +// CEILINGdotMATH function rounds a supplied number up to a supplied multiple +// of significance. The syntax of the function is: +// +// CEILING.MATH(number,[significance],[mode]) +// +func (fn *formulaFuncs) CEILINGdotMATH(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH requires at least 1 argument") + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH allows at most 3 arguments") + } + number, significance, mode := 0.0, 1.0, 1.0 + n := argsList.Front().Value.(formulaArg).ToNumber() + if n.Type == ArgError { + return n + } + number = n.Number + if number < 0 { + significance = -1 + } + if argsList.Len() > 1 { + s := argsList.Front().Next().Value.(formulaArg).ToNumber() + if s.Type == ArgError { + return s + } + significance = s.Number + } + if argsList.Len() == 1 { + return newNumberFormulaArg(math.Ceil(number)) + } + if argsList.Len() > 2 { + m := argsList.Back().Value.(formulaArg).ToNumber() + if m.Type == ArgError { + return m + } + mode = m.Number + } + val, res := math.Modf(number / significance) + if res != 0 { + if number > 0 { + val++ + } else if mode < 0 { + val-- + } + } + return newNumberFormulaArg(val * significance) +} + +// CEILINGdotPRECISE function rounds a supplied number up (regardless of the +// number's sign), to the nearest multiple of a given number. The syntax of +// the function is: +// +// CEILING.PRECISE(number,[significance]) +// +func (fn *formulaFuncs) CEILINGdotPRECISE(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE allows at most 2 arguments") + } + number, significance := 0.0, 1.0 + n := argsList.Front().Value.(formulaArg).ToNumber() + if n.Type == ArgError { + return n + } + number = n.Number + if number < 0 { + significance = -1 + } + if argsList.Len() == 1 { + return newNumberFormulaArg(math.Ceil(number)) + } + if argsList.Len() > 1 { + s := argsList.Back().Value.(formulaArg).ToNumber() + if s.Type == ArgError { + return s + } + significance = s.Number + significance = math.Abs(significance) + if significance == 0 { + return newNumberFormulaArg(significance) + } + } + val, res := math.Modf(number / significance) + if res != 0 { + if number > 0 { + val++ + } + } + return newNumberFormulaArg(val * significance) +} + +// COMBIN function calculates the number of combinations (in any order) of a +// given number objects from a set. The syntax of the function is: +// +// COMBIN(number,number_chosen) +// +func (fn *formulaFuncs) COMBIN(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "COMBIN requires 2 argument") + } + number, chosen, val := 0.0, 0.0, 1.0 + n := argsList.Front().Value.(formulaArg).ToNumber() + if n.Type == ArgError { + return n + } + number = n.Number + c := argsList.Back().Value.(formulaArg).ToNumber() + if c.Type == ArgError { + return c + } + chosen = c.Number + number, chosen = math.Trunc(number), math.Trunc(chosen) + if chosen > number { + return newErrorFormulaArg(formulaErrorVALUE, "COMBIN requires number >= number_chosen") + } + if chosen == number || chosen == 0 { + return newNumberFormulaArg(1) + } + for c := float64(1); c <= chosen; c++ { + val *= (number + 1 - c) / c + } + return newNumberFormulaArg(math.Ceil(val)) +} + +// COMBINA function calculates the number of combinations, with repetitions, +// of a given number objects from a set. The syntax of the function is: +// +// COMBINA(number,number_chosen) +// +func (fn *formulaFuncs) COMBINA(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "COMBINA requires 2 argument") + } + var number, chosen float64 + n := argsList.Front().Value.(formulaArg).ToNumber() + if n.Type == ArgError { + return n + } + number = n.Number + c := argsList.Back().Value.(formulaArg).ToNumber() + if c.Type == ArgError { + return c + } + chosen = c.Number + number, chosen = math.Trunc(number), math.Trunc(chosen) + if number < chosen { + return newErrorFormulaArg(formulaErrorVALUE, "COMBINA requires number > number_chosen") + } + if number == 0 { + return newNumberFormulaArg(number) + } + args := list.New() + args.PushBack(formulaArg{ + String: fmt.Sprintf("%g", number+chosen-1), + Type: ArgString, + }) + args.PushBack(formulaArg{ + String: fmt.Sprintf("%g", number-1), + Type: ArgString, + }) + return fn.COMBIN(args) +} + +// COS function calculates the cosine of a given angle. The syntax of the +// function is: +// +// COS(number) +// +func (fn *formulaFuncs) COS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "COS requires 1 numeric argument") + } + val := argsList.Front().Value.(formulaArg).ToNumber() + if val.Type == ArgError { + return val + } + return newNumberFormulaArg(math.Cos(val.Number)) +} + +// COSH function calculates the hyperbolic cosine (cosh) of a supplied number. +// The syntax of the function is: +// +// COSH(number) +// +func (fn *formulaFuncs) COSH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "COSH requires 1 numeric argument") + } + val := argsList.Front().Value.(formulaArg).ToNumber() + if val.Type == ArgError { + return val + } + return newNumberFormulaArg(math.Cosh(val.Number)) +} + +// COT function calculates the cotangent of a given angle. The syntax of the +// function is: +// +// COT(number) +// +func (fn *formulaFuncs) COT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "COT requires 1 numeric argument") + } + val := argsList.Front().Value.(formulaArg).ToNumber() + if val.Type == ArgError { + return val + } + if val.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(1 / math.Tan(val.Number)) +} + +// COTH function calculates the hyperbolic cotangent (coth) of a supplied +// angle. The syntax of the function is: +// +// COTH(number) +// +func (fn *formulaFuncs) COTH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "COTH requires 1 numeric argument") + } + val := argsList.Front().Value.(formulaArg).ToNumber() + if val.Type == ArgError { + return val + } + if val.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg((math.Exp(val.Number) + math.Exp(-val.Number)) / (math.Exp(val.Number) - math.Exp(-val.Number))) +} + +// CSC function calculates the cosecant of a given angle. The syntax of the +// function is: +// +// CSC(number) +// +func (fn *formulaFuncs) CSC(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "CSC requires 1 numeric argument") + } + val := argsList.Front().Value.(formulaArg).ToNumber() + if val.Type == ArgError { + return val + } + if val.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(1 / math.Sin(val.Number)) +} + +// CSCH function calculates the hyperbolic cosecant (csch) of a supplied +// angle. The syntax of the function is: +// +// CSCH(number) +// +func (fn *formulaFuncs) CSCH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "CSCH requires 1 numeric argument") + } + val := argsList.Front().Value.(formulaArg).ToNumber() + if val.Type == ArgError { + return val + } + if val.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(1 / math.Sinh(val.Number)) +} + +// DECIMAL function converts a text representation of a number in a specified +// base, into a decimal value. The syntax of the function is: +// +// DECIMAL(text,radix) +// +func (fn *formulaFuncs) DECIMAL(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "DECIMAL requires 2 numeric arguments") + } + text := argsList.Front().Value.(formulaArg).String + var radix int + var err error + radix, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String) + if err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + if len(text) > 2 && (strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X")) { + text = text[2:] + } + val, err := strconv.ParseInt(text, radix, 64) + if err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + return newNumberFormulaArg(float64(val)) +} + +// DEGREES function converts radians into degrees. The syntax of the function +// is: +// +// DEGREES(angle) +// +func (fn *formulaFuncs) DEGREES(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DEGREES requires 1 numeric argument") + } + val := argsList.Front().Value.(formulaArg).ToNumber() + if val.Type == ArgError { + return val + } + if val.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(180.0 / math.Pi * val.Number) +} + +// EVEN function rounds a supplied number away from zero (i.e. rounds a +// positive number up and a negative number down), to the next even number. +// The syntax of the function is: +// +// EVEN(number) +// +func (fn *formulaFuncs) EVEN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "EVEN requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + sign := math.Signbit(number.Number) + m, frac := math.Modf(number.Number / 2) + val := m * 2 + if frac != 0 { + if !sign { + val += 2 + } else { + val -= 2 + } + } + return newNumberFormulaArg(val) +} + +// EXP function calculates the value of the mathematical constant e, raised to +// the power of a given number. The syntax of the function is: +// +// EXP(number) +// +func (fn *formulaFuncs) EXP(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "EXP requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", math.Exp(number.Number)))) +} + +// fact returns the factorial of a supplied number. +func fact(number float64) float64 { + val := float64(1) + for i := float64(2); i <= number; i++ { + val *= i + } + return val +} + +// FACT function returns the factorial of a supplied number. The syntax of the +// function is: +// +// FACT(number) +// +func (fn *formulaFuncs) FACT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "FACT requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if number.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(fact(number.Number)) +} + +// FACTDOUBLE function returns the double factorial of a supplied number. The +// syntax of the function is: +// +// FACTDOUBLE(number) +// +func (fn *formulaFuncs) FACTDOUBLE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "FACTDOUBLE requires 1 numeric argument") + } + val := 1.0 + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if number.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + for i := math.Trunc(number.Number); i > 1; i -= 2 { + val *= i + } + return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", val))) +} + +// FLOOR function rounds a supplied number towards zero to the nearest +// multiple of a specified significance. The syntax of the function is: +// +// FLOOR(number,significance) +// +func (fn *formulaFuncs) FLOOR(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "FLOOR requires 2 numeric arguments") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + significance := argsList.Back().Value.(formulaArg).ToNumber() + if significance.Type == ArgError { + return significance + } + if significance.Number < 0 && number.Number >= 0 { + return newErrorFormulaArg(formulaErrorNUM, "invalid arguments to FLOOR") + } + val := number.Number + val, res := math.Modf(val / significance.Number) + if res != 0 { + if number.Number < 0 && res < 0 { + val-- + } + } + return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", val*significance.Number))) +} + +// FLOORdotMATH function rounds a supplied number down to a supplied multiple +// of significance. The syntax of the function is: +// +// FLOOR.MATH(number,[significance],[mode]) +// +func (fn *formulaFuncs) FLOORdotMATH(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH requires at least 1 argument") + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH allows at most 3 arguments") + } + significance, mode := 1.0, 1.0 + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if number.Number < 0 { + significance = -1 + } + if argsList.Len() > 1 { + s := argsList.Front().Next().Value.(formulaArg).ToNumber() + if s.Type == ArgError { + return s + } + significance = s.Number + } + if argsList.Len() == 1 { + return newNumberFormulaArg(math.Floor(number.Number)) + } + if argsList.Len() > 2 { + m := argsList.Back().Value.(formulaArg).ToNumber() + if m.Type == ArgError { + return m + } + mode = m.Number + } + val, res := math.Modf(number.Number / significance) + if res != 0 && number.Number < 0 && mode > 0 { + val-- + } + return newNumberFormulaArg(val * significance) +} + +// FLOORdotPRECISE function rounds a supplied number down to a supplied +// multiple of significance. The syntax of the function is: +// +// FLOOR.PRECISE(number,[significance]) +// +func (fn *formulaFuncs) FLOORdotPRECISE(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE allows at most 2 arguments") + } + var significance float64 + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if number.Number < 0 { + significance = -1 + } + if argsList.Len() == 1 { + return newNumberFormulaArg(math.Floor(number.Number)) + } + if argsList.Len() > 1 { + s := argsList.Back().Value.(formulaArg).ToNumber() + if s.Type == ArgError { + return s + } + significance = s.Number + significance = math.Abs(significance) + if significance == 0 { + return newNumberFormulaArg(significance) + } + } + val, res := math.Modf(number.Number / significance) + if res != 0 { + if number.Number < 0 { + val-- + } + } + return newNumberFormulaArg(val * significance) +} + +// gcd returns the greatest common divisor of two supplied integers. +func gcd(x, y float64) float64 { + x, y = math.Trunc(x), math.Trunc(y) + if x == 0 { + return y + } + if y == 0 { + return x + } + for x != y { + if x > y { + x = x - y + } else { + y = y - x + } + } + return x +} + +// GCD function returns the greatest common divisor of two or more supplied +// integers. The syntax of the function is: +// +// GCD(number1,[number2],...) +// +func (fn *formulaFuncs) GCD(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "GCD requires at least 1 argument") + } + var ( + val float64 + nums []float64 + ) + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgString: + num := token.ToNumber() + if num.Type == ArgError { + return num + } + val = num.Number + case ArgNumber: + val = token.Number + } + nums = append(nums, val) + } + if nums[0] < 0 { + return newErrorFormulaArg(formulaErrorVALUE, "GCD only accepts positive arguments") + } + if len(nums) == 1 { + return newNumberFormulaArg(nums[0]) + } + cd := nums[0] + for i := 1; i < len(nums); i++ { + if nums[i] < 0 { + return newErrorFormulaArg(formulaErrorVALUE, "GCD only accepts positive arguments") + } + cd = gcd(cd, nums[i]) + } + return newNumberFormulaArg(cd) +} + +// INT function truncates a supplied number down to the closest integer. The +// syntax of the function is: +// +// INT(number) +// +func (fn *formulaFuncs) INT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "INT requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + val, frac := math.Modf(number.Number) + if frac < 0 { + val-- + } + return newNumberFormulaArg(val) +} + +// ISOdotCEILING function rounds a supplied number up (regardless of the +// number's sign), to the nearest multiple of a supplied significance. The +// syntax of the function is: +// +// ISO.CEILING(number,[significance]) +// +func (fn *formulaFuncs) ISOdotCEILING(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING allows at most 2 arguments") + } + var significance float64 + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if number.Number < 0 { + significance = -1 + } + if argsList.Len() == 1 { + return newNumberFormulaArg(math.Ceil(number.Number)) + } + if argsList.Len() > 1 { + s := argsList.Back().Value.(formulaArg).ToNumber() + if s.Type == ArgError { + return s + } + significance = s.Number + significance = math.Abs(significance) + if significance == 0 { + return newNumberFormulaArg(significance) + } + } + val, res := math.Modf(number.Number / significance) + if res != 0 { + if number.Number > 0 { + val++ + } + } + return newNumberFormulaArg(val * significance) +} + +// lcm returns the least common multiple of two supplied integers. +func lcm(a, b float64) float64 { + a = math.Trunc(a) + b = math.Trunc(b) + if a == 0 && b == 0 { + return 0 + } + return a * b / gcd(a, b) +} + +// LCM function returns the least common multiple of two or more supplied +// integers. The syntax of the function is: +// +// LCM(number1,[number2],...) +// +func (fn *formulaFuncs) LCM(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "LCM requires at least 1 argument") + } + var ( + val float64 + nums []float64 + err error + ) + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgString: + if token.String == "" { + continue + } + if val, err = strconv.ParseFloat(token.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + case ArgNumber: + val = token.Number + } + nums = append(nums, val) + } + if nums[0] < 0 { + return newErrorFormulaArg(formulaErrorVALUE, "LCM only accepts positive arguments") + } + if len(nums) == 1 { + return newNumberFormulaArg(nums[0]) + } + cm := nums[0] + for i := 1; i < len(nums); i++ { + if nums[i] < 0 { + return newErrorFormulaArg(formulaErrorVALUE, "LCM only accepts positive arguments") + } + cm = lcm(cm, nums[i]) + } + return newNumberFormulaArg(cm) +} + +// LN function calculates the natural logarithm of a given number. The syntax +// of the function is: +// +// LN(number) +// +func (fn *formulaFuncs) LN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "LN requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(math.Log(number.Number)) +} + +// LOG function calculates the logarithm of a given number, to a supplied +// base. The syntax of the function is: +// +// LOG(number,[base]) +// +func (fn *formulaFuncs) LOG(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "LOG requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "LOG allows at most 2 arguments") + } + base := 10.0 + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if argsList.Len() > 1 { + b := argsList.Back().Value.(formulaArg).ToNumber() + if b.Type == ArgError { + return b + } + base = b.Number + } + if number.Number == 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorDIV) + } + if base == 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorDIV) + } + if base == 1 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(math.Log(number.Number) / math.Log(base)) +} + +// LOG10 function calculates the base 10 logarithm of a given number. The +// syntax of the function is: +// +// LOG10(number) +// +func (fn *formulaFuncs) LOG10(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "LOG10 requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(math.Log10(number.Number)) +} + +// minor function implement a minor of a matrix A is the determinant of some +// smaller square matrix. +func minor(sqMtx [][]float64, idx int) [][]float64 { + var ret [][]float64 + for i := range sqMtx { + if i == 0 { + continue + } + var row []float64 + for j := range sqMtx { + if j == idx { + continue + } + row = append(row, sqMtx[i][j]) + } + ret = append(ret, row) + } + return ret +} + +// det determinant of the 2x2 matrix. +func det(sqMtx [][]float64) float64 { + if len(sqMtx) == 2 { + m00 := sqMtx[0][0] + m01 := sqMtx[0][1] + m10 := sqMtx[1][0] + m11 := sqMtx[1][1] + return m00*m11 - m10*m01 + } + var res, sgn float64 = 0, 1 + for j := range sqMtx { + res += sgn * sqMtx[0][j] * det(minor(sqMtx, j)) + sgn *= -1 + } + return res +} + +// MDETERM calculates the determinant of a square matrix. The +// syntax of the function is: +// +// MDETERM(array) +// +func (fn *formulaFuncs) MDETERM(argsList *list.List) (result formulaArg) { + var ( + num float64 + numMtx [][]float64 + err error + strMtx [][]formulaArg + ) + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "MDETERM requires at least 1 argument") + } + strMtx = argsList.Front().Value.(formulaArg).Matrix + rows := len(strMtx) + for _, row := range argsList.Front().Value.(formulaArg).Matrix { + if len(row) != rows { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + var numRow []float64 + for _, ele := range row { + if num, err = strconv.ParseFloat(ele.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + numRow = append(numRow, num) + } + numMtx = append(numMtx, numRow) + } + return newNumberFormulaArg(det(numMtx)) +} + +// MOD function returns the remainder of a division between two supplied +// numbers. The syntax of the function is: +// +// MOD(number,divisor) +// +func (fn *formulaFuncs) MOD(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "MOD requires 2 numeric arguments") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + divisor := argsList.Back().Value.(formulaArg).ToNumber() + if divisor.Type == ArgError { + return divisor + } + if divisor.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, "MOD divide by zero") + } + trunc, rem := math.Modf(number.Number / divisor.Number) + if rem < 0 { + trunc-- + } + return newNumberFormulaArg(number.Number - divisor.Number*trunc) +} + +// MROUND function rounds a supplied number up or down to the nearest multiple +// of a given number. The syntax of the function is: +// +// MROUND(number,multiple) +// +func (fn *formulaFuncs) MROUND(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "MROUND requires 2 numeric arguments") + } + n := argsList.Front().Value.(formulaArg).ToNumber() + if n.Type == ArgError { + return n + } + multiple := argsList.Back().Value.(formulaArg).ToNumber() + if multiple.Type == ArgError { + return multiple + } + if multiple.Number == 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if multiple.Number < 0 && n.Number > 0 || + multiple.Number > 0 && n.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + number, res := math.Modf(n.Number / multiple.Number) + if math.Trunc(res+0.5) > 0 { + number++ + } + return newNumberFormulaArg(number * multiple.Number) +} + +// MULTINOMIAL function calculates the ratio of the factorial of a sum of +// supplied values to the product of factorials of those values. The syntax of +// the function is: +// +// MULTINOMIAL(number1,[number2],...) +// +func (fn *formulaFuncs) MULTINOMIAL(argsList *list.List) formulaArg { + val, num, denom := 0.0, 0.0, 1.0 + var err error + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgString: + if token.String == "" { + continue + } + if val, err = strconv.ParseFloat(token.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + case ArgNumber: + val = token.Number + } + num += val + denom *= fact(val) + } + return newNumberFormulaArg(fact(num) / denom) +} + +// MUNIT function returns the unit matrix for a specified dimension. The +// syntax of the function is: +// +// MUNIT(dimension) +// +func (fn *formulaFuncs) MUNIT(argsList *list.List) (result formulaArg) { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "MUNIT requires 1 numeric argument") + } + dimension := argsList.Back().Value.(formulaArg).ToNumber() + if dimension.Type == ArgError || dimension.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, dimension.Error) + } + matrix := make([][]formulaArg, 0, int(dimension.Number)) + for i := 0; i < int(dimension.Number); i++ { + row := make([]formulaArg, int(dimension.Number)) + for j := 0; j < int(dimension.Number); j++ { + if i == j { + row[j] = newNumberFormulaArg(1.0) + } else { + row[j] = newNumberFormulaArg(0.0) + } + } + matrix = append(matrix, row) + } + return newMatrixFormulaArg(matrix) +} + +// ODD function ounds a supplied number away from zero (i.e. rounds a positive +// number up and a negative number down), to the next odd number. The syntax +// of the function is: +// +// ODD(number) +// +func (fn *formulaFuncs) ODD(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ODD requires 1 numeric argument") + } + number := argsList.Back().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if number.Number == 0 { + return newNumberFormulaArg(1) + } + sign := math.Signbit(number.Number) + m, frac := math.Modf((number.Number - 1) / 2) + val := m*2 + 1 + if frac != 0 { + if !sign { + val += 2 + } else { + val -= 2 + } + } + return newNumberFormulaArg(val) +} + +// PI function returns the value of the mathematical constant π (pi), accurate +// to 15 digits (14 decimal places). The syntax of the function is: +// +// PI() +// +func (fn *formulaFuncs) PI(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "PI accepts no arguments") + } + return newNumberFormulaArg(math.Pi) +} + +// POWER function calculates a given number, raised to a supplied power. +// The syntax of the function is: +// +// POWER(number,power) +// +func (fn *formulaFuncs) POWER(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "POWER requires 2 numeric arguments") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type == ArgError { + return x + } + y := argsList.Back().Value.(formulaArg).ToNumber() + if y.Type == ArgError { + return y + } + if x.Number == 0 && y.Number == 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if x.Number == 0 && y.Number < 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(math.Pow(x.Number, y.Number)) +} + +// PRODUCT function returns the product (multiplication) of a supplied set of +// numerical values. The syntax of the function is: +// +// PRODUCT(number1,[number2],...) +// +func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg { + val, product := 0.0, 1.0 + var err error + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgUnknown: + continue + case ArgString: + if token.String == "" { + continue + } + if val, err = strconv.ParseFloat(token.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + product = product * val + case ArgNumber: + product = product * token.Number + case ArgMatrix: + for _, row := range token.Matrix { + for _, value := range row { + if value.String == "" { + continue + } + if val, err = strconv.ParseFloat(value.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + product = product * val + } + } + } + } + return newNumberFormulaArg(product) +} + +// QUOTIENT function returns the integer portion of a division between two +// supplied numbers. The syntax of the function is: +// +// QUOTIENT(numerator,denominator) +// +func (fn *formulaFuncs) QUOTIENT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "QUOTIENT requires 2 numeric arguments") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type == ArgError { + return x + } + y := argsList.Back().Value.(formulaArg).ToNumber() + if y.Type == ArgError { + return y + } + if y.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(math.Trunc(x.Number / y.Number)) +} + +// RADIANS function converts radians into degrees. The syntax of the function is: +// +// RADIANS(angle) +// +func (fn *formulaFuncs) RADIANS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "RADIANS requires 1 numeric argument") + } + angle := argsList.Front().Value.(formulaArg).ToNumber() + if angle.Type == ArgError { + return angle + } + return newNumberFormulaArg(math.Pi / 180.0 * angle.Number) +} + +// RAND function generates a random real number between 0 and 1. The syntax of +// the function is: +// +// RAND() +// +func (fn *formulaFuncs) RAND(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "RAND accepts no arguments") + } + return newNumberFormulaArg(rand.New(rand.NewSource(time.Now().UnixNano())).Float64()) +} + +// RANDBETWEEN function generates a random integer between two supplied +// integers. The syntax of the function is: +// +// RANDBETWEEN(bottom,top) +// +func (fn *formulaFuncs) RANDBETWEEN(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "RANDBETWEEN requires 2 numeric arguments") + } + bottom := argsList.Front().Value.(formulaArg).ToNumber() + if bottom.Type == ArgError { + return bottom + } + top := argsList.Back().Value.(formulaArg).ToNumber() + if top.Type == ArgError { + return top + } + if top.Number < bottom.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + num := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(int64(top.Number - bottom.Number + 1)) + return newNumberFormulaArg(float64(num + int64(bottom.Number))) +} + +// romanNumerals defined a numeral system that originated in ancient Rome and +// remained the usual way of writing numbers throughout Europe well into the +// Late Middle Ages. +type romanNumerals struct { + n float64 + s string +} + +var romanTable = [][]romanNumerals{ + { + {1000, "M"}, + {900, "CM"}, + {500, "D"}, + {400, "CD"}, + {100, "C"}, + {90, "XC"}, + {50, "L"}, + {40, "XL"}, + {10, "X"}, + {9, "IX"}, + {5, "V"}, + {4, "IV"}, + {1, "I"}, + }, + { + {1000, "M"}, + {950, "LM"}, + {900, "CM"}, + {500, "D"}, + {450, "LD"}, + {400, "CD"}, + {100, "C"}, + {95, "VC"}, + {90, "XC"}, + {50, "L"}, + {45, "VL"}, + {40, "XL"}, + {10, "X"}, + {9, "IX"}, + {5, "V"}, + {4, "IV"}, + {1, "I"}, + }, + { + {1000, "M"}, + {990, "XM"}, + {950, "LM"}, + {900, "CM"}, + {500, "D"}, + {490, "XD"}, + {450, "LD"}, + {400, "CD"}, + {100, "C"}, + {99, "IC"}, + {90, "XC"}, + {50, "L"}, + {45, "VL"}, + {40, "XL"}, + {10, "X"}, + {9, "IX"}, + {5, "V"}, + {4, "IV"}, + {1, "I"}, + }, + { + {1000, "M"}, + {995, "VM"}, + {990, "XM"}, + {950, "LM"}, + {900, "CM"}, + {500, "D"}, + {495, "VD"}, + {490, "XD"}, + {450, "LD"}, + {400, "CD"}, + {100, "C"}, + {99, "IC"}, + {90, "XC"}, + {50, "L"}, + {45, "VL"}, + {40, "XL"}, + {10, "X"}, + {9, "IX"}, + {5, "V"}, + {4, "IV"}, + {1, "I"}, + }, + { + {1000, "M"}, + {999, "IM"}, + {995, "VM"}, + {990, "XM"}, + {950, "LM"}, + {900, "CM"}, + {500, "D"}, + {499, "ID"}, + {495, "VD"}, + {490, "XD"}, + {450, "LD"}, + {400, "CD"}, + {100, "C"}, + {99, "IC"}, + {90, "XC"}, + {50, "L"}, + {45, "VL"}, + {40, "XL"}, + {10, "X"}, + {9, "IX"}, + {5, "V"}, + {4, "IV"}, + {1, "I"}, + }, +} + +// ROMAN function converts an arabic number to Roman. I.e. for a supplied +// integer, the function returns a text string depicting the roman numeral +// form of the number. The syntax of the function is: +// +// ROMAN(number,[form]) +// +func (fn *formulaFuncs) ROMAN(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "ROMAN requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ROMAN allows at most 2 arguments") + } + var form int + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if argsList.Len() > 1 { + f := argsList.Back().Value.(formulaArg).ToNumber() + if f.Type == ArgError { + return f + } + form = int(f.Number) + if form < 0 { + form = 0 + } else if form > 4 { + form = 4 + } + } + decimalTable := romanTable[0] + switch form { + case 1: + decimalTable = romanTable[1] + case 2: + decimalTable = romanTable[2] + case 3: + decimalTable = romanTable[3] + case 4: + decimalTable = romanTable[4] + } + val := math.Trunc(number.Number) + buf := bytes.Buffer{} + for _, r := range decimalTable { + for val >= r.n { + buf.WriteString(r.s) + val -= r.n + } + } + return newStringFormulaArg(buf.String()) +} + +type roundMode byte + +const ( + closest roundMode = iota + down + up +) + +// round rounds a supplied number up or down. +func (fn *formulaFuncs) round(number, digits float64, mode roundMode) float64 { + var significance float64 + if digits > 0 { + significance = math.Pow(1/10.0, digits) + } else { + significance = math.Pow(10.0, -digits) + } + val, res := math.Modf(number / significance) + switch mode { + case closest: + const eps = 0.499999999 + if res >= eps { + val++ + } else if res <= -eps { + val-- + } + case down: + case up: + if res > 0 { + val++ + } else if res < 0 { + val-- + } + } + return val * significance +} + +// ROUND function rounds a supplied number up or down, to a specified number +// of decimal places. The syntax of the function is: +// +// ROUND(number,num_digits) +// +func (fn *formulaFuncs) ROUND(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ROUND requires 2 numeric arguments") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + digits := argsList.Back().Value.(formulaArg).ToNumber() + if digits.Type == ArgError { + return digits + } + return newNumberFormulaArg(fn.round(number.Number, digits.Number, closest)) +} + +// ROUNDDOWN function rounds a supplied number down towards zero, to a +// specified number of decimal places. The syntax of the function is: +// +// ROUNDDOWN(number,num_digits) +// +func (fn *formulaFuncs) ROUNDDOWN(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ROUNDDOWN requires 2 numeric arguments") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + digits := argsList.Back().Value.(formulaArg).ToNumber() + if digits.Type == ArgError { + return digits + } + return newNumberFormulaArg(fn.round(number.Number, digits.Number, down)) +} + +// ROUNDUP function rounds a supplied number up, away from zero, to a +// specified number of decimal places. The syntax of the function is: +// +// ROUNDUP(number,num_digits) +// +func (fn *formulaFuncs) ROUNDUP(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ROUNDUP requires 2 numeric arguments") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + digits := argsList.Back().Value.(formulaArg).ToNumber() + if digits.Type == ArgError { + return digits + } + return newNumberFormulaArg(fn.round(number.Number, digits.Number, up)) +} + +// SEC function calculates the secant of a given angle. The syntax of the +// function is: +// +// SEC(number) +// +func (fn *formulaFuncs) SEC(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SEC requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(math.Cos(number.Number)) +} + +// SECH function calculates the hyperbolic secant (sech) of a supplied angle. +// The syntax of the function is: +// +// SECH(number) +// +func (fn *formulaFuncs) SECH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SECH requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(1 / math.Cosh(number.Number)) +} + +// SERIESSUM function returns the sum of a power series. The syntax of the +// function is: +// +// SERIESSUM(x,n,m,coefficients) +// +func (fn *formulaFuncs) SERIESSUM(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "SERIESSUM requires 4 arguments") + } + var x, n, m formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if n = argsList.Front().Next().Value.(formulaArg).ToNumber(); n.Type != ArgNumber { + return n + } + if m = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); m.Type != ArgNumber { + return m + } + var result, i float64 + for _, coefficient := range argsList.Back().Value.(formulaArg).ToList() { + if coefficient.Value() == "" { + continue + } + num := coefficient.ToNumber() + if num.Type != ArgNumber { + return num + } + result += num.Number * math.Pow(x.Number, n.Number+(m.Number*i)) + i++ + } + return newNumberFormulaArg(result) +} + +// SIGN function returns the arithmetic sign (+1, -1 or 0) of a supplied +// number. I.e. if the number is positive, the Sign function returns +1, if +// the number is negative, the function returns -1 and if the number is 0 +// (zero), the function returns 0. The syntax of the function is: +// +// SIGN(number) +// +func (fn *formulaFuncs) SIGN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SIGN requires 1 numeric argument") + } + val := argsList.Front().Value.(formulaArg).ToNumber() + if val.Type == ArgError { + return val + } + if val.Number < 0 { + return newNumberFormulaArg(-1) + } + if val.Number > 0 { + return newNumberFormulaArg(1) + } + return newNumberFormulaArg(0) +} + +// SIN function calculates the sine of a given angle. The syntax of the +// function is: +// +// SIN(number) +// +func (fn *formulaFuncs) SIN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SIN requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(math.Sin(number.Number)) +} + +// SINH function calculates the hyperbolic sine (sinh) of a supplied number. +// The syntax of the function is: +// +// SINH(number) +// +func (fn *formulaFuncs) SINH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SINH requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(math.Sinh(number.Number)) +} + +// SQRT function calculates the positive square root of a supplied number. The +// syntax of the function is: +// +// SQRT(number) +// +func (fn *formulaFuncs) SQRT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SQRT requires 1 numeric argument") + } + value := argsList.Front().Value.(formulaArg).ToNumber() + if value.Type == ArgError { + return value + } + if value.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(math.Sqrt(value.Number)) +} + +// SQRTPI function returns the square root of a supplied number multiplied by +// the mathematical constant, π. The syntax of the function is: +// +// SQRTPI(number) +// +func (fn *formulaFuncs) SQRTPI(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SQRTPI requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(math.Sqrt(number.Number * math.Pi)) +} + +// STDEV function calculates the sample standard deviation of a supplied set +// of values. The syntax of the function is: +// +// STDEV(number1,[number2],...) +// +func (fn *formulaFuncs) STDEV(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "STDEV requires at least 1 argument") + } + return fn.stdev(false, argsList) +} + +// STDEVdotS function calculates the sample standard deviation of a supplied +// set of values. The syntax of the function is: +// +// STDEV.S(number1,[number2],...) +// +func (fn *formulaFuncs) STDEVdotS(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "STDEV.S requires at least 1 argument") + } + return fn.stdev(false, argsList) +} + +// STDEVA function estimates standard deviation based on a sample. The +// standard deviation is a measure of how widely values are dispersed from +// the average value (the mean). The syntax of the function is: +// +// STDEVA(number1,[number2],...) +// +func (fn *formulaFuncs) STDEVA(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "STDEVA requires at least 1 argument") + } + return fn.stdev(true, argsList) +} + +// calcStdevPow is part of the implementation stdev. +func calcStdevPow(result, count float64, n, m formulaArg) (float64, float64) { + if result == -1 { + result = math.Pow(n.Number-m.Number, 2) + } else { + result += math.Pow(n.Number-m.Number, 2) + } + count++ + return result, count +} + +// calcStdev is part of the implementation stdev. +func calcStdev(stdeva bool, result, count float64, mean, token formulaArg) (float64, float64) { + for _, row := range token.ToList() { + if row.Type == ArgNumber || row.Type == ArgString { + if !stdeva && (row.Value() == "TRUE" || row.Value() == "FALSE") { + continue + } else if stdeva && (row.Value() == "TRUE" || row.Value() == "FALSE") { + num := row.ToBool() + if num.Type == ArgNumber { + result, count = calcStdevPow(result, count, num, mean) + continue + } + } else { + num := row.ToNumber() + if num.Type == ArgNumber { + result, count = calcStdevPow(result, count, num, mean) + } + } + } + } + return result, count +} + +// stdev is an implementation of the formula functions STDEV and STDEVA. +func (fn *formulaFuncs) stdev(stdeva bool, argsList *list.List) formulaArg { + count, result := -1.0, -1.0 + var mean formulaArg + if stdeva { + mean = fn.AVERAGEA(argsList) + } else { + mean = fn.AVERAGE(argsList) + } + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgString, ArgNumber: + if !stdeva && (token.Value() == "TRUE" || token.Value() == "FALSE") { + continue + } else if stdeva && (token.Value() == "TRUE" || token.Value() == "FALSE") { + num := token.ToBool() + if num.Type == ArgNumber { + result, count = calcStdevPow(result, count, num, mean) + continue + } + } else { + num := token.ToNumber() + if num.Type == ArgNumber { + result, count = calcStdevPow(result, count, num, mean) + } + } + case ArgList, ArgMatrix: + result, count = calcStdev(stdeva, result, count, mean, token) + } + } + if count > 0 && result >= 0 { + return newNumberFormulaArg(math.Sqrt(result / count)) + } + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) +} + +// POISSONdotDIST function calculates the Poisson Probability Mass Function or +// the Cumulative Poisson Probability Function for a supplied set of +// parameters. The syntax of the function is: +// +// POISSON.DIST(x,mean,cumulative) +// +func (fn *formulaFuncs) POISSONdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "POISSON.DIST requires 3 arguments") + } + return fn.POISSON(argsList) +} + +// POISSON function calculates the Poisson Probability Mass Function or the +// Cumulative Poisson Probability Function for a supplied set of parameters. +// The syntax of the function is: +// +// POISSON(x,mean,cumulative) +// +func (fn *formulaFuncs) POISSON(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "POISSON requires 3 arguments") + } + var x, mean, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { + return mean + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { + return cumulative + } + if x.Number < 0 || mean.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if cumulative.Number == 1 { + summer := 0.0 + floor := math.Floor(x.Number) + for i := 0; i <= int(floor); i++ { + summer += math.Pow(mean.Number, float64(i)) / fact(float64(i)) + } + return newNumberFormulaArg(math.Exp(0-mean.Number) * summer) + } + return newNumberFormulaArg(math.Exp(0-mean.Number) * math.Pow(mean.Number, x.Number) / fact(x.Number)) +} + +// SUM function adds together a supplied set of numbers and returns the sum of +// these values. The syntax of the function is: +// +// SUM(number1,[number2],...) +// +func (fn *formulaFuncs) SUM(argsList *list.List) formulaArg { + var sum float64 + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgError: + return token + case ArgString: + if num := token.ToNumber(); num.Type == ArgNumber { + sum += num.Number + } + case ArgNumber: + sum += token.Number + case ArgMatrix: + for _, row := range token.Matrix { + for _, value := range row { + if num := value.ToNumber(); num.Type == ArgNumber { + sum += num.Number + } + } + } + } + } + return newNumberFormulaArg(sum) +} + +// SUMIF function finds the values in a supplied array, that satisfy a given +// criteria, and returns the sum of the corresponding values in a second +// supplied array. The syntax of the function is: +// +// SUMIF(range,criteria,[sum_range]) +// +func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "SUMIF requires at least 2 arguments") + } + criteria := formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String) + rangeMtx := argsList.Front().Value.(formulaArg).Matrix + var sumRange [][]formulaArg + if argsList.Len() == 3 { + sumRange = argsList.Back().Value.(formulaArg).Matrix + } + var sum, val float64 + var err error + for rowIdx, row := range rangeMtx { + for colIdx, col := range row { + var ok bool + fromVal := col.String + if col.String == "" { + continue + } + ok, _ = formulaCriteriaEval(fromVal, criteria) + if ok { + if argsList.Len() == 3 { + if len(sumRange) <= rowIdx || len(sumRange[rowIdx]) <= colIdx { + continue + } + fromVal = sumRange[rowIdx][colIdx].String + } + if val, err = strconv.ParseFloat(fromVal, 64); err != nil { + continue + } + sum += val + } + } + } + return newNumberFormulaArg(sum) +} + +// SUMIFS function finds values in one or more supplied arrays, that satisfy a +// set of criteria, and returns the sum of the corresponding values in a +// further supplied array. The syntax of the function is: +// +// SUMIFS(sum_range,criteria_range1,criteria1,[criteria_range2,criteria2],...) +// +func (fn *formulaFuncs) SUMIFS(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "SUMIFS requires at least 3 arguments") + } + if argsList.Len()%2 != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + var args []formulaArg + sum, sumRange := 0.0, argsList.Front().Value.(formulaArg).Matrix + for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { + args = append(args, arg.Value.(formulaArg)) + } + for _, ref := range formulaIfsMatch(args) { + if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber { + sum += num.Number + } + } + return newNumberFormulaArg(sum) +} + +// sumproduct is an implementation of the formula function SUMPRODUCT. +func (fn *formulaFuncs) sumproduct(argsList *list.List) formulaArg { + var ( + argType ArgType + n int + res []float64 + sum float64 + ) + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + if argType == ArgUnknown { + argType = token.Type + } + if token.Type != argType { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + switch token.Type { + case ArgString, ArgNumber: + if num := token.ToNumber(); num.Type == ArgNumber { + sum = fn.PRODUCT(argsList).Number + continue + } + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + case ArgMatrix: + args := token.ToList() + if res == nil { + n = len(args) + res = make([]float64, n) + for i := range res { + res[i] = 1.0 + } + } + if len(args) != n { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + for i, value := range args { + num := value.ToNumber() + if num.Type != ArgNumber && value.Value() != "" { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + res[i] = res[i] * num.Number + } + } + } + for _, r := range res { + sum += r + } + return newNumberFormulaArg(sum) +} + +// SUMPRODUCT function returns the sum of the products of the corresponding +// values in a set of supplied arrays. The syntax of the function is: +// +// SUMPRODUCT(array1,[array2],[array3],...) +// +func (fn *formulaFuncs) SUMPRODUCT(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SUMPRODUCT requires at least 1 argument") + } + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + if token := arg.Value.(formulaArg); token.Type == ArgError { + return token + } + } + return fn.sumproduct(argsList) +} + +// SUMSQ function returns the sum of squares of a supplied set of values. The +// syntax of the function is: +// +// SUMSQ(number1,[number2],...) +// +func (fn *formulaFuncs) SUMSQ(argsList *list.List) formulaArg { + var val, sq float64 + var err error + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgString: + if token.String == "" { + continue + } + if val, err = strconv.ParseFloat(token.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + sq += val * val + case ArgNumber: + sq += token.Number + case ArgMatrix: + for _, row := range token.Matrix { + for _, value := range row { + if value.String == "" { + continue + } + if val, err = strconv.ParseFloat(value.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + sq += val * val + } + } + } + } + return newNumberFormulaArg(sq) +} + +// sumx is an implementation of the formula functions SUMX2MY2, SUMX2PY2 and +// SUMXMY2. +func (fn *formulaFuncs) sumx(name string, argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name)) + } + array1 := argsList.Front().Value.(formulaArg) + array2 := argsList.Back().Value.(formulaArg) + left, right := array1.ToList(), array2.ToList() + n := len(left) + if n != len(right) { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + result := 0.0 + for i := 0; i < n; i++ { + if lhs, rhs := left[i].ToNumber(), right[i].ToNumber(); lhs.Number != 0 && rhs.Number != 0 { + switch name { + case "SUMX2MY2": + result += lhs.Number*lhs.Number - rhs.Number*rhs.Number + case "SUMX2PY2": + result += lhs.Number*lhs.Number + rhs.Number*rhs.Number + default: + result += (lhs.Number - rhs.Number) * (lhs.Number - rhs.Number) + } + } + } + return newNumberFormulaArg(result) +} + +// SUMX2MY2 function returns the sum of the differences of squares of two +// supplied sets of values. The syntax of the function is: +// +// SUMX2MY2(array_x,array_y) +// +func (fn *formulaFuncs) SUMX2MY2(argsList *list.List) formulaArg { + return fn.sumx("SUMX2MY2", argsList) +} + +// SUMX2PY2 function returns the sum of the sum of squares of two supplied sets +// of values. The syntax of the function is: +// +// SUMX2PY2(array_x,array_y) +// +func (fn *formulaFuncs) SUMX2PY2(argsList *list.List) formulaArg { + return fn.sumx("SUMX2PY2", argsList) +} + +// SUMXMY2 function returns the sum of the squares of differences between +// corresponding values in two supplied arrays. The syntax of the function +// is: +// +// SUMXMY2(array_x,array_y) +// +func (fn *formulaFuncs) SUMXMY2(argsList *list.List) formulaArg { + return fn.sumx("SUMXMY2", argsList) +} + +// TAN function calculates the tangent of a given angle. The syntax of the +// function is: +// +// TAN(number) +// +func (fn *formulaFuncs) TAN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "TAN requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(math.Tan(number.Number)) +} + +// TANH function calculates the hyperbolic tangent (tanh) of a supplied +// number. The syntax of the function is: +// +// TANH(number) +// +func (fn *formulaFuncs) TANH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "TANH requires 1 numeric argument") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + return newNumberFormulaArg(math.Tanh(number.Number)) +} + +// TRUNC function truncates a supplied number to a specified number of decimal +// places. The syntax of the function is: +// +// TRUNC(number,[number_digits]) +// +func (fn *formulaFuncs) TRUNC(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "TRUNC requires at least 1 argument") + } + var digits, adjust, rtrim float64 + var err error + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type == ArgError { + return number + } + if argsList.Len() > 1 { + d := argsList.Back().Value.(formulaArg).ToNumber() + if d.Type == ArgError { + return d + } + digits = d.Number + digits = math.Floor(digits) + } + adjust = math.Pow(10, digits) + x := int((math.Abs(number.Number) - math.Abs(float64(int(number.Number)))) * adjust) + if x != 0 { + if rtrim, err = strconv.ParseFloat(strings.TrimRight(strconv.Itoa(x), "0"), 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + } + if (digits > 0) && (rtrim < adjust/10) { + return newNumberFormulaArg(number.Number) + } + return newNumberFormulaArg(float64(int(number.Number*adjust)) / adjust) +} + +// Statistical Functions + +// AVEDEV function calculates the average deviation of a supplied set of +// values. The syntax of the function is: +// +// AVEDEV(number1,[number2],...) +// +func (fn *formulaFuncs) AVEDEV(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "AVEDEV requires at least 1 argument") + } + average := fn.AVERAGE(argsList) + if average.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + result, count := 0.0, 0.0 + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + num := arg.Value.(formulaArg).ToNumber() + if num.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + result += math.Abs(num.Number - average.Number) + count++ + } + return newNumberFormulaArg(result / count) +} + +// AVERAGE function returns the arithmetic mean of a list of supplied numbers. +// The syntax of the function is: +// +// AVERAGE(number1,[number2],...) +// +func (fn *formulaFuncs) AVERAGE(argsList *list.List) formulaArg { + var args []formulaArg + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + args = append(args, arg.Value.(formulaArg)) + } + count, sum := fn.countSum(false, args) + if count == 0 { + return newErrorFormulaArg(formulaErrorDIV, "AVERAGE divide by zero") + } + return newNumberFormulaArg(sum / count) +} + +// AVERAGEA function returns the arithmetic mean of a list of supplied numbers +// with text cell and zero values. The syntax of the function is: +// +// AVERAGEA(number1,[number2],...) +// +func (fn *formulaFuncs) AVERAGEA(argsList *list.List) formulaArg { + var args []formulaArg + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + args = append(args, arg.Value.(formulaArg)) + } + count, sum := fn.countSum(true, args) + if count == 0 { + return newErrorFormulaArg(formulaErrorDIV, "AVERAGEA divide by zero") + } + return newNumberFormulaArg(sum / count) +} + +// AVERAGEIF function finds the values in a supplied array that satisfy a +// specified criteria, and returns the average (i.e. the statistical mean) of +// the corresponding values in a second supplied array. The syntax of the +// function is: +// +// AVERAGEIF(range,criteria,[average_range]) +// +func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIF requires at least 2 arguments") + } + var ( + criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String) + rangeMtx = argsList.Front().Value.(formulaArg).Matrix + cellRange [][]formulaArg + args []formulaArg + val float64 + err error + ok bool + ) + if argsList.Len() == 3 { + cellRange = argsList.Back().Value.(formulaArg).Matrix + } + for rowIdx, row := range rangeMtx { + for colIdx, col := range row { + fromVal := col.String + if col.String == "" { + continue + } + ok, _ = formulaCriteriaEval(fromVal, criteria) + if ok { + if argsList.Len() == 3 { + if len(cellRange) <= rowIdx || len(cellRange[rowIdx]) <= colIdx { + continue + } + fromVal = cellRange[rowIdx][colIdx].String + } + if val, err = strconv.ParseFloat(fromVal, 64); err != nil { + continue + } + args = append(args, newNumberFormulaArg(val)) + } + } + } + count, sum := fn.countSum(false, args) + if count == 0 { + return newErrorFormulaArg(formulaErrorDIV, "AVERAGEIF divide by zero") + } + return newNumberFormulaArg(sum / count) +} + +// AVERAGEIFS function finds entries in one or more arrays, that satisfy a set +// of supplied criteria, and returns the average (i.e. the statistical mean) +// of the corresponding values in a further supplied array. The syntax of the +// function is: +// +// AVERAGEIFS(average_range,criteria_range1,criteria1,[criteria_range2,criteria2],...) +// +func (fn *formulaFuncs) AVERAGEIFS(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIFS requires at least 3 arguments") + } + if argsList.Len()%2 != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + var args []formulaArg + sum, sumRange := 0.0, argsList.Front().Value.(formulaArg).Matrix + for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { + args = append(args, arg.Value.(formulaArg)) + } + count := 0.0 + for _, ref := range formulaIfsMatch(args) { + if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber { + sum += num.Number + count++ + } + } + if count == 0 { + return newErrorFormulaArg(formulaErrorDIV, "AVERAGEIF divide by zero") + } + return newNumberFormulaArg(sum / count) +} + +// getBetaHelperContFrac continued fractions for the beta function. +func getBetaHelperContFrac(fX, fA, fB float64) float64 { + var a1, b1, a2, b2, fnorm, cfnew, cf, rm float64 + a1, b1, b2 = 1, 1, 1-(fA+fB)/(fA+1)*fX + if b2 == 0 { + a2, fnorm, cf = 0, 1, 1 + } else { + a2, fnorm = 1, 1/b2 + cf = a2 * fnorm + } + cfnew, rm = 1, 1 + fMaxIter, fMachEps := 50000.0, 2.22045e-016 + bfinished := false + for rm < fMaxIter && !bfinished { + apl2m := fA + 2*rm + d2m := rm * (fB - rm) * fX / ((apl2m - 1) * apl2m) + d2m1 := -(fA + rm) * (fA + fB + rm) * fX / (apl2m * (apl2m + 1)) + a1 = (a2 + d2m*a1) * fnorm + b1 = (b2 + d2m*b1) * fnorm + a2 = a1 + d2m1*a2*fnorm + b2 = b1 + d2m1*b2*fnorm + if b2 != 0 { + fnorm = 1 / b2 + cfnew = a2 * fnorm + bfinished = math.Abs(cf-cfnew) < math.Abs(cf)*fMachEps + } + cf = cfnew + rm += 1 + } + return cf +} + +// getLanczosSum uses a variant of the Lanczos sum with a rational function. +func getLanczosSum(fZ float64) float64 { + num := []float64{ + 23531376880.41075968857200767445163675473, + 42919803642.64909876895789904700198885093, + 35711959237.35566804944018545154716670596, + 17921034426.03720969991975575445893111267, + 6039542586.35202800506429164430729792107, + 1439720407.311721673663223072794912393972, + 248874557.8620541565114603864132294232163, + 31426415.58540019438061423162831820536287, + 2876370.628935372441225409051620849613599, + 186056.2653952234950402949897160456992822, + 8071.672002365816210638002902272250613822, + 210.8242777515793458725097339207133627117, + 2.506628274631000270164908177133837338626, + } + denom := []float64{ + 0, + 39916800, + 120543840, + 150917976, + 105258076, + 45995730, + 13339535, + 2637558, + 357423, + 32670, + 1925, + 66, + 1, + } + var sumNum, sumDenom, zInv float64 + if fZ <= 1 { + sumNum = num[12] + sumDenom = denom[12] + for i := 11; i >= 0; i-- { + sumNum *= fZ + sumNum += num[i] + sumDenom *= fZ + sumDenom += denom[i] + } + } else { + zInv = 1 / fZ + sumNum = num[0] + sumDenom = denom[0] + for i := 1; i <= 12; i++ { + sumNum *= zInv + sumNum += num[i] + sumDenom *= zInv + sumDenom += denom[i] + } + } + return sumNum / sumDenom +} + +// getBeta return beta distribution. +func getBeta(fAlpha, fBeta float64) float64 { + var fA, fB float64 + if fAlpha > fBeta { + fA = fAlpha + fB = fBeta + } else { + fA = fBeta + fB = fAlpha + } + const maxGammaArgument = 171.624376956302 + if fA+fB < maxGammaArgument { + return math.Gamma(fA) / math.Gamma(fA+fB) * math.Gamma(fB) + } + fg := 6.024680040776729583740234375 + fgm := fg - 0.5 + fLanczos := getLanczosSum(fA) + fLanczos /= getLanczosSum(fA + fB) + fLanczos *= getLanczosSum(fB) + fABgm := fA + fB + fgm + fLanczos *= math.Sqrt((fABgm / (fA + fgm)) / (fB + fgm)) + fTempA := fB / (fA + fgm) + fTempB := fA / (fB + fgm) + fResult := math.Exp(-fA*math.Log1p(fTempA) - fB*math.Log1p(fTempB) - fgm) + fResult *= fLanczos + return fResult +} + +// getBetaDistPDF is an implementation for the Beta probability density +// function. +func getBetaDistPDF(fX, fA, fB float64) float64 { + if fX <= 0 || fX >= 1 { + return 0 + } + fLogDblMax, fLogDblMin := math.Log(1.79769e+308), math.Log(2.22507e-308) + fLogY := math.Log(0.5 - fX + 0.5) + if fX < 0.1 { + fLogY = math.Log1p(-fX) + } + fLogX := math.Log(fX) + fAm1LogX := (fA - 1) * fLogX + fBm1LogY := (fB - 1) * fLogY + fLogBeta := getLogBeta(fA, fB) + if fAm1LogX < fLogDblMax && fAm1LogX > fLogDblMin && fBm1LogY < fLogDblMax && + fBm1LogY > fLogDblMin && fLogBeta < fLogDblMax && fLogBeta > fLogDblMin && + fAm1LogX+fBm1LogY < fLogDblMax && fAm1LogX+fBm1LogY > fLogDblMin { + return math.Pow(fX, fA-1) * math.Pow(0.5-fX+0.5, fB-1) / getBeta(fA, fB) + } + return math.Exp(fAm1LogX + fBm1LogY - fLogBeta) +} + +// getLogBeta return beta with logarithm. +func getLogBeta(fAlpha, fBeta float64) float64 { + var fA, fB float64 + if fAlpha > fBeta { + fA, fB = fAlpha, fBeta + } else { + fA, fB = fBeta, fAlpha + } + fg := 6.024680040776729583740234375 + fgm := fg - 0.5 + fLanczos := getLanczosSum(fA) + fLanczos /= getLanczosSum(fA + fB) + fLanczos *= getLanczosSum(fB) + fLogLanczos := math.Log(fLanczos) + fABgm := fA + fB + fgm + fLogLanczos += 0.5 * (math.Log(fABgm) - math.Log(fA+fgm) - math.Log(fB+fgm)) + fTempA := fB / (fA + fgm) + fTempB := fA / (fB + fgm) + fResult := -fA*math.Log1p(fTempA) - fB*math.Log1p(fTempB) - fgm + fResult += fLogLanczos + return fResult +} + +// getBetaDist is an implementation for the beta distribution function. +func getBetaDist(fXin, fAlpha, fBeta float64) float64 { + if fXin <= 0 { + return 0 + } + if fXin >= 1 { + return 1 + } + if fBeta == 1 { + return math.Pow(fXin, fAlpha) + } + if fAlpha == 1 { + return -math.Expm1(fBeta * math.Log1p(-fXin)) + } + var fResult float64 + fY, flnY := (0.5-fXin)+0.5, math.Log1p(-fXin) + fX, flnX := fXin, math.Log(fXin) + fA, fB := fAlpha, fBeta + bReflect := fXin > fAlpha/(fAlpha+fBeta) + if bReflect { + fA = fBeta + fB = fAlpha + fX = fY + fY = fXin + flnX = flnY + flnY = math.Log(fXin) + } + fResult = getBetaHelperContFrac(fX, fA, fB) / fA + fP, fQ := fA/(fA+fB), fB/(fA+fB) + var fTemp float64 + if fA > 1 && fB > 1 && fP < 0.97 && fQ < 0.97 { + fTemp = getBetaDistPDF(fX, fA, fB) * fX * fY + } else { + fTemp = math.Exp(fA*flnX + fB*flnY - getLogBeta(fA, fB)) + } + fResult *= fTemp + if bReflect { + fResult = 0.5 - fResult + 0.5 + } + return fResult +} + +// prepareBETAdotDISTArgs checking and prepare arguments for the formula +// function BETA.DIST. +func (fn *formulaFuncs) prepareBETAdotDISTArgs(argsList *list.List) formulaArg { + if argsList.Len() < 4 { + return newErrorFormulaArg(formulaErrorVALUE, "BETA.DIST requires at least 4 arguments") + } + if argsList.Len() > 6 { + return newErrorFormulaArg(formulaErrorVALUE, "BETA.DIST requires at most 6 arguments") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + alpha := argsList.Front().Next().Value.(formulaArg).ToNumber() + if alpha.Type != ArgNumber { + return alpha + } + beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if beta.Type != ArgNumber { + return beta + } + if alpha.Number <= 0 || beta.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + cumulative := argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool() + if cumulative.Type != ArgNumber { + return cumulative + } + a, b := newNumberFormulaArg(0), newNumberFormulaArg(1) + if argsList.Len() > 4 { + if a = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber { + return a + } + } + if argsList.Len() == 6 { + if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber { + return b + } + } + return newListFormulaArg([]formulaArg{x, alpha, beta, cumulative, a, b}) +} + +// BETAdotDIST function calculates the cumulative beta distribution function +// or the probability density function of the Beta distribution, for a +// supplied set of parameters. The syntax of the function is: +// +// BETA.DIST(x,alpha,beta,cumulative,[A],[B]) +// +func (fn *formulaFuncs) BETAdotDIST(argsList *list.List) formulaArg { + args := fn.prepareBETAdotDISTArgs(argsList) + if args.Type != ArgList { + return args + } + x, alpha, beta, cumulative, a, b := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5] + if x.Number < a.Number || x.Number > b.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if a.Number == b.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + fScale := b.Number - a.Number + x.Number = (x.Number - a.Number) / fScale + if cumulative.Number == 1 { + return newNumberFormulaArg(getBetaDist(x.Number, alpha.Number, beta.Number)) + } + return newNumberFormulaArg(getBetaDistPDF(x.Number, alpha.Number, beta.Number) / fScale) +} + +// BETADIST function calculates the cumulative beta probability density +// function for a supplied set of parameters. The syntax of the function is: +// +// BETADIST(x,alpha,beta,[A],[B]) +// +func (fn *formulaFuncs) BETADIST(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "BETADIST requires at least 3 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "BETADIST requires at most 5 arguments") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + alpha := argsList.Front().Next().Value.(formulaArg).ToNumber() + if alpha.Type != ArgNumber { + return alpha + } + beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if beta.Type != ArgNumber { + return beta + } + if alpha.Number <= 0 || beta.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + a, b := newNumberFormulaArg(0), newNumberFormulaArg(1) + if argsList.Len() > 3 { + if a = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber { + return a + } + } + if argsList.Len() == 5 { + if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber { + return b + } + } + if x.Number < a.Number || x.Number > b.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if a.Number == b.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(getBetaDist((x.Number-a.Number)/(b.Number-a.Number), alpha.Number, beta.Number)) +} + +// d1mach returns double precision real machine constants. +func d1mach(i int) float64 { + arr := []float64{ + 2.2250738585072014e-308, + 1.7976931348623158e+308, + 1.1102230246251565e-16, + 2.2204460492503131e-16, + 0.301029995663981195, + } + if i > len(arr) { + return 0 + } + return arr[i-1] +} + +// chebyshevInit determines the number of terms for the double precision +// orthogonal series "dos" needed to insure the error is no larger +// than "eta". Ordinarily eta will be chosen to be one-tenth machine +// precision. +func chebyshevInit(nos int, eta float64, dos []float64) int { + i, e := 0, 0.0 + if nos < 1 { + return 0 + } + for ii := 1; ii <= nos; ii++ { + i = nos - ii + e += math.Abs(dos[i]) + if e > eta { + return i + } + } + return i +} + +// chebyshevEval evaluates the n-term Chebyshev series "a" at "x". +func chebyshevEval(n int, x float64, a []float64) float64 { + if n < 1 || n > 1000 || x < -1.1 || x > 1.1 { + return math.NaN() + } + twox, b0, b1, b2 := x*2, 0.0, 0.0, 0.0 + for i := 1; i <= n; i++ { + b2 = b1 + b1 = b0 + b0 = twox*b1 - b2 + a[n-i] + } + return (b0 - b2) * 0.5 +} + +// lgammacor is an implementation for the log(gamma) correction. +func lgammacor(x float64) float64 { + algmcs := []float64{ + 0.1666389480451863247205729650822, -0.1384948176067563840732986059135e-4, + 0.9810825646924729426157171547487e-8, -0.1809129475572494194263306266719e-10, + 0.6221098041892605227126015543416e-13, -0.3399615005417721944303330599666e-15, + 0.2683181998482698748957538846666e-17, -0.2868042435334643284144622399999e-19, + 0.3962837061046434803679306666666e-21, -0.6831888753985766870111999999999e-23, + 0.1429227355942498147573333333333e-24, -0.3547598158101070547199999999999e-26, + 0.1025680058010470912000000000000e-27, -0.3401102254316748799999999999999e-29, + 0.1276642195630062933333333333333e-30, + } + nalgm := chebyshevInit(15, d1mach(3), algmcs) + xbig := 1.0 / math.Sqrt(d1mach(3)) + xmax := math.Exp(math.Min(math.Log(d1mach(2)/12.0), -math.Log(12.0*d1mach(1)))) + if x < 10.0 { + return math.NaN() + } else if x >= xmax { + return 4.930380657631324e-32 + } else if x < xbig { + tmp := 10.0 / x + return chebyshevEval(nalgm, tmp*tmp*2.0-1.0, algmcs) / x + } + return 1.0 / (x * 12.0) +} + +// logrelerr compute the relative error logarithm. +func logrelerr(x float64) float64 { + alnrcs := []float64{ + 0.10378693562743769800686267719098e+1, -0.13364301504908918098766041553133, + 0.19408249135520563357926199374750e-1, -0.30107551127535777690376537776592e-2, + 0.48694614797154850090456366509137e-3, -0.81054881893175356066809943008622e-4, + 0.13778847799559524782938251496059e-4, -0.23802210894358970251369992914935e-5, + 0.41640416213865183476391859901989e-6, -0.73595828378075994984266837031998e-7, + 0.13117611876241674949152294345011e-7, -0.23546709317742425136696092330175e-8, + 0.42522773276034997775638052962567e-9, -0.77190894134840796826108107493300e-10, + 0.14075746481359069909215356472191e-10, -0.25769072058024680627537078627584e-11, + 0.47342406666294421849154395005938e-12, -0.87249012674742641745301263292675e-13, + 0.16124614902740551465739833119115e-13, -0.29875652015665773006710792416815e-14, + 0.55480701209082887983041321697279e-15, -0.10324619158271569595141333961932e-15, + 0.19250239203049851177878503244868e-16, -0.35955073465265150011189707844266e-17, + 0.67264542537876857892194574226773e-18, -0.12602624168735219252082425637546e-18, + 0.23644884408606210044916158955519e-19, -0.44419377050807936898878389179733e-20, + 0.83546594464034259016241293994666e-21, -0.15731559416479562574899253521066e-21, + 0.29653128740247422686154369706666e-22, -0.55949583481815947292156013226666e-23, + 0.10566354268835681048187284138666e-23, -0.19972483680670204548314999466666e-24, + 0.37782977818839361421049855999999e-25, -0.71531586889081740345038165333333e-26, + 0.13552488463674213646502024533333e-26, -0.25694673048487567430079829333333e-27, + 0.48747756066216949076459519999999e-28, -0.92542112530849715321132373333333e-29, + 0.17578597841760239233269760000000e-29, -0.33410026677731010351377066666666e-30, + 0.63533936180236187354180266666666e-31, + } + nlnrel := chebyshevInit(43, 0.1*d1mach(3), alnrcs) + if x <= -1 { + return math.NaN() + } + if math.Abs(x) <= 0.375 { + return x * (1.0 - x*chebyshevEval(nlnrel, x/0.375, alnrcs)) + } + return math.Log(x + 1.0) +} + +// logBeta is an implementation for the log of the beta distribution +// function. +func logBeta(a, b float64) float64 { + corr, p, q := 0.0, a, a + if b < p { + p = b + } + if b > q { + q = b + } + if p < 0 { + return math.NaN() + } + if p == 0 { + return math.MaxFloat64 + } + if p >= 10.0 { + corr = lgammacor(p) + lgammacor(q) - lgammacor(p+q) + f1 := q * logrelerr(-p/(p+q)) + return math.Log(q)*-0.5 + 0.918938533204672741780329736406 + corr + (p-0.5)*math.Log(p/(p+q)) + math.Nextafter(f1, f1) + } + if q >= 10 { + corr = lgammacor(q) - lgammacor(p+q) + val, _ := math.Lgamma(p) + return val + corr + p - p*math.Log(p+q) + (q-0.5)*logrelerr(-p/(p+q)) + } + return math.Log(math.Gamma(p) * (math.Gamma(q) / math.Gamma(p+q))) +} + +// pbetaRaw is a part of pbeta for the beta distribution. +func pbetaRaw(alnsml, ans, eps, p, pin, q, sml, x, y float64) float64 { + if q > 1.0 { + xb := p*math.Log(y) + q*math.Log(1.0-y) - logBeta(p, q) - math.Log(q) + ib := int(math.Max(xb/alnsml, 0.0)) + term := math.Exp(xb - float64(ib)*alnsml) + c := 1.0 / (1.0 - y) + p1 := q * c / (p + q - 1.0) + finsum := 0.0 + n := int(q) + if q == float64(n) { + n = n - 1 + } + for i := 1; i <= n; i++ { + if p1 <= 1 && term/eps <= finsum { + break + } + xi := float64(i) + term = (q - xi + 1.0) * c * term / (p + q - xi) + if term > 1.0 { + ib = ib - 1 + term = term * sml + } + if ib == 0 { + finsum = finsum + term + } + } + ans = ans + finsum + } + if y != x || p != pin { + ans = 1.0 - ans + } + ans = math.Max(math.Min(ans, 1.0), 0.0) + return ans +} + +// pbeta returns distribution function of the beta distribution. +func pbeta(x, pin, qin float64) (ans float64) { + eps := d1mach(3) + alneps := math.Log(eps) + sml := d1mach(1) + alnsml := math.Log(sml) + y := x + p := pin + q := qin + if p/(p+q) < x { + y = 1.0 - y + p = qin + q = pin + } + if (p+q)*y/(p+1.0) < eps { + xb := p*math.Log(math.Max(y, sml)) - math.Log(p) - logBeta(p, q) + if xb > alnsml && y != 0.0 { + ans = math.Exp(xb) + } + if y != x || p != pin { + ans = 1.0 - ans + } + } else { + ps := q - math.Floor(q) + if ps == 0.0 { + ps = 1.0 + } + xb := p*math.Log(y) - logBeta(ps, p) - math.Log(p) + if xb >= alnsml { + ans = math.Exp(xb) + term := ans * p + if ps != 1.0 { + n := int(math.Max(alneps/math.Log(y), 4.0)) + for i := 1; i <= n; i++ { + xi := float64(i) + term = term * (xi - ps) * y / xi + ans = ans + term/(p+xi) + } + } + } + ans = pbetaRaw(alnsml, ans, eps, p, pin, q, sml, x, y) + } + return ans +} + +// betainvProbIterator is a part of betainv for the inverse of the beta +// function. +func betainvProbIterator(alpha1, alpha3, beta1, beta2, beta3, logBeta, maxCumulative, prob1, prob2 float64) float64 { + var i, j, prev, prop4 float64 + j = 1 + for prob := 0; prob < 1000; prob++ { + prop3 := pbeta(beta3, alpha1, beta1) + prop3 = (prop3 - prob1) * math.Exp(logBeta+prob2*math.Log(beta3)+beta2*math.Log(1.0-beta3)) + if prop3*prop4 <= 0 { + prev = math.Max(math.Abs(j), maxCumulative) + } + h := 1.0 + for iteratorCount := 0; iteratorCount < 1000; iteratorCount++ { + j = h * prop3 + if math.Abs(j) < prev { + i = beta3 - j + if i >= 0 && i <= 1.0 { + if prev <= alpha3 { + return beta3 + } + if math.Abs(prop3) <= alpha3 { + return beta3 + } + if i != 0 && i != 1.0 { + break + } + } + } + h /= 3.0 + } + if i == beta3 { + return beta3 + } + beta3, prop4 = i, prop3 + } + return beta3 +} + +// calcBetainv is an implementation for the quantile of the beta +// distribution. +func calcBetainv(probability, alpha, beta, lower, upper float64) float64 { + minCumulative, maxCumulative := 1.0e-300, 3.0e-308 + lowerBound, upperBound := maxCumulative, 1.0-2.22e-16 + needSwap := false + var alpha1, alpha2, beta1, beta2, beta3, prob1, x, y float64 + if probability <= 0.5 { + prob1, alpha1, beta1 = probability, alpha, beta + } else { + prob1, alpha1, beta1, needSwap = 1.0-probability, beta, alpha, true + } + logBetaNum := logBeta(alpha, beta) + prob2 := math.Sqrt(-math.Log(prob1 * prob1)) + prob3 := prob2 - (prob2*0.27061+2.3075)/(prob2*(prob2*0.04481+0.99229)+1) + if alpha1 > 1 && beta1 > 1 { + alpha2, beta2, prob2 = 1/(alpha1+alpha1-1), 1/(beta1+beta1-1), (prob3*prob3-3)/6 + x = 2 / (alpha2 + beta2) + y = prob3*math.Sqrt(x+prob2)/x - (beta2-alpha2)*(prob2+5/6.0-2/(x*3)) + beta3 = alpha1 / (alpha1 + beta1*math.Exp(y+y)) + } else { + beta2, prob2 = 1/(beta1*9), beta1+beta1 + beta2 = prob2 * math.Pow(1-beta2+prob3*math.Sqrt(beta2), 3) + if beta2 <= 0 { + beta3 = 1 - math.Exp((math.Log((1-prob1)*beta1)+logBetaNum)/beta1) + } else { + beta2 = (prob2 + alpha1*4 - 2) / beta2 + if beta2 <= 1 { + beta3 = math.Exp((logBetaNum + math.Log(alpha1*prob1)) / alpha1) + } else { + beta3 = 1 - 2/(beta2+1) + } + } + } + beta2, prob2 = 1-beta1, 1-alpha1 + if beta3 < lowerBound { + beta3 = lowerBound + } else if beta3 > upperBound { + beta3 = upperBound + } + alpha3 := math.Max(minCumulative, math.Pow(10.0, -13.0-2.5/(alpha1*alpha1)-0.5/(prob1*prob1))) + beta3 = betainvProbIterator(alpha1, alpha3, beta1, beta2, beta3, logBetaNum, maxCumulative, prob1, prob2) + if needSwap { + beta3 = 1.0 - beta3 + } + return (upper-lower)*beta3 + lower +} + +// betainv is an implementation of the formula functions BETAINV and +// BETA.INV. +func (fn *formulaFuncs) betainv(name string, argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 3 arguments", name)) + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 5 arguments", name)) + } + probability := argsList.Front().Value.(formulaArg).ToNumber() + if probability.Type != ArgNumber { + return probability + } + if probability.Number <= 0 || probability.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + alpha := argsList.Front().Next().Value.(formulaArg).ToNumber() + if alpha.Type != ArgNumber { + return alpha + } + beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if beta.Type != ArgNumber { + return beta + } + if alpha.Number <= 0 || beta.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + a, b := newNumberFormulaArg(0), newNumberFormulaArg(1) + if argsList.Len() > 3 { + if a = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber { + return a + } + } + if argsList.Len() == 5 { + if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber { + return b + } + } + if a.Number == b.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(calcBetainv(probability.Number, alpha.Number, beta.Number, a.Number, b.Number)) +} + +// BETAINV function uses an iterative procedure to calculate the inverse of +// the cumulative beta probability density function for a supplied +// probability. The syntax of the function is: +// +// BETAINV(probability,alpha,beta,[A],[B]) +// +func (fn *formulaFuncs) BETAINV(argsList *list.List) formulaArg { + return fn.betainv("BETAINV", argsList) +} + +// BETAdotINV function uses an iterative procedure to calculate the inverse of +// the cumulative beta probability density function for a supplied +// probability. The syntax of the function is: +// +// BETA.INV(probability,alpha,beta,[A],[B]) +// +func (fn *formulaFuncs) BETAdotINV(argsList *list.List) formulaArg { + return fn.betainv("BETA.INV", argsList) +} + +// incompleteGamma is an implementation of the incomplete gamma function. +func incompleteGamma(a, x float64) float64 { + max := 32 + summer := 0.0 + for n := 0; n <= max; n++ { + divisor := a + for i := 1; i <= n; i++ { + divisor *= a + float64(i) + } + summer += math.Pow(x, float64(n)) / divisor + } + return math.Pow(x, a) * math.Exp(0-x) * summer +} + +// binomCoeff implement binomial coefficient calculation. +func binomCoeff(n, k float64) float64 { + return fact(n) / (fact(k) * fact(n-k)) +} + +// binomdist implement binomial distribution calculation. +func binomdist(x, n, p float64) float64 { + return binomCoeff(n, x) * math.Pow(p, x) * math.Pow(1-p, n-x) +} + +// BINOMdotDIST function returns the Binomial Distribution probability for a +// given number of successes from a specified number of trials. The syntax of +// the function is: +// +// BINOM.DIST(number_s,trials,probability_s,cumulative) +// +func (fn *formulaFuncs) BINOMdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST requires 4 arguments") + } + return fn.BINOMDIST(argsList) +} + +// BINOMDIST function returns the Binomial Distribution probability of a +// specified number of successes out of a specified number of trials. The +// syntax of the function is: +// +// BINOMDIST(number_s,trials,probability_s,cumulative) +// +func (fn *formulaFuncs) BINOMDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "BINOMDIST requires 4 arguments") + } + var s, trials, probability, cumulative formulaArg + if s = argsList.Front().Value.(formulaArg).ToNumber(); s.Type != ArgNumber { + return s + } + if trials = argsList.Front().Next().Value.(formulaArg).ToNumber(); trials.Type != ArgNumber { + return trials + } + if s.Number < 0 || s.Number > trials.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if probability = argsList.Back().Prev().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + + if probability.Number < 0 || probability.Number > 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { + return cumulative + } + if cumulative.Number == 1 { + bm := 0.0 + for i := 0; i <= int(s.Number); i++ { + bm += binomdist(float64(i), trials.Number, probability.Number) + } + return newNumberFormulaArg(bm) + } + return newNumberFormulaArg(binomdist(s.Number, trials.Number, probability.Number)) +} + +// BINOMdotDISTdotRANGE function returns the Binomial Distribution probability +// for the number of successes from a specified number of trials falling into +// a specified range. +// +// BINOM.DIST.RANGE(trials,probability_s,number_s,[number_s2]) +// +func (fn *formulaFuncs) BINOMdotDISTdotRANGE(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST.RANGE requires at least 3 arguments") + } + if argsList.Len() > 4 { + return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST.RANGE requires at most 4 arguments") + } + trials := argsList.Front().Value.(formulaArg).ToNumber() + if trials.Type != ArgNumber { + return trials + } + probability := argsList.Front().Next().Value.(formulaArg).ToNumber() + if probability.Type != ArgNumber { + return probability + } + if probability.Number < 0 || probability.Number > 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + num1 := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if num1.Type != ArgNumber { + return num1 + } + if num1.Number < 0 || num1.Number > trials.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + num2 := num1 + if argsList.Len() > 3 { + if num2 = argsList.Back().Value.(formulaArg).ToNumber(); num2.Type != ArgNumber { + return num2 + } + } + if num2.Number < 0 || num2.Number > trials.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + sum := 0.0 + for i := num1.Number; i <= num2.Number; i++ { + sum += binomdist(i, trials.Number, probability.Number) + } + return newNumberFormulaArg(sum) +} + +// binominv implement inverse of the binomial distribution calculation. +func binominv(n, p, alpha float64) float64 { + q, i, sum, max := 1-p, 0.0, 0.0, 0.0 + n = math.Floor(n) + if q > p { + factor := math.Pow(q, n) + sum = factor + for i = 0; i < n && sum < alpha; i++ { + factor *= (n - i) / (i + 1) * p / q + sum += factor + } + return i + } + factor := math.Pow(p, n) + sum, max = 1-factor, n + for i = 0; i < max && sum >= alpha; i++ { + factor *= (n - i) / (i + 1) * q / p + sum -= factor + } + return n - i +} + +// BINOMdotINV function returns the inverse of the Cumulative Binomial +// Distribution. The syntax of the function is: +// +// BINOM.INV(trials,probability_s,alpha) +// +func (fn *formulaFuncs) BINOMdotINV(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "BINOM.INV requires 3 numeric arguments") + } + trials := argsList.Front().Value.(formulaArg).ToNumber() + if trials.Type != ArgNumber { + return trials + } + if trials.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + probability := argsList.Front().Next().Value.(formulaArg).ToNumber() + if probability.Type != ArgNumber { + return probability + } + if probability.Number <= 0 || probability.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + alpha := argsList.Back().Value.(formulaArg).ToNumber() + if alpha.Type != ArgNumber { + return alpha + } + if alpha.Number <= 0 || alpha.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(binominv(trials.Number, probability.Number, alpha.Number)) +} + +// CHIDIST function calculates the right-tailed probability of the chi-square +// distribution. The syntax of the function is: +// +// CHIDIST(x,degrees_freedom) +// +func (fn *formulaFuncs) CHIDIST(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CHIDIST requires 2 numeric arguments") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + degrees := argsList.Back().Value.(formulaArg).ToNumber() + if degrees.Type != ArgNumber { + return degrees + } + logSqrtPi, sqrtPi := math.Log(math.Sqrt(math.Pi)), 1/math.Sqrt(math.Pi) + var e, s, z, c, y float64 + a, x1, even := x.Number/2, x.Number, int(degrees.Number)%2 == 0 + if degrees.Number > 1 { + y = math.Exp(-a) + } + args := list.New() + args.PushBack(newNumberFormulaArg(-math.Sqrt(x1))) + o := fn.NORMSDIST(args) + s = 2 * o.Number + if even { + s = y + } + if degrees.Number > 2 { + x1 = (degrees.Number - 1) / 2 + z = 0.5 + if even { + z = 1 + } + if a > 20 { + e = logSqrtPi + if even { + e = 0 + } + c = math.Log(a) + for z <= x1 { + e = math.Log(z) + e + s += math.Exp(c*z - a - e) + z += 1 + } + return newNumberFormulaArg(s) + } + e = sqrtPi / math.Sqrt(a) + if even { + e = 1 + } + c = 0 + for z <= x1 { + e = e * (a / z) + c = c + e + z += 1 + } + return newNumberFormulaArg(c*y + s) + } + return newNumberFormulaArg(s) +} + +// CHIINV function calculates the inverse of the right-tailed probability of +// the Chi-Square Distribution. The syntax of the function is: +// +// CHIINV(probability,deg_freedom) +// +func (fn *formulaFuncs) CHIINV(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CHIINV requires 2 numeric arguments") + } + probability := argsList.Front().Value.(formulaArg).ToNumber() + if probability.Type != ArgNumber { + return probability + } + if probability.Number <= 0 || probability.Number > 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + deg := argsList.Back().Value.(formulaArg).ToNumber() + if deg.Type != ArgNumber { + return deg + } + if deg.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(gammainv(1-probability.Number, 0.5*deg.Number, 2.0)) +} + +// CHITEST function uses the chi-square test to calculate the probability that +// the differences between two supplied data sets (of observed and expected +// frequencies), are likely to be simply due to sampling error, or if they are +// likely to be real. The syntax of the function is: +// +// CHITEST(actual_range,expected_range) +// +func (fn *formulaFuncs) CHITEST(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CHITEST requires 2 arguments") + } + actual, expected := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg) + actualList, expectedList := actual.ToList(), expected.ToList() + rows := len(actual.Matrix) + columns := len(actualList) / rows + if len(actualList) != len(expectedList) || len(actualList) == 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + var result float64 + var degrees int + for i := 0; i < len(actualList); i++ { + a, e := actualList[i].ToNumber(), expectedList[i].ToNumber() + if a.Type == ArgNumber && e.Type == ArgNumber { + if e.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + if e.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + result += (a.Number - e.Number) * (a.Number - e.Number) / e.Number + } + } + if rows == 1 { + degrees = columns - 1 + } else if columns == 1 { + degrees = rows - 1 + } else { + degrees = (columns - 1) * (rows - 1) + } + args := list.New() + args.PushBack(newNumberFormulaArg(result)) + args.PushBack(newNumberFormulaArg(float64(degrees))) + return fn.CHIDIST(args) +} + +// getGammaSeries calculates a power-series of the gamma function. +func getGammaSeries(fA, fX float64) float64 { + var ( + fHalfMachEps = 2.22045e-016 / 2 + fDenomfactor = fA + fSummand = 1 / fA + fSum = fSummand + nCount = 1 + ) + for fSummand/fSum > fHalfMachEps && nCount <= 10000 { + fDenomfactor = fDenomfactor + 1 + fSummand = fSummand * fX / fDenomfactor + fSum = fSum + fSummand + nCount = nCount + 1 + } + return fSum +} + +// getGammaContFraction returns continued fraction with odd items of the gamma +// function. +func getGammaContFraction(fA, fX float64) float64 { + var ( + fBigInv = 2.22045e-016 + fHalfMachEps = fBigInv / 2 + fBig = 1 / fBigInv + fCount = 0.0 + fY = 1 - fA + fDenom = fX + 2 - fA + fPkm1 = fX + 1 + fPkm2 = 1.0 + fQkm1 = fDenom * fX + fQkm2 = fX + fApprox = fPkm1 / fQkm1 + bFinished = false + ) + for !bFinished && fCount < 10000 { + fCount = fCount + 1 + fY = fY + 1 + fDenom = fDenom + 2 + var ( + fNum = fY * fCount + f1 = fPkm1 * fDenom + f2 = fPkm2 * fNum + fPk = math.Nextafter(f1, f1) - math.Nextafter(f2, f2) + f3 = fQkm1 * fDenom + f4 = fQkm2 * fNum + fQk = math.Nextafter(f3, f3) - math.Nextafter(f4, f4) + ) + if fQk != 0 { + fR := fPk / fQk + bFinished = math.Abs((fApprox-fR)/fR) <= fHalfMachEps + fApprox = fR + } + fPkm2, fPkm1, fQkm2, fQkm1 = fPkm1, fPk, fQkm1, fQk + if math.Abs(fPk) > fBig { + // reduce a fraction does not change the value + fPkm2 = fPkm2 * fBigInv + fPkm1 = fPkm1 * fBigInv + fQkm2 = fQkm2 * fBigInv + fQkm1 = fQkm1 * fBigInv + } + } + return fApprox +} + +// getLogGammaHelper is a part of implementation of the function getLogGamma. +func getLogGammaHelper(fZ float64) float64 { + _fg := 6.024680040776729583740234375 + zgHelp := fZ + _fg - 0.5 + return math.Log(getLanczosSum(fZ)) + (fZ-0.5)*math.Log(zgHelp) - zgHelp +} + +// getGammaHelper is a part of implementation of the function getLogGamma. +func getGammaHelper(fZ float64) float64 { + var ( + gamma = getLanczosSum(fZ) + fg = 6.024680040776729583740234375 + zgHelp = fZ + fg - 0.5 + // avoid intermediate overflow + halfpower = math.Pow(zgHelp, fZ/2-0.25) + ) + gamma *= halfpower + gamma /= math.Exp(zgHelp) + gamma *= halfpower + if fZ <= 20 && fZ == math.Floor(fZ) { + gamma = math.Round(gamma) + } + return gamma +} + +// getLogGamma calculates the natural logarithm of the gamma function. +func getLogGamma(fZ float64) float64 { + fMaxGammaArgument := 171.624376956302 + if fZ >= fMaxGammaArgument { + return getLogGammaHelper(fZ) + } + if fZ >= 1.0 { + return math.Log(getGammaHelper(fZ)) + } + if fZ >= 0.5 { + return math.Log(getGammaHelper(fZ+1) / fZ) + } + return getLogGammaHelper(fZ+2) - math.Log(fZ+1) - math.Log(fZ) +} + +// getLowRegIGamma returns lower regularized incomplete gamma function. +func getLowRegIGamma(fA, fX float64) float64 { + fLnFactor := fA*math.Log(fX) - fX - getLogGamma(fA) + fFactor := math.Exp(fLnFactor) + if fX > fA+1 { + return 1 - fFactor*getGammaContFraction(fA, fX) + } + return fFactor * getGammaSeries(fA, fX) +} + +// getChiSqDistCDF returns left tail for the Chi-Square distribution. +func getChiSqDistCDF(fX, fDF float64) float64 { + if fX <= 0 { + return 0 + } + return getLowRegIGamma(fDF/2, fX/2) +} + +// getChiSqDistPDF calculates the probability density function for the +// Chi-Square distribution. +func getChiSqDistPDF(fX, fDF float64) float64 { + if fDF*fX > 1391000 { + return math.Exp((0.5*fDF-1)*math.Log(fX*0.5) - 0.5*fX - math.Log(2) - getLogGamma(0.5*fDF)) + } + var fCount, fValue float64 + if math.Mod(fDF, 2) < 0.5 { + fValue = 0.5 + fCount = 2 + } else { + fValue = 1 / math.Sqrt(fX*2*math.Pi) + fCount = 1 + } + for fCount < fDF { + fValue *= fX / fCount + fCount += 2 + } + if fX >= 1425 { + fValue = math.Exp(math.Log(fValue) - fX/2) + } else { + fValue *= math.Exp(-fX / 2) + } + return fValue +} + +// CHISQdotDIST function calculates the Probability Density Function or the +// Cumulative Distribution Function for the Chi-Square Distribution. The +// syntax of the function is: +// +// CHISQ.DIST(x,degrees_freedom,cumulative) +// +func (fn *formulaFuncs) CHISQdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.DIST requires 3 arguments") + } + var x, degrees, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { + return cumulative + } + if x.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + maxDeg := math.Pow10(10) + if degrees.Number < 1 || degrees.Number >= maxDeg { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative.Number == 1 { + return newNumberFormulaArg(getChiSqDistCDF(x.Number, degrees.Number)) + } + return newNumberFormulaArg(getChiSqDistPDF(x.Number, degrees.Number)) +} + +// CHISQdotDISTdotRT function calculates the right-tailed probability of the +// Chi-Square Distribution. The syntax of the function is: +// +// CHISQ.DIST.RT(x,degrees_freedom) +// +func (fn *formulaFuncs) CHISQdotDISTdotRT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.DIST.RT requires 2 numeric arguments") + } + return fn.CHIDIST(argsList) +} + +// CHISQdotTEST function performs the chi-square test on two supplied data sets +// (of observed and expected frequencies), and returns the probability that +// the differences between the sets are simply due to sampling error. The +// syntax of the function is: +// +// CHISQ.TEST(actual_range,expected_range) +// +func (fn *formulaFuncs) CHISQdotTEST(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.TEST requires 2 arguments") + } + return fn.CHITEST(argsList) +} + +// hasChangeOfSign check if the sign has been changed. +func hasChangeOfSign(u, w float64) bool { + return (u < 0 && w > 0) || (u > 0 && w < 0) +} + +// calcInverseIterator directly maps the required parameters for inverse +// distribution functions. +type calcInverseIterator struct { + name string + fp, fDF, nT float64 +} + +// callBack implements the callback function for the inverse iterator. +func (iterator *calcInverseIterator) callBack(x float64) float64 { + if iterator.name == "CHISQ.INV" { + return iterator.fp - getChiSqDistCDF(x, iterator.fDF) + } + return iterator.fp - getTDist(x, iterator.fDF, iterator.nT) +} + +// inverseQuadraticInterpolation inverse quadratic interpolation with +// additional brackets. +func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx, fBy float64) float64 { + fYEps := 1.0e-307 + fXEps := 2.22045e-016 + fPx, fPy, fQx, fQy, fRx, fRy := fAx, fAy, fBx, fBy, fAx, fAy + fSx := 0.5 * (fAx + fBx) + bHasToInterpolate := true + nCount := 0 + for nCount < 500 && math.Abs(fRy) > fYEps && (fBx-fAx) > math.Max(math.Abs(fAx), math.Abs(fBx))*fXEps { + if bHasToInterpolate { + if fPy != fQy && fQy != fRy && fRy != fPy { + fSx = fPx*fRy*fQy/(fRy-fPy)/(fQy-fPy) + fRx*fQy*fPy/(fQy-fRy)/(fPy-fRy) + + fQx*fPy*fRy/(fPy-fQy)/(fRy-fQy) + bHasToInterpolate = (fAx < fSx) && (fSx < fBx) + } else { + bHasToInterpolate = false + } + } + if !bHasToInterpolate { + fSx = 0.5 * (fAx + fBx) + fQx, fQy = fBx, fBy + bHasToInterpolate = true + } + fPx, fQx, fRx, fPy, fQy = fQx, fRx, fSx, fQy, fRy + fRy = iterator.callBack(fSx) + if hasChangeOfSign(fAy, fRy) { + fBx, fBy = fRx, fRy + } else { + fAx, fAy = fRx, fRy + } + bHasToInterpolate = bHasToInterpolate && (math.Abs(fRy)*2 <= math.Abs(fQy)) + nCount++ + } + return fRx +} + +// calcIterateInverse function calculates the iteration for inverse +// distributions. +func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64 { + fAy, fBy := iterator.callBack(fAx), iterator.callBack(fBx) + var fTemp float64 + var nCount int + for nCount = 0; nCount < 1000 && !hasChangeOfSign(fAy, fBy); nCount++ { + if math.Abs(fAy) <= math.Abs(fBy) { + fTemp = fAx + fAx += 2 * (fAx - fBx) + if fAx < 0 { + fAx = 0 + } + fBx = fTemp + fBy = fAy + fAy = iterator.callBack(fAx) + } else { + fTemp = fBx + fBx += 2 * (fBx - fAx) + fAx = fTemp + fAy = fBy + fBy = iterator.callBack(fBx) + } + } + if fAy == 0 || fBy == 0 { + return 0 + } + return inverseQuadraticInterpolation(iterator, fAx, fAy, fBx, fBy) +} + +// CHISQdotINV function calculates the inverse of the left-tailed probability +// of the Chi-Square Distribution. The syntax of the function is: +// +// CHISQ.INV(probability,degrees_freedom) +// +func (fn *formulaFuncs) CHISQdotINV(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.INV requires 2 numeric arguments") + } + var probability, degrees formulaArg + if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if probability.Number < 0 || probability.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if degrees.Number < 1 || degrees.Number > math.Pow10(10) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{ + name: "CHISQ.INV", + fp: probability.Number, + fDF: degrees.Number, + }, degrees.Number/2, degrees.Number)) +} + +// CHISQdotINVdotRT function calculates the inverse of the right-tailed +// probability of the Chi-Square Distribution. The syntax of the function is: +// +// CHISQ.INV.RT(probability,degrees_freedom) +// +func (fn *formulaFuncs) CHISQdotINVdotRT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CHISQ.INV.RT requires 2 numeric arguments") + } + return fn.CHIINV(argsList) +} + +// confidence is an implementation of the formula functions CONFIDENCE and +// CONFIDENCE.NORM. +func (fn *formulaFuncs) confidence(name string, argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 numeric arguments", name)) + } + alpha := argsList.Front().Value.(formulaArg).ToNumber() + if alpha.Type != ArgNumber { + return alpha + } + if alpha.Number <= 0 || alpha.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + stdDev := argsList.Front().Next().Value.(formulaArg).ToNumber() + if stdDev.Type != ArgNumber { + return stdDev + } + if stdDev.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + size := argsList.Back().Value.(formulaArg).ToNumber() + if size.Type != ArgNumber { + return size + } + if size.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + args := list.New() + args.Init() + args.PushBack(newNumberFormulaArg(alpha.Number / 2)) + args.PushBack(newNumberFormulaArg(0)) + args.PushBack(newNumberFormulaArg(1)) + return newNumberFormulaArg(-fn.NORMINV(args).Number * (stdDev.Number / math.Sqrt(size.Number))) +} + +// CONFIDENCE function uses a Normal Distribution to calculate a confidence +// value that can be used to construct the Confidence Interval for a +// population mean, for a supplied probability and sample size. It is assumed +// that the standard deviation of the population is known. The syntax of the +// function is: +// +// CONFIDENCE(alpha,standard_dev,size) +// +func (fn *formulaFuncs) CONFIDENCE(argsList *list.List) formulaArg { + return fn.confidence("CONFIDENCE", argsList) +} + +// CONFIDENCEdotNORM function uses a Normal Distribution to calculate a +// confidence value that can be used to construct the confidence interval for +// a population mean, for a supplied probability and sample size. It is +// assumed that the standard deviation of the population is known. The syntax +// of the function is: +// +// CONFIDENCE.NORM(alpha,standard_dev,size) +// +func (fn *formulaFuncs) CONFIDENCEdotNORM(argsList *list.List) formulaArg { + return fn.confidence("CONFIDENCE.NORM", argsList) +} + +// CONFIDENCEdotT function uses a Student's T-Distribution to calculate a +// confidence value that can be used to construct the confidence interval for +// a population mean, for a supplied probablity and supplied sample size. It +// is assumed that the standard deviation of the population is known. The +// syntax of the function is: +// +// CONFIDENCE.T(alpha,standard_dev,size) +// +func (fn *formulaFuncs) CONFIDENCEdotT(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "CONFIDENCE.T requires 3 arguments") + } + var alpha, standardDev, size formulaArg + if alpha = argsList.Front().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber { + return alpha + } + if standardDev = argsList.Front().Next().Value.(formulaArg).ToNumber(); standardDev.Type != ArgNumber { + return standardDev + } + if size = argsList.Back().Value.(formulaArg).ToNumber(); size.Type != ArgNumber { + return size + } + if alpha.Number <= 0 || alpha.Number >= 1 || standardDev.Number <= 0 || size.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if size.Number == 1 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(standardDev.Number * calcIterateInverse(calcInverseIterator{ + name: "CONFIDENCE.T", + fp: alpha.Number, + fDF: size.Number - 1, + nT: 2, + }, size.Number/2, size.Number) / math.Sqrt(size.Number)) +} + +// COVAR function calculates the covariance of two supplied sets of values. The +// syntax of the function is: +// +// COVAR(array1,array2) +// +func (fn *formulaFuncs) COVAR(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "COVAR requires 2 arguments") + } + array1 := argsList.Front().Value.(formulaArg) + array2 := argsList.Back().Value.(formulaArg) + left, right := array1.ToList(), array2.ToList() + n := len(left) + if n != len(right) { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + l1, l2 := list.New(), list.New() + l1.PushBack(array1) + l2.PushBack(array2) + result, skip := 0.0, 0 + mean1, mean2 := fn.AVERAGE(l1), fn.AVERAGE(l2) + for i := 0; i < n; i++ { + arg1 := left[i].ToNumber() + arg2 := right[i].ToNumber() + if arg1.Type == ArgError || arg2.Type == ArgError { + skip++ + continue + } + result += (arg1.Number - mean1.Number) * (arg2.Number - mean2.Number) + } + return newNumberFormulaArg(result / float64(n-skip)) +} + +// COVARIANCEdotP function calculates the population covariance of two supplied +// sets of values. The syntax of the function is: +// +// COVARIANCE.P(array1,array2) +// +func (fn *formulaFuncs) COVARIANCEdotP(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "COVARIANCE.P requires 2 arguments") + } + return fn.COVAR(argsList) +} + +// calcStringCountSum is part of the implementation countSum. +func calcStringCountSum(countText bool, count, sum float64, num, arg formulaArg) (float64, float64) { + if countText && num.Type == ArgError && arg.String != "" { + count++ + } + if num.Type == ArgNumber { + sum += num.Number + count++ + } + return count, sum +} + +// countSum get count and sum for a formula arguments array. +func (fn *formulaFuncs) countSum(countText bool, args []formulaArg) (count, sum float64) { + for _, arg := range args { + switch arg.Type { + case ArgNumber: + if countText || !arg.Boolean { + sum += arg.Number + count++ + } + case ArgString: + if !countText && (arg.Value() == "TRUE" || arg.Value() == "FALSE") { + continue + } else if countText && (arg.Value() == "TRUE" || arg.Value() == "FALSE") { + num := arg.ToBool() + if num.Type == ArgNumber { + count++ + sum += num.Number + continue + } + } + num := arg.ToNumber() + count, sum = calcStringCountSum(countText, count, sum, num, arg) + case ArgList, ArgMatrix: + cnt, summary := fn.countSum(countText, arg.ToList()) + sum += summary + count += cnt + } + } + return +} + +// CORREL function calculates the Pearson Product-Moment Correlation +// Coefficient for two sets of values. The syntax of the function is: +// +// CORREL(array1,array2) +// +func (fn *formulaFuncs) CORREL(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CORREL requires 2 arguments") + } + array1 := argsList.Front().Value.(formulaArg) + array2 := argsList.Back().Value.(formulaArg) + left, right := array1.ToList(), array2.ToList() + n := len(left) + if n != len(right) { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + l1, l2, l3 := list.New(), list.New(), list.New() + for i := 0; i < n; i++ { + if lhs, rhs := left[i].ToNumber(), right[i].ToNumber(); lhs.Number != 0 && rhs.Number != 0 { + l1.PushBack(lhs) + l2.PushBack(rhs) + } + } + stdev1, stdev2 := fn.STDEV(l1), fn.STDEV(l2) + if stdev1.Number == 0 || stdev2.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + mean1, mean2, skip := fn.AVERAGE(l1), fn.AVERAGE(l2), 0 + for i := 0; i < n; i++ { + lhs, rhs := left[i].ToNumber(), right[i].ToNumber() + if lhs.Number == 0 || rhs.Number == 0 { + skip++ + continue + } + l3.PushBack(newNumberFormulaArg((lhs.Number - mean1.Number) * (rhs.Number - mean2.Number))) + } + return newNumberFormulaArg(fn.SUM(l3).Number / float64(n-skip-1) / stdev1.Number / stdev2.Number) +} + +// COUNT function returns the count of numeric values in a supplied set of +// cells or values. This count includes both numbers and dates. The syntax of +// the function is: +// +// COUNT(value1,[value2],...) +// +func (fn *formulaFuncs) COUNT(argsList *list.List) formulaArg { + var count int + for token := argsList.Front(); token != nil; token = token.Next() { + arg := token.Value.(formulaArg) + switch arg.Type { + case ArgString: + if arg.ToNumber().Type != ArgError { + count++ + } + case ArgNumber: + count++ + case ArgMatrix: + for _, row := range arg.Matrix { + for _, value := range row { + if value.ToNumber().Type != ArgError { + count++ + } + } + } + } + } + return newNumberFormulaArg(float64(count)) +} + +// COUNTA function returns the number of non-blanks within a supplied set of +// cells or values. The syntax of the function is: +// +// COUNTA(value1,[value2],...) +// +func (fn *formulaFuncs) COUNTA(argsList *list.List) formulaArg { + var count int + for token := argsList.Front(); token != nil; token = token.Next() { + arg := token.Value.(formulaArg) + switch arg.Type { + case ArgString: + if arg.String != "" { + count++ + } + case ArgNumber: + count++ + case ArgMatrix: + for _, row := range arg.ToList() { + switch row.Type { + case ArgString: + if row.String != "" { + count++ + } + case ArgNumber: + count++ + } + } + } + } + return newNumberFormulaArg(float64(count)) +} + +// COUNTBLANK function returns the number of blank cells in a supplied range. +// The syntax of the function is: +// +// COUNTBLANK(range) +// +func (fn *formulaFuncs) COUNTBLANK(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "COUNTBLANK requires 1 argument") + } + var count float64 + for _, cell := range argsList.Front().Value.(formulaArg).ToList() { + if cell.Value() == "" { + count++ + } + } + return newNumberFormulaArg(count) +} + +// COUNTIF function returns the number of cells within a supplied range, that +// satisfy a given criteria. The syntax of the function is: +// +// COUNTIF(range,criteria) +// +func (fn *formulaFuncs) COUNTIF(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "COUNTIF requires 2 arguments") + } + var ( + criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String) + count float64 + ) + for _, cell := range argsList.Front().Value.(formulaArg).ToList() { + if ok, _ := formulaCriteriaEval(cell.Value(), criteria); ok { + count++ + } + } + return newNumberFormulaArg(count) +} + +// formulaIfsMatch function returns cells reference array which match criteria. +func formulaIfsMatch(args []formulaArg) (cellRefs []cellRef) { + for i := 0; i < len(args)-1; i += 2 { + var match []cellRef + matrix, criteria := args[i].Matrix, formulaCriteriaParser(args[i+1].Value()) + if i == 0 { + for rowIdx, row := range matrix { + for colIdx, col := range row { + if ok, _ := formulaCriteriaEval(col.Value(), criteria); ok { + match = append(match, cellRef{Col: colIdx, Row: rowIdx}) + } + } + } + } else { + for _, ref := range cellRefs { + value := matrix[ref.Row][ref.Col] + if ok, _ := formulaCriteriaEval(value.Value(), criteria); ok { + match = append(match, ref) + } + } + } + if len(match) == 0 { + return + } + cellRefs = match[:] + } + return +} + +// COUNTIFS function returns the number of rows within a table, that satisfy a +// set of given criteria. The syntax of the function is: +// +// COUNTIFS(criteria_range1,criteria1,[criteria_range2,criteria2],...) +// +func (fn *formulaFuncs) COUNTIFS(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "COUNTIFS requires at least 2 arguments") + } + if argsList.Len()%2 != 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + var args []formulaArg + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + args = append(args, arg.Value.(formulaArg)) + } + return newNumberFormulaArg(float64(len(formulaIfsMatch(args)))) +} + +// CRITBINOM function returns the inverse of the Cumulative Binomial +// Distribution. I.e. for a specific number of independent trials, the +// function returns the smallest value (number of successes) for which the +// cumulative binomial distribution is greater than or equal to a specified +// value. The syntax of the function is: +// +// CRITBINOM(trials,probability_s,alpha) +// +func (fn *formulaFuncs) CRITBINOM(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "CRITBINOM requires 3 numeric arguments") + } + return fn.BINOMdotINV(argsList) +} + +// DEVSQ function calculates the sum of the squared deviations from the sample +// mean. The syntax of the function is: +// +// DEVSQ(number1,[number2],...) +// +func (fn *formulaFuncs) DEVSQ(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DEVSQ requires at least 1 numeric argument") + } + avg, count, result := fn.AVERAGE(argsList), -1, 0.0 + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + for _, number := range arg.Value.(formulaArg).ToList() { + num := number.ToNumber() + if num.Type != ArgNumber { + continue + } + count++ + if count == 0 { + result = math.Pow(num.Number-avg.Number, 2) + continue + } + result += math.Pow(num.Number-avg.Number, 2) + } + } + if count == -1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(result) +} + +// FISHER function calculates the Fisher Transformation for a supplied value. +// The syntax of the function is: +// +// FISHER(x) +// +func (fn *formulaFuncs) FISHER(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument") + } + token := argsList.Front().Value.(formulaArg) + switch token.Type { + case ArgString: + arg := token.ToNumber() + if arg.Type == ArgNumber { + if arg.Number <= -1 || arg.Number >= 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(0.5 * math.Log((1+arg.Number)/(1-arg.Number))) + } + case ArgNumber: + if token.Number <= -1 || token.Number >= 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(0.5 * math.Log((1+token.Number)/(1-token.Number))) + } + return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument") +} + +// FISHERINV function calculates the inverse of the Fisher Transformation and +// returns a value between -1 and +1. The syntax of the function is: +// +// FISHERINV(y) +// +func (fn *formulaFuncs) FISHERINV(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument") + } + token := argsList.Front().Value.(formulaArg) + switch token.Type { + case ArgString: + arg := token.ToNumber() + if arg.Type == ArgNumber { + return newNumberFormulaArg((math.Exp(2*arg.Number) - 1) / (math.Exp(2*arg.Number) + 1)) + } + case ArgNumber: + return newNumberFormulaArg((math.Exp(2*token.Number) - 1) / (math.Exp(2*token.Number) + 1)) + } + return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument") +} + +// GAMMA function returns the value of the Gamma Function, Γ(n), for a +// specified number, n. The syntax of the function is: +// +// GAMMA(number) +// +func (fn *formulaFuncs) GAMMA(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument") + } + token := argsList.Front().Value.(formulaArg) + switch token.Type { + case ArgString: + arg := token.ToNumber() + if arg.Type == ArgNumber { + if arg.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(math.Gamma(arg.Number)) + } + case ArgNumber: + if token.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(math.Gamma(token.Number)) + } + return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument") +} + +// GAMMAdotDIST function returns the Gamma Distribution, which is frequently +// used to provide probabilities for values that may have a skewed +// distribution, such as queuing analysis. +// +// GAMMA.DIST(x,alpha,beta,cumulative) +// +func (fn *formulaFuncs) GAMMAdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMA.DIST requires 4 arguments") + } + return fn.GAMMADIST(argsList) +} + +// GAMMADIST function returns the Gamma Distribution, which is frequently used +// to provide probabilities for values that may have a skewed distribution, +// such as queuing analysis. +// +// GAMMADIST(x,alpha,beta,cumulative) +// +func (fn *formulaFuncs) GAMMADIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMADIST requires 4 arguments") + } + var x, alpha, beta, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if x.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if alpha = argsList.Front().Next().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber { + return alpha + } + if beta = argsList.Back().Prev().Value.(formulaArg).ToNumber(); beta.Type != ArgNumber { + return beta + } + if alpha.Number <= 0 || beta.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { + return cumulative + } + if cumulative.Number == 1 { + return newNumberFormulaArg(incompleteGamma(alpha.Number, x.Number/beta.Number) / math.Gamma(alpha.Number)) + } + return newNumberFormulaArg((1 / (math.Pow(beta.Number, alpha.Number) * math.Gamma(alpha.Number))) * math.Pow(x.Number, alpha.Number-1) * math.Exp(0-(x.Number/beta.Number))) +} + +// gammainv returns the inverse of the Gamma distribution for the specified +// value. +func gammainv(probability, alpha, beta float64) float64 { + xLo, xHi := 0.0, alpha*beta*5 + dx, x, xNew, result := 1024.0, 1.0, 1.0, 0.0 + for i := 0; math.Abs(dx) > 8.88e-016 && i <= 256; i++ { + result = incompleteGamma(alpha, x/beta) / math.Gamma(alpha) + e := result - probability + if e == 0 { + dx = 0 + } else if e < 0 { + xLo = x + } else { + xHi = x + } + pdf := (1 / (math.Pow(beta, alpha) * math.Gamma(alpha))) * math.Pow(x, alpha-1) * math.Exp(0-(x/beta)) + if pdf != 0 { + dx = e / pdf + xNew = x - dx + } + if xNew < xLo || xNew > xHi || pdf == 0 { + xNew = (xLo + xHi) / 2 + dx = xNew - x + } + x = xNew + } + return x +} + +// GAMMAdotINV function returns the inverse of the Gamma Cumulative +// Distribution. The syntax of the function is: +// +// GAMMA.INV(probability,alpha,beta) +// +func (fn *formulaFuncs) GAMMAdotINV(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMA.INV requires 3 arguments") + } + return fn.GAMMAINV(argsList) +} + +// GAMMAINV function returns the inverse of the Gamma Cumulative Distribution. +// The syntax of the function is: +// +// GAMMAINV(probability,alpha,beta) +// +func (fn *formulaFuncs) GAMMAINV(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMAINV requires 3 arguments") + } + var probability, alpha, beta formulaArg + if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + if probability.Number < 0 || probability.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if alpha = argsList.Front().Next().Value.(formulaArg).ToNumber(); alpha.Type != ArgNumber { + return alpha + } + if beta = argsList.Back().Value.(formulaArg).ToNumber(); beta.Type != ArgNumber { + return beta + } + if alpha.Number <= 0 || beta.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(gammainv(probability.Number, alpha.Number, beta.Number)) +} + +// GAMMALN function returns the natural logarithm of the Gamma Function, Γ +// (n). The syntax of the function is: +// +// GAMMALN(x) +// +func (fn *formulaFuncs) GAMMALN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument") + } + token := argsList.Front().Value.(formulaArg) + switch token.Type { + case ArgString: + arg := token.ToNumber() + if arg.Type == ArgNumber { + if arg.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(math.Log(math.Gamma(arg.Number))) + } + case ArgNumber: + if token.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(math.Log(math.Gamma(token.Number))) + } + return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument") +} + +// GAMMALNdotPRECISE function returns the natural logarithm of the Gamma +// Function, Γ(n). The syntax of the function is: +// +// GAMMALN.PRECISE(x) +// +func (fn *formulaFuncs) GAMMALNdotPRECISE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN.PRECISE requires 1 numeric argument") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + if x.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(getLogGamma(x.Number)) +} + +// GAUSS function returns the probability that a member of a standard normal +// population will fall between the mean and a specified number of standard +// deviations from the mean. The syntax of the function is: +// +// GAUSS(z) +// +func (fn *formulaFuncs) GAUSS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "GAUSS requires 1 numeric argument") + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) + args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) + args.PushBack(newBoolFormulaArg(true)) + normdist := fn.NORMDIST(args) + if normdist.Type != ArgNumber { + return normdist + } + return newNumberFormulaArg(normdist.Number - 0.5) +} + +// GEOMEAN function calculates the geometric mean of a supplied set of values. +// The syntax of the function is: +// +// GEOMEAN(number1,[number2],...) +// +func (fn *formulaFuncs) GEOMEAN(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "GEOMEAN requires at least 1 numeric argument") + } + product := fn.PRODUCT(argsList) + if product.Type != ArgNumber { + return product + } + count := fn.COUNT(argsList) + min := fn.MIN(argsList) + if product.Number > 0 && min.Number > 0 { + return newNumberFormulaArg(math.Pow(product.Number, 1/count.Number)) + } + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) +} + +// HARMEAN function calculates the harmonic mean of a supplied set of values. +// The syntax of the function is: +// +// HARMEAN(number1,[number2],...) +// +func (fn *formulaFuncs) HARMEAN(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "HARMEAN requires at least 1 argument") + } + if min := fn.MIN(argsList); min.Number < 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + number, val, cnt := 0.0, 0.0, 0.0 + for token := argsList.Front(); token != nil; token = token.Next() { + arg := token.Value.(formulaArg) + switch arg.Type { + case ArgString: + num := arg.ToNumber() + if num.Type != ArgNumber { + continue + } + number = num.Number + case ArgNumber: + number = arg.Number + } + if number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + val += 1 / number + cnt++ + } + return newNumberFormulaArg(1 / (val / cnt)) +} + +// checkHYPGEOMDISTArgs checking arguments for the formula function HYPGEOMDIST +// and HYPGEOM.DIST. +func checkHYPGEOMDISTArgs(sampleS, numberSample, populationS, numberPop formulaArg) bool { + return sampleS.Number < 0 || + sampleS.Number > math.Min(numberSample.Number, populationS.Number) || + sampleS.Number < math.Max(0, numberSample.Number-numberPop.Number+populationS.Number) || + numberSample.Number <= 0 || + numberSample.Number > numberPop.Number || + populationS.Number <= 0 || + populationS.Number > numberPop.Number || + numberPop.Number <= 0 +} + +// prepareHYPGEOMDISTArgs prepare arguments for the formula function +// HYPGEOMDIST and HYPGEOM.DIST. +func (fn *formulaFuncs) prepareHYPGEOMDISTArgs(name string, argsList *list.List) formulaArg { + if name == "HYPGEOMDIST" && argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "HYPGEOMDIST requires 4 numeric arguments") + } + if name == "HYPGEOM.DIST" && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "HYPGEOM.DIST requires 5 arguments") + } + var sampleS, numberSample, populationS, numberPop, cumulative formulaArg + if sampleS = argsList.Front().Value.(formulaArg).ToNumber(); sampleS.Type != ArgNumber { + return sampleS + } + if numberSample = argsList.Front().Next().Value.(formulaArg).ToNumber(); numberSample.Type != ArgNumber { + return numberSample + } + if populationS = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); populationS.Type != ArgNumber { + return populationS + } + if numberPop = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); numberPop.Type != ArgNumber { + return numberPop + } + if checkHYPGEOMDISTArgs(sampleS, numberSample, populationS, numberPop) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if name == "HYPGEOM.DIST" { + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber { + return cumulative + } + } + return newListFormulaArg([]formulaArg{sampleS, numberSample, populationS, numberPop, cumulative}) +} + +// HYPGEOMdotDIST function returns the value of the hypergeometric distribution +// for a specified number of successes from a population sample. The function +// can calculate the cumulative distribution or the probability density +// function. The syntax of the function is: +// +// HYPGEOM.DIST(sample_s,number_sample,population_s,number_pop,cumulative) +// +func (fn *formulaFuncs) HYPGEOMdotDIST(argsList *list.List) formulaArg { + args := fn.prepareHYPGEOMDISTArgs("HYPGEOM.DIST", argsList) + if args.Type != ArgList { + return args + } + sampleS, numberSample, populationS, numberPop, cumulative := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4] + if cumulative.Number == 1 { + var res float64 + for i := 0; i <= int(sampleS.Number); i++ { + res += binomCoeff(populationS.Number, float64(i)) * + binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-float64(i)) / + binomCoeff(numberPop.Number, numberSample.Number) + } + return newNumberFormulaArg(res) + } + return newNumberFormulaArg(binomCoeff(populationS.Number, sampleS.Number) * + binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-sampleS.Number) / + binomCoeff(numberPop.Number, numberSample.Number)) +} + +// HYPGEOMDIST function returns the value of the hypergeometric distribution +// for a given number of successes from a sample of a population. The syntax +// of the function is: +// +// HYPGEOMDIST(sample_s,number_sample,population_s,number_pop) +// +func (fn *formulaFuncs) HYPGEOMDIST(argsList *list.List) formulaArg { + args := fn.prepareHYPGEOMDISTArgs("HYPGEOMDIST", argsList) + if args.Type != ArgList { + return args + } + sampleS, numberSample, populationS, numberPop := args.List[0], args.List[1], args.List[2], args.List[3] + return newNumberFormulaArg(binomCoeff(populationS.Number, sampleS.Number) * + binomCoeff(numberPop.Number-populationS.Number, numberSample.Number-sampleS.Number) / + binomCoeff(numberPop.Number, numberSample.Number)) +} + +// KURT function calculates the kurtosis of a supplied set of values. The +// syntax of the function is: +// +// KURT(number1,[number2],...) +// +func (fn *formulaFuncs) KURT(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "KURT requires at least 1 argument") + } + mean, stdev := fn.AVERAGE(argsList), fn.STDEV(argsList) + if stdev.Number > 0 { + count, summer := 0.0, 0.0 + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgString, ArgNumber: + num := token.ToNumber() + if num.Type == ArgError { + continue + } + summer += math.Pow((num.Number-mean.Number)/stdev.Number, 4) + count++ + case ArgList, ArgMatrix: + for _, row := range token.ToList() { + if row.Type == ArgNumber || row.Type == ArgString { + num := row.ToNumber() + if num.Type == ArgError { + continue + } + summer += math.Pow((num.Number-mean.Number)/stdev.Number, 4) + count++ + } + } + } + } + if count > 3 { + return newNumberFormulaArg(summer*(count*(count+1)/((count-1)*(count-2)*(count-3))) - (3 * math.Pow(count-1, 2) / ((count - 2) * (count - 3)))) + } + } + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) +} + +// EXPONdotDIST function returns the value of the exponential distribution for +// a give value of x. The user can specify whether the probability density +// function or the cumulative distribution function is used. The syntax of the +// Expondist function is: +// +// EXPON.DIST(x,lambda,cumulative) +// +func (fn *formulaFuncs) EXPONdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "EXPON.DIST requires 3 arguments") + } + return fn.EXPONDIST(argsList) +} + +// EXPONDIST function returns the value of the exponential distribution for a +// give value of x. The user can specify whether the probability density +// function or the cumulative distribution function is used. The syntax of the +// Expondist function is: +// +// EXPONDIST(x,lambda,cumulative) +// +func (fn *formulaFuncs) EXPONDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "EXPONDIST requires 3 arguments") + } + var x, lambda, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if lambda = argsList.Front().Next().Value.(formulaArg).ToNumber(); lambda.Type != ArgNumber { + return lambda + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { + return cumulative + } + if x.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if lambda.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative.Number == 1 { + return newNumberFormulaArg(1 - math.Exp(-lambda.Number*x.Number)) + } + return newNumberFormulaArg(lambda.Number * math.Exp(-lambda.Number*x.Number)) +} + +// FdotDIST function calculates the Probability Density Function or the +// Cumulative Distribution Function for the F Distribution. This function is +// frequently used to measure the degree of diversity between two data +// sets. The syntax of the function is: +// +// F.DIST(x,deg_freedom1,deg_freedom2,cumulative) +// +func (fn *formulaFuncs) FdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "F.DIST requires 4 arguments") + } + var x, deg1, deg2, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if deg1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); deg1.Type != ArgNumber { + return deg1 + } + if deg2 = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); deg2.Type != ArgNumber { + return deg2 + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { + return cumulative + } + if x.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + maxDeg := math.Pow10(10) + if deg1.Number < 1 || deg1.Number >= maxDeg { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if deg2.Number < 1 || deg2.Number >= maxDeg { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative.Number == 1 { + return newNumberFormulaArg(1 - getBetaDist(deg2.Number/(deg2.Number+deg1.Number*x.Number), deg2.Number/2, deg1.Number/2)) + } + return newNumberFormulaArg(math.Gamma((deg2.Number+deg1.Number)/2) / (math.Gamma(deg1.Number/2) * math.Gamma(deg2.Number/2)) * math.Pow(deg1.Number/deg2.Number, deg1.Number/2) * (math.Pow(x.Number, (deg1.Number-2)/2) / math.Pow(1+(deg1.Number/deg2.Number)*x.Number, (deg1.Number+deg2.Number)/2))) +} + +// FDIST function calculates the (right-tailed) F Probability Distribution, +// which measures the degree of diversity between two data sets. The syntax +// of the function is: +// +// FDIST(x,deg_freedom1,deg_freedom2) +// +func (fn *formulaFuncs) FDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "FDIST requires 3 arguments") + } + var x, deg1, deg2 formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if deg1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); deg1.Type != ArgNumber { + return deg1 + } + if deg2 = argsList.Back().Value.(formulaArg).ToNumber(); deg2.Type != ArgNumber { + return deg2 + } + if x.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + maxDeg := math.Pow10(10) + if deg1.Number < 1 || deg1.Number >= maxDeg { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if deg2.Number < 1 || deg2.Number >= maxDeg { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + args := list.New() + args.PushBack(newNumberFormulaArg(deg1.Number * x.Number / (deg1.Number*x.Number + deg2.Number))) + args.PushBack(newNumberFormulaArg(0.5 * deg1.Number)) + args.PushBack(newNumberFormulaArg(0.5 * deg2.Number)) + args.PushBack(newNumberFormulaArg(0)) + args.PushBack(newNumberFormulaArg(1)) + return newNumberFormulaArg(1 - fn.BETADIST(args).Number) +} + +// FdotDISTdotRT function calculates the (right-tailed) F Probability +// Distribution, which measures the degree of diversity between two data sets. +// The syntax of the function is: +// +// F.DIST.RT(x,deg_freedom1,deg_freedom2) +// +func (fn *formulaFuncs) FdotDISTdotRT(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "F.DIST.RT requires 3 arguments") + } + return fn.FDIST(argsList) +} + +// prepareFinvArgs checking and prepare arguments for the formula function +// F.INV, F.INV.RT and FINV. +func (fn *formulaFuncs) prepareFinvArgs(name string, argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name)) + } + var probability, d1, d2 formulaArg + if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + if d1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); d1.Type != ArgNumber { + return d1 + } + if d2 = argsList.Back().Value.(formulaArg).ToNumber(); d2.Type != ArgNumber { + return d2 + } + if probability.Number <= 0 || probability.Number > 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if d1.Number < 1 || d1.Number >= math.Pow10(10) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if d2.Number < 1 || d2.Number >= math.Pow10(10) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newListFormulaArg([]formulaArg{probability, d1, d2}) +} + +// FdotINV function calculates the inverse of the Cumulative F Distribution +// for a supplied probability. The syntax of the F.Inv function is: +// +// F.INV(probability,deg_freedom1,deg_freedom2) +// +func (fn *formulaFuncs) FdotINV(argsList *list.List) formulaArg { + args := fn.prepareFinvArgs("F.INV", argsList) + if args.Type != ArgList { + return args + } + probability, d1, d2 := args.List[0], args.List[1], args.List[2] + return newNumberFormulaArg((1/calcBetainv(1-probability.Number, d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number)) +} + +// FdotINVdotRT function calculates the inverse of the (right-tailed) F +// Probability Distribution for a supplied probability. The syntax of the +// function is: +// +// F.INV.RT(probability,deg_freedom1,deg_freedom2) +// +func (fn *formulaFuncs) FdotINVdotRT(argsList *list.List) formulaArg { + args := fn.prepareFinvArgs("F.INV.RT", argsList) + if args.Type != ArgList { + return args + } + probability, d1, d2 := args.List[0], args.List[1], args.List[2] + return newNumberFormulaArg((1/calcBetainv(1-(1-probability.Number), d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number)) +} + +// FINV function calculates the inverse of the (right-tailed) F Probability +// Distribution for a supplied probability. The syntax of the function is: +// +// FINV(probability,deg_freedom1,deg_freedom2) +// +func (fn *formulaFuncs) FINV(argsList *list.List) formulaArg { + args := fn.prepareFinvArgs("FINV", argsList) + if args.Type != ArgList { + return args + } + probability, d1, d2 := args.List[0], args.List[1], args.List[2] + return newNumberFormulaArg((1/calcBetainv(1-(1-probability.Number), d2.Number/2, d1.Number/2, 0, 1) - 1) * (d2.Number / d1.Number)) +} + +// FdotTEST function returns the F-Test for two supplied arrays. I.e. the +// function returns the two-tailed probability that the variances in the two +// supplied arrays are not significantly different. The syntax of the Ftest +// function is: +// +// F.TEST(array1,array2) +// +func (fn *formulaFuncs) FdotTEST(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "F.TEST requires 2 arguments") + } + array1 := argsList.Front().Value.(formulaArg) + array2 := argsList.Back().Value.(formulaArg) + left, right := array1.ToList(), array2.ToList() + collectMatrix := func(args []formulaArg) (n, accu float64) { + var p, sum float64 + for _, arg := range args { + if num := arg.ToNumber(); num.Type == ArgNumber { + x := num.Number - p + y := x / (n + 1) + p += y + accu += n * x * y + n++ + sum += num.Number + } + } + return + } + nums, accu := collectMatrix(left) + f3 := nums - 1 + if nums == 1 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + f1 := accu / (nums - 1) + if f1 == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + nums, accu = collectMatrix(right) + f4 := nums - 1 + if nums == 1 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + f2 := accu / (nums - 1) + if f2 == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + args := list.New() + args.PushBack(newNumberFormulaArg(f1 / f2)) + args.PushBack(newNumberFormulaArg(f3)) + args.PushBack(newNumberFormulaArg(f4)) + probability := (1 - fn.FDIST(args).Number) * 2 + if probability > 1 { + probability = 2 - probability + } + return newNumberFormulaArg(probability) +} + +// FTEST function returns the F-Test for two supplied arrays. I.e. the function +// returns the two-tailed probability that the variances in the two supplied +// arrays are not significantly different. The syntax of the Ftest function +// is: +// +// FTEST(array1,array2) +// +func (fn *formulaFuncs) FTEST(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "FTEST requires 2 arguments") + } + return fn.FdotTEST(argsList) +} + +// LOGINV function calculates the inverse of the Cumulative Log-Normal +// Distribution Function of x, for a supplied probability. The syntax of the +// function is: +// +// LOGINV(probability,mean,standard_dev) +// +func (fn *formulaFuncs) LOGINV(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "LOGINV requires 3 arguments") + } + var probability, mean, stdDev formulaArg + if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { + return mean + } + if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { + return stdDev + } + if probability.Number <= 0 || probability.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if stdDev.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + args := list.New() + args.PushBack(probability) + args.PushBack(newNumberFormulaArg(0)) + args.PushBack(newNumberFormulaArg(1)) + norminv := fn.NORMINV(args) + return newNumberFormulaArg(math.Exp(mean.Number + stdDev.Number*norminv.Number)) +} + +// LOGNORMdotINV function calculates the inverse of the Cumulative Log-Normal +// Distribution Function of x, for a supplied probability. The syntax of the +// function is: +// +// LOGNORM.INV(probability,mean,standard_dev) +// +func (fn *formulaFuncs) LOGNORMdotINV(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "LOGNORM.INV requires 3 arguments") + } + return fn.LOGINV(argsList) +} + +// LOGNORMdotDIST function calculates the Log-Normal Probability Density +// Function or the Cumulative Log-Normal Distribution Function for a supplied +// value of x. The syntax of the function is: +// +// LOGNORM.DIST(x,mean,standard_dev,cumulative) +// +func (fn *formulaFuncs) LOGNORMdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "LOGNORM.DIST requires 4 arguments") + } + var x, mean, stdDev, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { + return mean + } + if stdDev = argsList.Back().Prev().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { + return stdDev + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { + return cumulative + } + if x.Number <= 0 || stdDev.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative.Number == 1 { + args := list.New() + args.PushBack(newNumberFormulaArg((math.Log(x.Number) - mean.Number) / stdDev.Number)) + args.PushBack(newNumberFormulaArg(0)) + args.PushBack(newNumberFormulaArg(1)) + args.PushBack(cumulative) + return fn.NORMDIST(args) + } + return newNumberFormulaArg((1 / (math.Sqrt(2*math.Pi) * stdDev.Number * x.Number)) * + math.Exp(0-(math.Pow(math.Log(x.Number)-mean.Number, 2)/(2*math.Pow(stdDev.Number, 2))))) +} + +// LOGNORMDIST function calculates the Cumulative Log-Normal Distribution +// Function at a supplied value of x. The syntax of the function is: +// +// LOGNORMDIST(x,mean,standard_dev) +// +func (fn *formulaFuncs) LOGNORMDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "LOGNORMDIST requires 3 arguments") + } + var x, mean, stdDev formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { + return mean + } + if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { + return stdDev + } + if x.Number <= 0 || stdDev.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + args := list.New() + args.PushBack(newNumberFormulaArg((math.Log(x.Number) - mean.Number) / stdDev.Number)) + return fn.NORMSDIST(args) +} + +// MODE function returns the statistical mode (the most frequently occurring +// value) of a list of supplied numbers. If there are 2 or more most +// frequently occurring values in the supplied data, the function returns the +// lowest of these values The syntax of the function is: +// +// MODE(number1,[number2],...) +// +func (fn *formulaFuncs) MODE(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "MODE requires at least 1 argument") + } + var values []float64 + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + cells := arg.Value.(formulaArg) + if cells.Type != ArgMatrix && cells.ToNumber().Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + for _, cell := range cells.ToList() { + if num := cell.ToNumber(); num.Type == ArgNumber { + values = append(values, num.Number) + } + } + } + sort.Float64s(values) + cnt := len(values) + var count, modeCnt int + var mode float64 + for i := 0; i < cnt; i++ { + count = 0 + for j := 0; j < cnt; j++ { + if j != i && values[j] == values[i] { + count++ + } + } + if count > modeCnt { + modeCnt = count + mode = values[i] + } + } + if modeCnt == 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(mode) +} + +// MODEdotMULT function returns a vertical array of the statistical modes +// (the most frequently occurring values) within a list of supplied numbers. +// The syntax of the function is: +// +// MODE.MULT(number1,[number2],...) +// +func (fn *formulaFuncs) MODEdotMULT(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "MODE.MULT requires at least 1 argument") + } + var values []float64 + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + cells := arg.Value.(formulaArg) + if cells.Type != ArgMatrix && cells.ToNumber().Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + for _, cell := range cells.ToList() { + if num := cell.ToNumber(); num.Type == ArgNumber { + values = append(values, num.Number) + } + } + } + sort.Float64s(values) + cnt := len(values) + var count, modeCnt int + var mtx [][]formulaArg + for i := 0; i < cnt; i++ { + count = 0 + for j := i + 1; j < cnt; j++ { + if values[i] == values[j] { + count++ + } + } + if count > modeCnt { + modeCnt = count + mtx = [][]formulaArg{} + mtx = append(mtx, []formulaArg{newNumberFormulaArg(values[i])}) + } else if count == modeCnt { + mtx = append(mtx, []formulaArg{newNumberFormulaArg(values[i])}) + } + } + if modeCnt == 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newMatrixFormulaArg(mtx) +} + +// MODEdotSNGL function returns the statistical mode (the most frequently +// occurring value) within a list of supplied numbers. If there are 2 or more +// most frequently occurring values in the supplied data, the function returns +// the lowest of these values. The syntax of the function is: +// +// MODE.SNGL(number1,[number2],...) +// +func (fn *formulaFuncs) MODEdotSNGL(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "MODE.SNGL requires at least 1 argument") + } + return fn.MODE(argsList) +} + +// NEGBINOMdotDIST function calculates the probability mass function or the +// cumulative distribution function for the Negative Binomial Distribution. +// This gives the probability that there will be a given number of failures +// before a required number of successes is achieved. The syntax of the +// function is: +// +// NEGBINOM.DIST(number_f,number_s,probability_s,cumulative) +// +func (fn *formulaFuncs) NEGBINOMdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "NEGBINOM.DIST requires 4 arguments") + } + var f, s, probability, cumulative formulaArg + if f = argsList.Front().Value.(formulaArg).ToNumber(); f.Type != ArgNumber { + return f + } + if s = argsList.Front().Next().Value.(formulaArg).ToNumber(); s.Type != ArgNumber { + return s + } + if probability = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber { + return cumulative + } + if f.Number < 0 || s.Number < 1 || probability.Number < 0 || probability.Number > 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative.Number == 1 { + return newNumberFormulaArg(1 - getBetaDist(1-probability.Number, f.Number+1, s.Number)) + } + return newNumberFormulaArg(binomCoeff(f.Number+s.Number-1, s.Number-1) * math.Pow(probability.Number, s.Number) * math.Pow(1-probability.Number, f.Number)) +} + +// NEGBINOMDIST function calculates the Negative Binomial Distribution for a +// given set of parameters. This gives the probability that there will be a +// specified number of failures before a required number of successes is +// achieved. The syntax of the function is: +// +// NEGBINOMDIST(number_f,number_s,probability_s) +// +func (fn *formulaFuncs) NEGBINOMDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "NEGBINOMDIST requires 3 arguments") + } + var f, s, probability formulaArg + if f = argsList.Front().Value.(formulaArg).ToNumber(); f.Type != ArgNumber { + return f + } + if s = argsList.Front().Next().Value.(formulaArg).ToNumber(); s.Type != ArgNumber { + return s + } + if probability = argsList.Back().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + if f.Number < 0 || s.Number < 1 || probability.Number < 0 || probability.Number > 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(binomCoeff(f.Number+s.Number-1, s.Number-1) * math.Pow(probability.Number, s.Number) * math.Pow(1-probability.Number, f.Number)) +} + +// NORMdotDIST function calculates the Normal Probability Density Function or +// the Cumulative Normal Distribution. Function for a supplied set of +// parameters. The syntax of the function is: +// +// NORM.DIST(x,mean,standard_dev,cumulative) +// +func (fn *formulaFuncs) NORMdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "NORM.DIST requires 4 arguments") + } + return fn.NORMDIST(argsList) +} + +// NORMDIST function calculates the Normal Probability Density Function or the +// Cumulative Normal Distribution. Function for a supplied set of parameters. +// The syntax of the function is: +// +// NORMDIST(x,mean,standard_dev,cumulative) +// +func (fn *formulaFuncs) NORMDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "NORMDIST requires 4 arguments") + } + var x, mean, stdDev, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { + return mean + } + if stdDev = argsList.Back().Prev().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { + return stdDev + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError { + return cumulative + } + if stdDev.Number < 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if cumulative.Number == 1 { + return newNumberFormulaArg(0.5 * (1 + math.Erf((x.Number-mean.Number)/(stdDev.Number*math.Sqrt(2))))) + } + return newNumberFormulaArg((1 / (math.Sqrt(2*math.Pi) * stdDev.Number)) * math.Exp(0-(math.Pow(x.Number-mean.Number, 2)/(2*(stdDev.Number*stdDev.Number))))) +} + +// NORMdotINV function calculates the inverse of the Cumulative Normal +// Distribution Function for a supplied value of x, and a supplied +// distribution mean & standard deviation. The syntax of the function is: +// +// NORM.INV(probability,mean,standard_dev) +// +func (fn *formulaFuncs) NORMdotINV(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "NORM.INV requires 3 arguments") + } + return fn.NORMINV(argsList) +} + +// NORMINV function calculates the inverse of the Cumulative Normal +// Distribution Function for a supplied value of x, and a supplied +// distribution mean & standard deviation. The syntax of the function is: +// +// NORMINV(probability,mean,standard_dev) +// +func (fn *formulaFuncs) NORMINV(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "NORMINV requires 3 arguments") + } + var prob, mean, stdDev formulaArg + if prob = argsList.Front().Value.(formulaArg).ToNumber(); prob.Type != ArgNumber { + return prob + } + if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber { + return mean + } + if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber { + return stdDev + } + if prob.Number < 0 || prob.Number > 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if stdDev.Number < 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + inv, err := norminv(prob.Number) + if err != nil { + return newErrorFormulaArg(err.Error(), err.Error()) + } + return newNumberFormulaArg(inv*stdDev.Number + mean.Number) +} + +// NORMdotSdotDIST function calculates the Standard Normal Cumulative +// Distribution Function for a supplied value. The syntax of the function +// is: +// +// NORM.S.DIST(z) +// +func (fn *formulaFuncs) NORMdotSdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.DIST requires 2 numeric arguments") + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) + args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) + args.PushBack(argsList.Back().Value.(formulaArg)) + return fn.NORMDIST(args) +} + +// NORMSDIST function calculates the Standard Normal Cumulative Distribution +// Function for a supplied value. The syntax of the function is: +// +// NORMSDIST(z) +// +func (fn *formulaFuncs) NORMSDIST(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "NORMSDIST requires 1 numeric argument") + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) + args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) + args.PushBack(formulaArg{Type: ArgNumber, Number: 1, Boolean: true}) + return fn.NORMDIST(args) +} + +// NORMSINV function calculates the inverse of the Standard Normal Cumulative +// Distribution Function for a supplied probability value. The syntax of the +// function is: +// +// NORMSINV(probability) +// +func (fn *formulaFuncs) NORMSINV(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "NORMSINV requires 1 numeric argument") + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) + args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) + return fn.NORMINV(args) +} + +// NORMdotSdotINV function calculates the inverse of the Standard Normal +// Cumulative Distribution Function for a supplied probability value. The +// syntax of the function is: +// +// NORM.S.INV(probability) +// +func (fn *formulaFuncs) NORMdotSdotINV(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.INV requires 1 numeric argument") + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + args.PushBack(formulaArg{Type: ArgNumber, Number: 0}) + args.PushBack(formulaArg{Type: ArgNumber, Number: 1}) + return fn.NORMINV(args) +} + +// norminv returns the inverse of the normal cumulative distribution for the +// specified value. +func norminv(p float64) (float64, error) { + a := map[int]float64{ + 1: -3.969683028665376e+01, 2: 2.209460984245205e+02, 3: -2.759285104469687e+02, + 4: 1.383577518672690e+02, 5: -3.066479806614716e+01, 6: 2.506628277459239e+00, + } + b := map[int]float64{ + 1: -5.447609879822406e+01, 2: 1.615858368580409e+02, 3: -1.556989798598866e+02, + 4: 6.680131188771972e+01, 5: -1.328068155288572e+01, + } + c := map[int]float64{ + 1: -7.784894002430293e-03, 2: -3.223964580411365e-01, 3: -2.400758277161838e+00, + 4: -2.549732539343734e+00, 5: 4.374664141464968e+00, 6: 2.938163982698783e+00, + } + d := map[int]float64{ + 1: 7.784695709041462e-03, 2: 3.224671290700398e-01, 3: 2.445134137142996e+00, + 4: 3.754408661907416e+00, + } + pLow := 0.02425 // Use lower region approx. below this + pHigh := 1 - pLow // Use upper region approx. above this + if 0 < p && p < pLow { + // Rational approximation for lower region. + q := math.Sqrt(-2 * math.Log(p)) + return (((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) / + ((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil + } else if pLow <= p && p <= pHigh { + // Rational approximation for central region. + q := p - 0.5 + r := q * q + f1 := ((((a[1]*r+a[2])*r+a[3])*r+a[4])*r + a[5]) * r + f2 := (b[1]*r + b[2]) * r + f3 := ((math.Nextafter(f2, f2)+b[3])*r + b[4]) * r + f4 := (math.Nextafter(f3, f3) + b[5]) * r + return (math.Nextafter(f1, f1) + a[6]) * q / + (math.Nextafter(f4, f4) + 1), nil + } else if pHigh < p && p < 1 { + // Rational approximation for upper region. + q := math.Sqrt(-2 * math.Log(1-p)) + return -(((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) / + ((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil + } + return 0, errors.New(formulaErrorNUM) +} + +// kth is an implementation of the formula functions LARGE and SMALL. +func (fn *formulaFuncs) kth(name string, argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name)) + } + array := argsList.Front().Value.(formulaArg).ToList() + argK := argsList.Back().Value.(formulaArg).ToNumber() + if argK.Type != ArgNumber { + return argK + } + k := int(argK.Number) + if k < 1 { + return newErrorFormulaArg(formulaErrorNUM, "k should be > 0") + } + var data []float64 + for _, arg := range array { + if numArg := arg.ToNumber(); numArg.Type == ArgNumber { + data = append(data, numArg.Number) + } + } + if len(data) < k { + return newErrorFormulaArg(formulaErrorNUM, "k should be <= length of array") + } + sort.Float64s(data) + if name == "LARGE" { + return newNumberFormulaArg(data[len(data)-k]) + } + return newNumberFormulaArg(data[k-1]) +} + +// LARGE function returns the k'th largest value from an array of numeric +// values. The syntax of the function is: +// +// LARGE(array,k) +// +func (fn *formulaFuncs) LARGE(argsList *list.List) formulaArg { + return fn.kth("LARGE", argsList) +} + +// MAX function returns the largest value from a supplied set of numeric +// values. The syntax of the function is: +// +// MAX(number1,[number2],...) +// +func (fn *formulaFuncs) MAX(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "MAX requires at least 1 argument") + } + return fn.max(false, argsList) +} + +// MAXA function returns the largest value from a supplied set of numeric +// values, while counting text and the logical value FALSE as the value 0 and +// counting the logical value TRUE as the value 1. The syntax of the function +// is: +// +// MAXA(number1,[number2],...) +// +func (fn *formulaFuncs) MAXA(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "MAXA requires at least 1 argument") + } + return fn.max(true, argsList) +} + +// MAXIFS function returns the maximum value from a subset of values that are +// specified according to one or more criteria. The syntax of the function +// is: +// +// MAXIFS(max_range,criteria_range1,criteria1,[criteria_range2,criteria2],...) +// +func (fn *formulaFuncs) MAXIFS(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "MAXIFS requires at least 3 arguments") + } + if argsList.Len()%2 != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + var args []formulaArg + max, maxRange := -math.MaxFloat64, argsList.Front().Value.(formulaArg).Matrix + for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { + args = append(args, arg.Value.(formulaArg)) + } + for _, ref := range formulaIfsMatch(args) { + if num := maxRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber && max < num.Number { + max = num.Number + } + } + if max == -math.MaxFloat64 { + max = 0 + } + return newNumberFormulaArg(max) +} + +// calcListMatrixMax is part of the implementation max. +func calcListMatrixMax(maxa bool, max float64, arg formulaArg) float64 { + for _, row := range arg.ToList() { + switch row.Type { + case ArgString: + if !maxa && (row.Value() == "TRUE" || row.Value() == "FALSE") { + continue + } else { + num := row.ToBool() + if num.Type == ArgNumber && num.Number > max { + max = num.Number + continue + } + } + num := row.ToNumber() + if num.Type != ArgError && num.Number > max { + max = num.Number + } + case ArgNumber: + if row.Number > max { + max = row.Number + } + } + } + return max +} + +// max is an implementation of the formula functions MAX and MAXA. +func (fn *formulaFuncs) max(maxa bool, argsList *list.List) formulaArg { + max := -math.MaxFloat64 + for token := argsList.Front(); token != nil; token = token.Next() { + arg := token.Value.(formulaArg) + switch arg.Type { + case ArgString: + if !maxa && (arg.Value() == "TRUE" || arg.Value() == "FALSE") { + continue + } else { + num := arg.ToBool() + if num.Type == ArgNumber && num.Number > max { + max = num.Number + continue + } + } + num := arg.ToNumber() + if num.Type != ArgError && num.Number > max { + max = num.Number + } + case ArgNumber: + if arg.Number > max { + max = arg.Number + } + case ArgList, ArgMatrix: + max = calcListMatrixMax(maxa, max, arg) + case ArgError: + return arg + } + } + if max == -math.MaxFloat64 { + max = 0 + } + return newNumberFormulaArg(max) +} + +// MEDIAN function returns the statistical median (the middle value) of a list +// of supplied numbers. The syntax of the function is: +// +// MEDIAN(number1,[number2],...) +// +func (fn *formulaFuncs) MEDIAN(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "MEDIAN requires at least 1 argument") + } + var values []float64 + var median, digits float64 + var err error + for token := argsList.Front(); token != nil; token = token.Next() { + arg := token.Value.(formulaArg) + switch arg.Type { + case ArgString: + num := arg.ToNumber() + if num.Type == ArgError { + return newErrorFormulaArg(formulaErrorVALUE, num.Error) + } + values = append(values, num.Number) + case ArgNumber: + values = append(values, arg.Number) + case ArgMatrix: + for _, row := range arg.Matrix { + for _, value := range row { + if value.String == "" { + continue + } + if digits, err = strconv.ParseFloat(value.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + values = append(values, digits) + } + } + } + } + sort.Float64s(values) + if len(values)%2 == 0 { + median = (values[len(values)/2-1] + values[len(values)/2]) / 2 + } else { + median = values[len(values)/2] + } + return newNumberFormulaArg(median) +} + +// MIN function returns the smallest value from a supplied set of numeric +// values. The syntax of the function is: +// +// MIN(number1,[number2],...) +// +func (fn *formulaFuncs) MIN(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "MIN requires at least 1 argument") + } + return fn.min(false, argsList) +} + +// MINA function returns the smallest value from a supplied set of numeric +// values, while counting text and the logical value FALSE as the value 0 and +// counting the logical value TRUE as the value 1. The syntax of the function +// is: +// +// MINA(number1,[number2],...) +// +func (fn *formulaFuncs) MINA(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "MINA requires at least 1 argument") + } + return fn.min(true, argsList) +} + +// MINIFS function returns the minimum value from a subset of values that are +// specified according to one or more criteria. The syntax of the function +// is: +// +// MINIFS(min_range,criteria_range1,criteria1,[criteria_range2,criteria2],...) +// +func (fn *formulaFuncs) MINIFS(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "MINIFS requires at least 3 arguments") + } + if argsList.Len()%2 != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + var args []formulaArg + min, minRange := math.MaxFloat64, argsList.Front().Value.(formulaArg).Matrix + for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { + args = append(args, arg.Value.(formulaArg)) + } + for _, ref := range formulaIfsMatch(args) { + if num := minRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber && min > num.Number { + min = num.Number + } + } + if min == math.MaxFloat64 { + min = 0 + } + return newNumberFormulaArg(min) +} + +// calcListMatrixMin is part of the implementation min. +func calcListMatrixMin(mina bool, min float64, arg formulaArg) float64 { + for _, row := range arg.ToList() { + switch row.Type { + case ArgString: + if !mina && (row.Value() == "TRUE" || row.Value() == "FALSE") { + continue + } else { + num := row.ToBool() + if num.Type == ArgNumber && num.Number < min { + min = num.Number + continue + } + } + num := row.ToNumber() + if num.Type != ArgError && num.Number < min { + min = num.Number + } + case ArgNumber: + if row.Number < min { + min = row.Number + } + } + } + return min +} + +// min is an implementation of the formula functions MIN and MINA. +func (fn *formulaFuncs) min(mina bool, argsList *list.List) formulaArg { + min := math.MaxFloat64 + for token := argsList.Front(); token != nil; token = token.Next() { + arg := token.Value.(formulaArg) + switch arg.Type { + case ArgString: + if !mina && (arg.Value() == "TRUE" || arg.Value() == "FALSE") { + continue + } else { + num := arg.ToBool() + if num.Type == ArgNumber && num.Number < min { + min = num.Number + continue + } + } + num := arg.ToNumber() + if num.Type != ArgError && num.Number < min { + min = num.Number + } + case ArgNumber: + if arg.Number < min { + min = arg.Number + } + case ArgList, ArgMatrix: + min = calcListMatrixMin(mina, min, arg) + case ArgError: + return arg + } + } + if min == math.MaxFloat64 { + min = 0 + } + return newNumberFormulaArg(min) +} + +// PERCENTILEdotEXC function returns the k'th percentile (i.e. the value below +// which k% of the data values fall) for a supplied range of values and a +// supplied k (between 0 & 1 exclusive).The syntax of the function is: +// +// PERCENTILE.EXC(array,k) +// +func (fn *formulaFuncs) PERCENTILEdotEXC(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE.EXC requires 2 arguments") + } + array := argsList.Front().Value.(formulaArg).ToList() + k := argsList.Back().Value.(formulaArg).ToNumber() + if k.Type != ArgNumber { + return k + } + if k.Number <= 0 || k.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + var numbers []float64 + for _, arg := range array { + if arg.Type == ArgError { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + num := arg.ToNumber() + if num.Type == ArgNumber { + numbers = append(numbers, num.Number) + } + } + cnt := len(numbers) + sort.Float64s(numbers) + idx := k.Number * (float64(cnt) + 1) + base := math.Floor(idx) + next := base - 1 + proportion := math.Nextafter(idx, idx) - base + return newNumberFormulaArg(numbers[int(next)] + ((numbers[int(base)] - numbers[int(next)]) * proportion)) +} + +// PERCENTILEdotINC function returns the k'th percentile (i.e. the value below +// which k% of the data values fall) for a supplied range of values and a +// supplied k. The syntax of the function is: +// +// PERCENTILE.INC(array,k) +// +func (fn *formulaFuncs) PERCENTILEdotINC(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE.INC requires 2 arguments") + } + return fn.PERCENTILE(argsList) +} + +// PERCENTILE function returns the k'th percentile (i.e. the value below which +// k% of the data values fall) for a supplied range of values and a supplied +// k. The syntax of the function is: +// +// PERCENTILE(array,k) +// +func (fn *formulaFuncs) PERCENTILE(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE requires 2 arguments") + } + array := argsList.Front().Value.(formulaArg).ToList() + k := argsList.Back().Value.(formulaArg).ToNumber() + if k.Type != ArgNumber { + return k + } + if k.Number < 0 || k.Number > 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + var numbers []float64 + for _, arg := range array { + if arg.Type == ArgError { + return arg + } + num := arg.ToNumber() + if num.Type == ArgNumber { + numbers = append(numbers, num.Number) + } + } + cnt := len(numbers) + sort.Float64s(numbers) + idx := k.Number * (float64(cnt) - 1) + base := math.Floor(idx) + if idx == base { + return newNumberFormulaArg(numbers[int(idx)]) + } + next := base + 1 + proportion := math.Nextafter(idx, idx) - base + return newNumberFormulaArg(numbers[int(base)] + ((numbers[int(next)] - numbers[int(base)]) * proportion)) +} + +// percentrank is an implementation of the formula functions PERCENTRANK and +// PERCENTRANK.INC. +func (fn *formulaFuncs) percentrank(name string, argsList *list.List) formulaArg { + if argsList.Len() != 2 && argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 or 3 arguments", name)) + } + array := argsList.Front().Value.(formulaArg).ToList() + x := argsList.Front().Next().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + var numbers []float64 + for _, arg := range array { + if arg.Type == ArgError { + return arg + } + num := arg.ToNumber() + if num.Type == ArgNumber { + numbers = append(numbers, num.Number) + } + } + cnt := len(numbers) + sort.Float64s(numbers) + if x.Number < numbers[0] || x.Number > numbers[cnt-1] { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + pos, significance := float64(inFloat64Slice(numbers, x.Number)), newNumberFormulaArg(3) + if argsList.Len() == 3 { + if significance = argsList.Back().Value.(formulaArg).ToNumber(); significance.Type != ArgNumber { + return significance + } + if significance.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s arguments significance should be > 1", name)) + } + } + if pos == -1 { + pos = 0 + cmp := numbers[0] + for cmp < x.Number { + pos++ + cmp = numbers[int(pos)] + } + pos-- + pos += (x.Number - numbers[int(pos)]) / (cmp - numbers[int(pos)]) + } + pow := math.Pow(10, significance.Number) + digit := pow * pos / (float64(cnt) - 1) + if name == "PERCENTRANK.EXC" { + digit = pow * (pos + 1) / (float64(cnt) + 1) + } + return newNumberFormulaArg(math.Floor(digit) / pow) +} + +// PERCENTRANKdotEXC function calculates the relative position, between 0 and +// 1 (exclusive), of a specified value within a supplied array. The syntax of +// the function is: +// +// PERCENTRANK.EXC(array,x,[significance]) +// +func (fn *formulaFuncs) PERCENTRANKdotEXC(argsList *list.List) formulaArg { + return fn.percentrank("PERCENTRANK.EXC", argsList) +} + +// PERCENTRANKdotINC function calculates the relative position, between 0 and +// 1 (inclusive), of a specified value within a supplied array.The syntax of +// the function is: +// +// PERCENTRANK.INC(array,x,[significance]) +// +func (fn *formulaFuncs) PERCENTRANKdotINC(argsList *list.List) formulaArg { + return fn.percentrank("PERCENTRANK.INC", argsList) +} + +// PERCENTRANK function calculates the relative position of a specified value, +// within a set of values, as a percentage. The syntax of the function is: +// +// PERCENTRANK(array,x,[significance]) +// +func (fn *formulaFuncs) PERCENTRANK(argsList *list.List) formulaArg { + return fn.percentrank("PERCENTRANK", argsList) +} + +// PERMUT function calculates the number of permutations of a specified number +// of objects from a set of objects. The syntax of the function is: +// +// PERMUT(number,number_chosen) +// +func (fn *formulaFuncs) PERMUT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "PERMUT requires 2 numeric arguments") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + chosen := argsList.Back().Value.(formulaArg).ToNumber() + if number.Type != ArgNumber { + return number + } + if chosen.Type != ArgNumber { + return chosen + } + if number.Number < chosen.Number { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(math.Round(fact(number.Number) / fact(number.Number-chosen.Number))) +} + +// PERMUTATIONA function calculates the number of permutations, with +// repetitions, of a specified number of objects from a set. The syntax of +// the function is: +// +// PERMUTATIONA(number,number_chosen) +// +func (fn *formulaFuncs) PERMUTATIONA(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "PERMUTATIONA requires 2 numeric arguments") + } + number := argsList.Front().Value.(formulaArg).ToNumber() + chosen := argsList.Back().Value.(formulaArg).ToNumber() + if number.Type != ArgNumber { + return number + } + if chosen.Type != ArgNumber { + return chosen + } + num, numChosen := math.Floor(number.Number), math.Floor(chosen.Number) + if num < 0 || numChosen < 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(math.Pow(num, numChosen)) +} + +// PHI function returns the value of the density function for a standard normal +// distribution for a supplied number. The syntax of the function is: +// +// PHI(x) +// +func (fn *formulaFuncs) PHI(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "PHI requires 1 argument") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + return newNumberFormulaArg(0.39894228040143268 * math.Exp(-(x.Number*x.Number)/2)) +} + +// QUARTILE function returns a requested quartile of a supplied range of +// values. The syntax of the function is: +// +// QUARTILE(array,quart) +// +func (fn *formulaFuncs) QUARTILE(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE requires 2 arguments") + } + quart := argsList.Back().Value.(formulaArg).ToNumber() + if quart.Type != ArgNumber { + return quart + } + if quart.Number < 0 || quart.Number > 4 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + args.PushBack(newNumberFormulaArg(quart.Number / 4)) + return fn.PERCENTILE(args) +} + +// QUARTILEdotEXC function returns a requested quartile of a supplied range of +// values, based on a percentile range of 0 to 1 exclusive. The syntax of the +// function is: +// +// QUARTILE.EXC(array,quart) +// +func (fn *formulaFuncs) QUARTILEdotEXC(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE.EXC requires 2 arguments") + } + quart := argsList.Back().Value.(formulaArg).ToNumber() + if quart.Type != ArgNumber { + return quart + } + if quart.Number <= 0 || quart.Number >= 4 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + args.PushBack(newNumberFormulaArg(quart.Number / 4)) + return fn.PERCENTILEdotEXC(args) +} + +// QUARTILEdotINC function returns a requested quartile of a supplied range of +// values. The syntax of the function is: +// +// QUARTILE.INC(array,quart) +// +func (fn *formulaFuncs) QUARTILEdotINC(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE.INC requires 2 arguments") + } + return fn.QUARTILE(argsList) +} + +// rank is an implementation of the formula functions RANK and RANK.EQ. +func (fn *formulaFuncs) rank(name string, argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name)) + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 3 arguments", name)) + } + num := argsList.Front().Value.(formulaArg).ToNumber() + if num.Type != ArgNumber { + return num + } + var arr []float64 + for _, arg := range argsList.Front().Next().Value.(formulaArg).ToList() { + n := arg.ToNumber() + if n.Type == ArgNumber { + arr = append(arr, n.Number) + } + } + sort.Float64s(arr) + order := newNumberFormulaArg(0) + if argsList.Len() == 3 { + if order = argsList.Back().Value.(formulaArg).ToNumber(); order.Type != ArgNumber { + return order + } + } + if order.Number == 0 { + sort.Sort(sort.Reverse(sort.Float64Slice(arr))) + } + if idx := inFloat64Slice(arr, num.Number); idx != -1 { + return newNumberFormulaArg(float64(idx + 1)) + } + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) +} + +// RANKdotEQ function returns the statistical rank of a given value, within a +// supplied array of values. If there are duplicate values in the list, these +// are given the same rank. The syntax of the function is: +// +// RANK.EQ(number,ref,[order]) +// +func (fn *formulaFuncs) RANKdotEQ(argsList *list.List) formulaArg { + return fn.rank("RANK.EQ", argsList) +} + +// RANK function returns the statistical rank of a given value, within a +// supplied array of values. If there are duplicate values in the list, these +// are given the same rank. The syntax of the function is: +// +// RANK(number,ref,[order]) +// +func (fn *formulaFuncs) RANK(argsList *list.List) formulaArg { + return fn.rank("RANK", argsList) +} + +// SKEW function calculates the skewness of the distribution of a supplied set +// of values. The syntax of the function is: +// +// SKEW(number1,[number2],...) +// +func (fn *formulaFuncs) SKEW(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SKEW requires at least 1 argument") + } + mean, stdDev, count, summer := fn.AVERAGE(argsList), fn.STDEV(argsList), 0.0, 0.0 + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgNumber, ArgString: + num := token.ToNumber() + if num.Type == ArgError { + return num + } + summer += math.Pow((num.Number-mean.Number)/stdDev.Number, 3) + count++ + case ArgList, ArgMatrix: + for _, row := range token.ToList() { + numArg := row.ToNumber() + if numArg.Type != ArgNumber { + continue + } + summer += math.Pow((numArg.Number-mean.Number)/stdDev.Number, 3) + count++ + } + } + } + if count > 2 { + return newNumberFormulaArg(summer * (count / ((count - 1) * (count - 2)))) + } + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) +} + +// SMALL function returns the k'th smallest value from an array of numeric +// values. The syntax of the function is: +// +// SMALL(array,k) +// +func (fn *formulaFuncs) SMALL(argsList *list.List) formulaArg { + return fn.kth("SMALL", argsList) +} + +// STANDARDIZE function returns a normalized value of a distribution that is +// characterized by a supplied mean and standard deviation. The syntax of the +// function is: +// +// STANDARDIZE(x,mean,standard_dev) +// +func (fn *formulaFuncs) STANDARDIZE(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "STANDARDIZE requires 3 arguments") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + mean := argsList.Front().Next().Value.(formulaArg).ToNumber() + if mean.Type != ArgNumber { + return mean + } + stdDev := argsList.Back().Value.(formulaArg).ToNumber() + if stdDev.Type != ArgNumber { + return stdDev + } + if stdDev.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg((x.Number - mean.Number) / stdDev.Number) +} + +// stdevp is an implementation of the formula functions STDEVP and STDEV.P. +func (fn *formulaFuncs) stdevp(name string, argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) + } + varp := fn.VARP(argsList) + if varp.Type != ArgNumber { + return varp + } + return newNumberFormulaArg(math.Sqrt(varp.Number)) +} + +// STDEVP function calculates the standard deviation of a supplied set of +// values. The syntax of the function is: +// +// STDEVP(number1,[number2],...) +// +func (fn *formulaFuncs) STDEVP(argsList *list.List) formulaArg { + return fn.stdevp("STDEVP", argsList) +} + +// STDEVdotP function calculates the standard deviation of a supplied set of +// values. +// +// STDEV.P( number1, [number2], ... ) +// +func (fn *formulaFuncs) STDEVdotP(argsList *list.List) formulaArg { + return fn.stdevp("STDEV.P", argsList) +} + +// getTDist is an implementation for the beta distribution probability density +// function. +func getTDist(T, fDF, nType float64) float64 { + var res float64 + switch nType { + case 1: + res = 0.5 * getBetaDist(fDF/(fDF+T*T), fDF/2, 0.5) + break + case 2: + res = getBetaDist(fDF/(fDF+T*T), fDF/2, 0.5) + break + case 3: + res = math.Pow(1+(T*T/fDF), -(fDF+1)/2) / (math.Sqrt(fDF) * getBeta(0.5, fDF/2.0)) + break + case 4: + X := fDF / (T*T + fDF) + R := 0.5 * getBetaDist(X, 0.5*fDF, 0.5) + res = 1 - R + if T < 0 { + res = R + } + break + } + return res +} + +// TdotDIST function calculates the one-tailed Student's T Distribution, which +// is a continuous probability distribution that is frequently used for +// testing hypotheses on small sample data sets. The syntax of the function +// is: +// +// T.DIST(x,degrees_freedom,cumulative) +// +func (fn *formulaFuncs) TdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "T.DIST requires 3 arguments") + } + var x, degrees, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber { + return cumulative + } + if cumulative.Number == 1 && degrees.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative.Number == 0 { + if degrees.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if degrees.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 3)) + } + return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 4)) +} + +// TdotDISTdot2T function calculates the two-tailed Student's T Distribution, +// which is a continuous probability distribution that is frequently used for +// testing hypotheses on small sample data sets. The syntax of the function +// is: +// +// T.DIST.2T(x,degrees_freedom) +// +func (fn *formulaFuncs) TdotDISTdot2T(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "T.DIST.2T requires 2 arguments") + } + var x, degrees formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if x.Number < 0 || degrees.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 2)) +} + +// TdotDISTdotRT function calculates the right-tailed Student's T Distribution, +// which is a continuous probability distribution that is frequently used for +// testing hypotheses on small sample data sets. The syntax of the function +// is: +// +// T.DIST.RT(x,degrees_freedom) +// +func (fn *formulaFuncs) TdotDISTdotRT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "T.DIST.RT requires 2 arguments") + } + var x, degrees formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if degrees.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + v := getTDist(x.Number, degrees.Number, 1) + if x.Number < 0 { + v = 1 - v + } + return newNumberFormulaArg(v) +} + +// TDIST function calculates the Student's T Distribution, which is a +// continuous probability distribution that is frequently used for testing +// hypotheses on small sample data sets. The syntax of the function is: +// +// TDIST(x,degrees_freedom,tails) +// +func (fn *formulaFuncs) TDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "TDIST requires 3 arguments") + } + var x, degrees, tails formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if tails = argsList.Back().Value.(formulaArg).ToNumber(); tails.Type != ArgNumber { + return tails + } + if x.Number < 0 || degrees.Number < 1 || (tails.Number != 1 && tails.Number != 2) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(getTDist(x.Number, degrees.Number, tails.Number)) +} + +// TdotINV function calculates the left-tailed inverse of the Student's T +// Distribution, which is a continuous probability distribution that is +// frequently used for testing hypotheses on small sample data sets. The +// syntax of the function is: +// +// T.INV(probability,degrees_freedom) +// +func (fn *formulaFuncs) TdotINV(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "T.INV requires 2 arguments") + } + var probability, degrees formulaArg + if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if probability.Number <= 0 || probability.Number >= 1 || degrees.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if probability.Number < 0.5 { + return newNumberFormulaArg(-calcIterateInverse(calcInverseIterator{ + name: "T.INV", + fp: 1 - probability.Number, + fDF: degrees.Number, + nT: 4, + }, degrees.Number/2, degrees.Number)) + } + return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{ + name: "T.INV", + fp: probability.Number, + fDF: degrees.Number, + nT: 4, + }, degrees.Number/2, degrees.Number)) +} + +// TdotINVdot2T function calculates the inverse of the two-tailed Student's T +// Distribution, which is a continuous probability distribution that is +// frequently used for testing hypotheses on small sample data sets. The +// syntax of the function is: +// +// T.INV.2T(probability,degrees_freedom) +// +func (fn *formulaFuncs) TdotINVdot2T(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "T.INV.2T requires 2 arguments") + } + var probability, degrees formulaArg + if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber { + return probability + } + if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if probability.Number <= 0 || probability.Number > 1 || degrees.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{ + name: "T.INV.2T", + fp: probability.Number, + fDF: degrees.Number, + nT: 2, + }, degrees.Number/2, degrees.Number)) +} + +// TINV function calculates the inverse of the two-tailed Student's T +// Distribution, which is a continuous probability distribution that is +// frequently used for testing hypotheses on small sample data sets. The +// syntax of the function is: +// +// TINV(probability,degrees_freedom) +// +func (fn *formulaFuncs) TINV(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "TINV requires 2 arguments") + } + return fn.TdotINVdot2T(argsList) +} + +// tTest calculates the probability associated with the Student's T Test. +func tTest(bTemplin bool, pMat1, pMat2 [][]formulaArg, nC1, nC2, nR1, nR2 int, fT, fF float64) (float64, float64, bool) { + var fCount1, fCount2, fSum1, fSumSqr1, fSum2, fSumSqr2 float64 + var fVal formulaArg + for i := 0; i < nC1; i++ { + for j := 0; j < nR1; j++ { + fVal = pMat1[i][j].ToNumber() + if fVal.Type == ArgNumber { + fSum1 += fVal.Number + fSumSqr1 += fVal.Number * fVal.Number + fCount1++ + } + } + } + for i := 0; i < nC2; i++ { + for j := 0; j < nR2; j++ { + fVal = pMat2[i][j].ToNumber() + if fVal.Type == ArgNumber { + fSum2 += fVal.Number + fSumSqr2 += fVal.Number * fVal.Number + fCount2++ + } + } + } + if fCount1 < 2.0 || fCount2 < 2.0 { + return 0, 0, false + } + if bTemplin { + fS1 := (fSumSqr1 - fSum1*fSum1/fCount1) / (fCount1 - 1) / fCount1 + fS2 := (fSumSqr2 - fSum2*fSum2/fCount2) / (fCount2 - 1) / fCount2 + if fS1+fS2 == 0 { + return 0, 0, false + } + c := fS1 / (fS1 + fS2) + fT = math.Abs(fSum1/fCount1-fSum2/fCount2) / math.Sqrt(fS1+fS2) + fF = 1 / (c*c/(fCount1-1) + (1-c)*(1-c)/(fCount2-1)) + return fT, fF, true + } + fS1 := (fSumSqr1 - fSum1*fSum1/fCount1) / (fCount1 - 1) + fS2 := (fSumSqr2 - fSum2*fSum2/fCount2) / (fCount2 - 1) + fT = math.Abs(fSum1/fCount1-fSum2/fCount2) / math.Sqrt((fCount1-1)*fS1+(fCount2-1)*fS2) * math.Sqrt(fCount1*fCount2*(fCount1+fCount2-2)/(fCount1+fCount2)) + fF = fCount1 + fCount2 - 2 + return fT, fF, true +} + +// tTest is an implementation of the formula function TTEST. +func (fn *formulaFuncs) tTest(pMat1, pMat2 [][]formulaArg, fTails, fTyp float64) formulaArg { + var fT, fF float64 + nC1 := len(pMat1) + nC2 := len(pMat2) + nR1 := len(pMat1[0]) + nR2 := len(pMat2[0]) + ok := true + if fTyp == 1 { + if nC1 != nC2 || nR1 != nR2 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + var fCount, fSum1, fSum2, fSumSqrD float64 + var fVal1, fVal2 formulaArg + for i := 0; i < nC1; i++ { + for j := 0; j < nR1; j++ { + fVal1 = pMat1[i][j].ToNumber() + fVal2 = pMat2[i][j].ToNumber() + if fVal1.Type != ArgNumber || fVal2.Type != ArgNumber { + continue + } + fSum1 += fVal1.Number + fSum2 += fVal2.Number + fSumSqrD += (fVal1.Number - fVal2.Number) * (fVal1.Number - fVal2.Number) + fCount++ + } + } + if fCount < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + fSumD := fSum1 - fSum2 + fDivider := fCount*fSumSqrD - fSumD*fSumD + if fDivider == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + fT = math.Abs(fSumD) * math.Sqrt((fCount-1)/fDivider) + fF = fCount - 1 + } else if fTyp == 2 { + fT, fF, ok = tTest(false, pMat1, pMat2, nC1, nC2, nR1, nR2, fT, fF) + } else { + fT, fF, ok = tTest(true, pMat1, pMat2, nC1, nC2, nR1, nR2, fT, fF) + } + if !ok { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(getTDist(fT, fF, fTails)) +} + +// TTEST function calculates the probability associated with the Student's T +// Test, which is commonly used for identifying whether two data sets are +// likely to have come from the same two underlying populations with the same +// mean. The syntax of the function is: +// +// TTEST(array1,array2,tails,type) +// +func (fn *formulaFuncs) TTEST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "TTEST requires 4 arguments") + } + var array1, array2, tails, typeArg formulaArg + array1 = argsList.Front().Value.(formulaArg) + array2 = argsList.Front().Next().Value.(formulaArg) + if tails = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); tails.Type != ArgNumber { + return tails + } + if typeArg = argsList.Back().Value.(formulaArg).ToNumber(); typeArg.Type != ArgNumber { + return typeArg + } + if len(array1.Matrix) == 0 || len(array2.Matrix) == 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if tails.Number != 1 && tails.Number != 2 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if typeArg.Number != 1 && typeArg.Number != 2 && typeArg.Number != 3 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return fn.tTest(array1.Matrix, array2.Matrix, tails.Number, typeArg.Number) +} + +// TdotTEST function calculates the probability associated with the Student's T +// Test, which is commonly used for identifying whether two data sets are +// likely to have come from the same two underlying populations with the same +// mean. The syntax of the function is: +// +// T.TEST(array1,array2,tails,type) +// +func (fn *formulaFuncs) TdotTEST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "T.TEST requires 4 arguments") + } + return fn.TTEST(argsList) +} + +// TRIMMEAN function calculates the trimmed mean (or truncated mean) of a +// supplied set of values. The syntax of the function is: +// +// TRIMMEAN(array,percent) +// +func (fn *formulaFuncs) TRIMMEAN(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "TRIMMEAN requires 2 arguments") + } + percent := argsList.Back().Value.(formulaArg).ToNumber() + if percent.Type != ArgNumber { + return percent + } + if percent.Number < 0 || percent.Number >= 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + var arr []float64 + arrArg := argsList.Front().Value.(formulaArg).ToList() + for _, cell := range arrArg { + num := cell.ToNumber() + if num.Type != ArgNumber { + continue + } + arr = append(arr, num.Number) + } + discard := math.Floor(float64(len(arr)) * percent.Number / 2) + sort.Float64s(arr) + for i := 0; i < int(discard); i++ { + if len(arr) > 0 { + arr = arr[1:] + } + if len(arr) > 0 { + arr = arr[:len(arr)-1] + } + } + + args := list.New().Init() + for _, ele := range arr { + args.PushBack(newNumberFormulaArg(ele)) + } + return fn.AVERAGE(args) +} + +// vars is an implementation of the formula functions VAR, VARA, VARP, VAR.P +// VAR.S and VARPA. +func (fn *formulaFuncs) vars(name string, argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) + } + summerA, summerB, count := 0.0, 0.0, 0.0 + minimum := 0.0 + if name == "VAR" || name == "VAR.S" || name == "VARA" { + minimum = 1.0 + } + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + for _, token := range arg.Value.(formulaArg).ToList() { + num := token.ToNumber() + if token.Value() != "TRUE" && num.Type == ArgNumber { + summerA += num.Number * num.Number + summerB += num.Number + count++ + continue + } + num = token.ToBool() + if num.Type == ArgNumber { + summerA += num.Number * num.Number + summerB += num.Number + count++ + continue + } + if name == "VARA" || name == "VARPA" { + count++ + } + } + } + if count > minimum { + summerA *= count + summerB *= summerB + return newNumberFormulaArg((summerA - summerB) / (count * (count - minimum))) + } + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) +} + +// VAR function returns the sample variance of a supplied set of values. The +// syntax of the function is: +// +// VAR(number1,[number2],...) +// +func (fn *formulaFuncs) VAR(argsList *list.List) formulaArg { + return fn.vars("VAR", argsList) +} + +// VARA function calculates the sample variance of a supplied set of values. +// The syntax of the function is: +// +// VARA(number1,[number2],...) +// +func (fn *formulaFuncs) VARA(argsList *list.List) formulaArg { + return fn.vars("VARA", argsList) +} + +// VARP function returns the Variance of a given set of values. The syntax of +// the function is: +// +// VARP(number1,[number2],...) +// +func (fn *formulaFuncs) VARP(argsList *list.List) formulaArg { + return fn.vars("VARP", argsList) +} + +// VARdotP function returns the Variance of a given set of values. The syntax +// of the function is: +// +// VAR.P(number1,[number2],...) +// +func (fn *formulaFuncs) VARdotP(argsList *list.List) formulaArg { + return fn.vars("VAR.P", argsList) +} + +// VARdotS function calculates the sample variance of a supplied set of +// values. The syntax of the function is: +// +// VAR.S(number1,[number2],...) +// +func (fn *formulaFuncs) VARdotS(argsList *list.List) formulaArg { + return fn.vars("VAR.S", argsList) +} + +// VARPA function returns the Variance of a given set of values. The syntax of +// the function is: +// +// VARPA(number1,[number2],...) +// +func (fn *formulaFuncs) VARPA(argsList *list.List) formulaArg { + return fn.vars("VARPA", argsList) +} + +// WEIBULL function calculates the Weibull Probability Density Function or the +// Weibull Cumulative Distribution Function for a supplied set of parameters. +// The syntax of the function is: +// +// WEIBULL(x,alpha,beta,cumulative) +// +func (fn *formulaFuncs) WEIBULL(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "WEIBULL requires 4 arguments") + } + x := argsList.Front().Value.(formulaArg).ToNumber() + alpha := argsList.Front().Next().Value.(formulaArg).ToNumber() + beta := argsList.Back().Prev().Value.(formulaArg).ToNumber() + if alpha.Type == ArgNumber && beta.Type == ArgNumber && x.Type == ArgNumber { + if alpha.Number < 0 || alpha.Number <= 0 || beta.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + cumulative := argsList.Back().Value.(formulaArg).ToBool() + if cumulative.Boolean && cumulative.Number == 1 { + return newNumberFormulaArg(1 - math.Exp(0-math.Pow(x.Number/beta.Number, alpha.Number))) + } + return newNumberFormulaArg((alpha.Number / math.Pow(beta.Number, alpha.Number)) * + math.Pow(x.Number, alpha.Number-1) * math.Exp(0-math.Pow(x.Number/beta.Number, alpha.Number))) + } + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) +} + +// WEIBULLdotDIST function calculates the Weibull Probability Density Function +// or the Weibull Cumulative Distribution Function for a supplied set of +// parameters. The syntax of the function is: +// +// WEIBULL.DIST(x,alpha,beta,cumulative) +// +func (fn *formulaFuncs) WEIBULLdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "WEIBULL.DIST requires 4 arguments") + } + return fn.WEIBULL(argsList) +} + +// ZdotTEST function calculates the one-tailed probability value of the +// Z-Test. The syntax of the function is: +// +// Z.TEST(array,x,[sigma]) +// +func (fn *formulaFuncs) ZdotTEST(argsList *list.List) formulaArg { + argsLen := argsList.Len() + if argsLen < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "Z.TEST requires at least 2 arguments") + } + if argsLen > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "Z.TEST accepts at most 3 arguments") + } + return fn.ZTEST(argsList) +} + +// ZTEST function calculates the one-tailed probability value of the Z-Test. +// The syntax of the function is: +// +// ZTEST(array,x,[sigma]) +// +func (fn *formulaFuncs) ZTEST(argsList *list.List) formulaArg { + argsLen := argsList.Len() + if argsLen < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ZTEST requires at least 2 arguments") + } + if argsLen > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "ZTEST accepts at most 3 arguments") + } + arrArg, arrArgs := argsList.Front().Value.(formulaArg), list.New() + arrArgs.PushBack(arrArg) + arr := fn.AVERAGE(arrArgs) + if arr.Type == ArgError { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + x := argsList.Front().Next().Value.(formulaArg).ToNumber() + if x.Type == ArgError { + return x + } + sigma := argsList.Back().Value.(formulaArg).ToNumber() + if sigma.Type == ArgError { + return sigma + } + if argsLen != 3 { + sigma = fn.STDEV(arrArgs).ToNumber() + } + normsdistArg := list.New() + div := sigma.Number / math.Sqrt(float64(len(arrArg.ToList()))) + if div == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + normsdistArg.PushBack(newNumberFormulaArg((arr.Number - x.Number) / div)) + return newNumberFormulaArg(1 - fn.NORMSDIST(normsdistArg).Number) +} + +// Information Functions + +// ERRORdotTYPE function receives an error value and returns an integer, that +// tells you the type of the supplied error. The syntax of the function is: +// +// ERROR.TYPE(error_val) +// +func (fn *formulaFuncs) ERRORdotTYPE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ERROR.TYPE requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + if token.Type == ArgError { + for i, errType := range []string{ + formulaErrorNULL, formulaErrorDIV, formulaErrorVALUE, formulaErrorREF, + formulaErrorNAME, formulaErrorNUM, formulaErrorNA, + } { + if errType == token.String { + return newNumberFormulaArg(float64(i) + 1) + } + } + } + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) +} + +// ISBLANK function tests if a specified cell is blank (empty) and if so, +// returns TRUE; Otherwise the function returns FALSE. The syntax of the +// function is: +// +// ISBLANK(value) +// +func (fn *formulaFuncs) ISBLANK(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISBLANK requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + result := "FALSE" + switch token.Type { + case ArgUnknown: + result = "TRUE" + case ArgString: + if token.String == "" { + result = "TRUE" + } + } + return newStringFormulaArg(result) +} + +// ISERR function tests if an initial supplied expression (or value) returns +// any Excel Error, except the #N/A error. If so, the function returns the +// logical value TRUE; If the supplied value is not an error or is the #N/A +// error, the ISERR function returns FALSE. The syntax of the function is: +// +// ISERR(value) +// +func (fn *formulaFuncs) ISERR(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISERR requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + result := false + if token.Type == ArgError { + for _, errType := range []string{ + formulaErrorDIV, formulaErrorNAME, formulaErrorNUM, + formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, + formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA, + } { + if errType == token.String { + result = true + } + } + } + return newBoolFormulaArg(result) +} + +// ISERROR function tests if an initial supplied expression (or value) returns +// an Excel Error, and if so, returns the logical value TRUE; Otherwise the +// function returns FALSE. The syntax of the function is: +// +// ISERROR(value) +// +func (fn *formulaFuncs) ISERROR(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISERROR requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + result := false + if token.Type == ArgError { + for _, errType := range []string{ + formulaErrorDIV, formulaErrorNAME, formulaErrorNA, formulaErrorNUM, + formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL, + formulaErrorCALC, formulaErrorGETTINGDATA, + } { + if errType == token.String { + result = true + } + } + } + return newBoolFormulaArg(result) +} + +// ISEVEN function tests if a supplied number (or numeric expression) +// evaluates to an even number, and if so, returns TRUE; Otherwise, the +// function returns FALSE. The syntax of the function is: +// +// ISEVEN(value) +// +func (fn *formulaFuncs) ISEVEN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISEVEN requires 1 argument") + } + var ( + token = argsList.Front().Value.(formulaArg) + result = "FALSE" + numeric int + err error + ) + if token.Type == ArgString { + if numeric, err = strconv.Atoi(token.String); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + if numeric == numeric/2*2 { + return newStringFormulaArg("TRUE") + } + } + return newStringFormulaArg(result) +} + +// ISFORMULA function tests if a specified cell contains a formula, and if so, +// returns TRUE; Otherwise, the function returns FALSE. The syntax of the +// function is: +// +// ISFORMULA(reference) +// +func (fn *formulaFuncs) ISFORMULA(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISFORMULA requires 1 argument") + } + arg := argsList.Front().Value.(formulaArg) + if arg.cellRefs != nil && arg.cellRefs.Len() == 1 { + ref := arg.cellRefs.Front().Value.(cellRef) + cell, _ := CoordinatesToCellName(ref.Col, ref.Row) + if formula, _ := fn.f.GetCellFormula(ref.Sheet, cell); len(formula) > 0 { + return newBoolFormulaArg(true) + } + } + return newBoolFormulaArg(false) +} + +// ISLOGICAL function tests if a supplied value (or expression) returns a +// logical value (i.e. evaluates to True or False). If so, the function +// returns TRUE; Otherwise, it returns FALSE. The syntax of the function is: +// +// ISLOGICAL(value) +// +func (fn *formulaFuncs) ISLOGICAL(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISLOGICAL requires 1 argument") + } + val := argsList.Front().Value.(formulaArg).Value() + if strings.EqualFold("TRUE", val) || strings.EqualFold("FALSE", val) { + return newBoolFormulaArg(true) + } + return newBoolFormulaArg(false) +} + +// ISNA function tests if an initial supplied expression (or value) returns +// the Excel #N/A Error, and if so, returns TRUE; Otherwise the function +// returns FALSE. The syntax of the function is: +// +// ISNA(value) +// +func (fn *formulaFuncs) ISNA(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISNA requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + result := "FALSE" + if token.Type == ArgError && token.String == formulaErrorNA { + result = "TRUE" + } + return newStringFormulaArg(result) +} + +// ISNONTEXT function tests if a supplied value is text. If not, the +// function returns TRUE; If the supplied value is text, the function returns +// FALSE. The syntax of the function is: +// +// ISNONTEXT(value) +// +func (fn *formulaFuncs) ISNONTEXT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISNONTEXT requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + result := "TRUE" + if token.Type == ArgString && token.String != "" { + result = "FALSE" + } + return newStringFormulaArg(result) +} + +// ISNUMBER function tests if a supplied value is a number. If so, +// the function returns TRUE; Otherwise it returns FALSE. The syntax of the +// function is: +// +// ISNUMBER(value) +// +func (fn *formulaFuncs) ISNUMBER(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISNUMBER requires 1 argument") + } + token, result := argsList.Front().Value.(formulaArg), false + if token.Type == ArgString && token.String != "" { + if _, err := strconv.Atoi(token.String); err == nil { + result = true + } + } + return newBoolFormulaArg(result) +} + +// ISODD function tests if a supplied number (or numeric expression) evaluates +// to an odd number, and if so, returns TRUE; Otherwise, the function returns +// FALSE. The syntax of the function is: +// +// ISODD(value) +// +func (fn *formulaFuncs) ISODD(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISODD requires 1 argument") + } + var ( + token = argsList.Front().Value.(formulaArg) + result = "FALSE" + numeric int + err error + ) + if token.Type == ArgString { + if numeric, err = strconv.Atoi(token.String); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + if numeric != numeric/2*2 { + return newStringFormulaArg("TRUE") + } + } + return newStringFormulaArg(result) +} + +// ISREF function tests if a supplied value is a reference. If so, the +// function returns TRUE; Otherwise it returns FALSE. The syntax of the +// function is: +// +// ISREF(value) +// +func (fn *formulaFuncs) ISREF(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISREF requires 1 argument") + } + arg := argsList.Front().Value.(formulaArg) + if arg.cellRanges != nil && arg.cellRanges.Len() > 0 || arg.cellRefs != nil && arg.cellRefs.Len() > 0 { + return newBoolFormulaArg(true) + } + return newBoolFormulaArg(false) +} + +// ISTEXT function tests if a supplied value is text, and if so, returns TRUE; +// Otherwise, the function returns FALSE. The syntax of the function is: +// +// ISTEXT(value) +// +func (fn *formulaFuncs) ISTEXT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISTEXT requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + if token.ToNumber().Type != ArgError { + return newBoolFormulaArg(false) + } + return newBoolFormulaArg(token.Type == ArgString) +} + +// N function converts data into a numeric value. The syntax of the function +// is: +// +// N(value) +// +func (fn *formulaFuncs) N(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "N requires 1 argument") + } + token, num := argsList.Front().Value.(formulaArg), 0.0 + if token.Type == ArgError { + return token + } + if arg := token.ToNumber(); arg.Type == ArgNumber { + num = arg.Number + } + if token.Value() == "TRUE" { + num = 1 + } + return newNumberFormulaArg(num) +} + +// NA function returns the Excel #N/A error. This error message has the +// meaning 'value not available' and is produced when an Excel Formula is +// unable to find a value that it needs. The syntax of the function is: +// +// NA() +// +func (fn *formulaFuncs) NA(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "NA accepts no arguments") + } + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) +} + +// SHEET function returns the Sheet number for a specified reference. The +// syntax of the function is: +// +// SHEET([value]) +// +func (fn *formulaFuncs) SHEET(argsList *list.List) formulaArg { + if argsList.Len() > 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SHEET accepts at most 1 argument") + } + if argsList.Len() == 0 { + return newNumberFormulaArg(float64(fn.f.GetSheetIndex(fn.sheet) + 1)) + } + arg := argsList.Front().Value.(formulaArg) + if sheetIdx := fn.f.GetSheetIndex(arg.Value()); sheetIdx != -1 { + return newNumberFormulaArg(float64(sheetIdx + 1)) + } + if arg.cellRanges != nil && arg.cellRanges.Len() > 0 { + if sheetIdx := fn.f.GetSheetIndex(arg.cellRanges.Front().Value.(cellRange).From.Sheet); sheetIdx != -1 { + return newNumberFormulaArg(float64(sheetIdx + 1)) + } + } + if arg.cellRefs != nil && arg.cellRefs.Len() > 0 { + if sheetIdx := fn.f.GetSheetIndex(arg.cellRefs.Front().Value.(cellRef).Sheet); sheetIdx != -1 { + return newNumberFormulaArg(float64(sheetIdx + 1)) + } + } + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) +} + +// SHEETS function returns the number of sheets in a supplied reference. The +// result includes sheets that are Visible, Hidden or Very Hidden. The syntax +// of the function is: +// +// SHEETS([reference]) +// +func (fn *formulaFuncs) SHEETS(argsList *list.List) formulaArg { + if argsList.Len() > 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SHEETS accepts at most 1 argument") + } + if argsList.Len() == 0 { + return newNumberFormulaArg(float64(len(fn.f.GetSheetList()))) + } + arg := argsList.Front().Value.(formulaArg) + sheetMap := map[string]struct{}{} + if arg.cellRanges != nil && arg.cellRanges.Len() > 0 { + for rng := arg.cellRanges.Front(); rng != nil; rng = rng.Next() { + sheetMap[rng.Value.(cellRange).From.Sheet] = struct{}{} + } + } + if arg.cellRefs != nil && arg.cellRefs.Len() > 0 { + for ref := arg.cellRefs.Front(); ref != nil; ref = ref.Next() { + sheetMap[ref.Value.(cellRef).Sheet] = struct{}{} + } + } + if len(sheetMap) > 0 { + return newNumberFormulaArg(float64(len(sheetMap))) + } + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) +} + +// TYPE function returns an integer that represents the value's data type. The +// syntax of the function is: +// +// TYPE(value) +// +func (fn *formulaFuncs) TYPE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "TYPE requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + switch token.Type { + case ArgError: + return newNumberFormulaArg(16) + case ArgMatrix: + return newNumberFormulaArg(64) + default: + if arg := token.ToNumber(); arg.Type != ArgError || len(token.Value()) == 0 { + return newNumberFormulaArg(1) + } + if arg := token.ToBool(); arg.Type != ArgError { + return newNumberFormulaArg(4) + } + return newNumberFormulaArg(2) + } +} + +// T function tests if a supplied value is text and if so, returns the +// supplied text; Otherwise, the function returns an empty text string. The +// syntax of the function is: +// +// T(value) +// +func (fn *formulaFuncs) T(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "T requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + if token.Type == ArgError { + return token + } + if token.Type == ArgNumber { + return newStringFormulaArg("") + } + return newStringFormulaArg(token.Value()) +} + +// Logical Functions + +// AND function tests a number of supplied conditions and returns TRUE or +// FALSE. The syntax of the function is: +// +// AND(logical_test1,[logical_test2],...) +// +func (fn *formulaFuncs) AND(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "AND requires at least 1 argument") + } + if argsList.Len() > 30 { + return newErrorFormulaArg(formulaErrorVALUE, "AND accepts at most 30 arguments") + } + var ( + and = true + val float64 + err error + ) + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgUnknown: + continue + case ArgString: + if token.String == "TRUE" { + continue + } + if token.String == "FALSE" { + return newStringFormulaArg(token.String) + } + if val, err = strconv.ParseFloat(token.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + and = and && (val != 0) + case ArgMatrix: + // TODO + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + return newBoolFormulaArg(and) +} + +// FALSE function returns the logical value FALSE. The syntax of the +// function is: +// +// FALSE() +// +func (fn *formulaFuncs) FALSE(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "FALSE takes no arguments") + } + return newBoolFormulaArg(false) +} + +// IFERROR function receives two values (or expressions) and tests if the +// first of these evaluates to an error. The syntax of the function is: +// +// IFERROR(value,value_if_error) +// +func (fn *formulaFuncs) IFERROR(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IFERROR requires 2 arguments") + } + value := argsList.Front().Value.(formulaArg) + if value.Type != ArgError { + if value.Type == ArgEmpty { + return newNumberFormulaArg(0) + } + return value + } + return argsList.Back().Value.(formulaArg) +} + +// IFNA function tests if an initial supplied value (or expression) evaluates +// to the Excel #N/A error. If so, the function returns a second supplied +// value; Otherwise the function returns the first supplied value. The syntax +// of the function is: +// +// IFNA(value,value_if_na) +// +func (fn *formulaFuncs) IFNA(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IFNA requires 2 arguments") + } + arg := argsList.Front().Value.(formulaArg) + if arg.Type == ArgError && arg.Value() == formulaErrorNA { + return argsList.Back().Value.(formulaArg) + } + return arg +} + +// IFS function tests a number of supplied conditions and returns the result +// corresponding to the first condition that evaluates to TRUE. If none of +// the supplied conditions evaluate to TRUE, the function returns the #N/A +// error. +// +// IFS(logical_test1,value_if_true1,[logical_test2,value_if_true2],...) +// +func (fn *formulaFuncs) IFS(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IFS requires at least 2 arguments") + } + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + if arg.Value.(formulaArg).ToBool().Number == 1 { + return arg.Next().Value.(formulaArg) + } + arg = arg.Next() + } + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) +} + +// NOT function returns the opposite to a supplied logical value. The syntax +// of the function is: +// +// NOT(logical) +// +func (fn *formulaFuncs) NOT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "NOT requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + switch token.Type { + case ArgString, ArgList: + if strings.ToUpper(token.String) == "TRUE" { + return newBoolFormulaArg(false) + } + if strings.ToUpper(token.String) == "FALSE" { + return newBoolFormulaArg(true) + } + case ArgNumber: + return newBoolFormulaArg(!(token.Number != 0)) + case ArgError: + return token + } + return newErrorFormulaArg(formulaErrorVALUE, "NOT expects 1 boolean or numeric argument") +} + +// OR function tests a number of supplied conditions and returns either TRUE +// or FALSE. The syntax of the function is: +// +// OR(logical_test1,[logical_test2],...) +// +func (fn *formulaFuncs) OR(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "OR requires at least 1 argument") + } + if argsList.Len() > 30 { + return newErrorFormulaArg(formulaErrorVALUE, "OR accepts at most 30 arguments") + } + var ( + or bool + val float64 + err error + ) + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgUnknown: + continue + case ArgString: + if token.String == "FALSE" { + continue + } + if token.String == "TRUE" { + or = true + continue + } + if val, err = strconv.ParseFloat(token.String, 64); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + or = val != 0 + case ArgMatrix: + // TODO + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or))) +} + +// SWITCH function compares a number of supplied values to a supplied test +// expression and returns a result corresponding to the first value that +// matches the test expression. A default value can be supplied, to be +// returned if none of the supplied values match the test expression. The +// syntax of the function is: +// +// +// SWITCH(expression,value1,result1,[value2,result2],[value3,result3],...,[default]) +// +func (fn *formulaFuncs) SWITCH(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "SWITCH requires at least 3 arguments") + } + target := argsList.Front().Value.(formulaArg) + argCount := argsList.Len() - 1 + switchCount := int(math.Floor(float64(argCount) / 2)) + hasDefaultClause := argCount%2 != 0 + result := newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + if hasDefaultClause { + result = argsList.Back().Value.(formulaArg) + } + if switchCount > 0 { + arg := argsList.Front() + for i := 0; i < switchCount; i++ { + arg = arg.Next() + if target.Value() == arg.Value.(formulaArg).Value() { + result = arg.Next().Value.(formulaArg) + break + } + arg = arg.Next() + } + } + return result +} + +// TRUE function returns the logical value TRUE. The syntax of the function +// is: +// +// TRUE() +// +func (fn *formulaFuncs) TRUE(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "TRUE takes no arguments") + } + return newBoolFormulaArg(true) +} + +// calcXor checking if numeric cell exists and count it by given arguments +// sequence for the formula function XOR. +func calcXor(argsList *list.List) formulaArg { + count, ok := 0, false + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgError: + return token + case ArgString: + if b := token.ToBool(); b.Type == ArgNumber { + ok = true + if b.Number == 1 { + count++ + } + continue + } + if num := token.ToNumber(); num.Type == ArgNumber { + ok = true + if num.Number != 0 { + count++ + } + } + case ArgNumber: + ok = true + if token.Number != 0 { + count++ + } + case ArgMatrix: + for _, value := range token.ToList() { + if num := value.ToNumber(); num.Type == ArgNumber { + ok = true + if num.Number != 0 { + count++ + } + } + } + } + } + if !ok { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return newBoolFormulaArg(count%2 != 0) +} + +// XOR function returns the Exclusive Or logical operation for one or more +// supplied conditions. I.e. the Xor function returns TRUE if an odd number +// of the supplied conditions evaluate to TRUE, and FALSE otherwise. The +// syntax of the function is: +// +// XOR(logical_test1,[logical_test2],...) +// +func (fn *formulaFuncs) XOR(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "XOR requires at least 1 argument") + } + return calcXor(argsList) +} + +// Date and Time Functions + +// DATE returns a date, from a user-supplied year, month and day. The syntax +// of the function is: +// +// DATE(year,month,day) +// +func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments") + } + year := argsList.Front().Value.(formulaArg).ToNumber() + month := argsList.Front().Next().Value.(formulaArg).ToNumber() + day := argsList.Back().Value.(formulaArg).ToNumber() + if year.Type != ArgNumber || month.Type != ArgNumber || day.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments") + } + d := makeDate(int(year.Number), time.Month(month.Number), int(day.Number)) + return newStringFormulaArg(timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), d)+1, false).String()) +} + +// calcDateDif is an implementation of the formula function DATEDIF, +// calculation difference between two dates. +func calcDateDif(unit string, diff float64, seq []int, startArg, endArg formulaArg) float64 { + ey, sy, em, sm, ed, sd := seq[0], seq[1], seq[2], seq[3], seq[4], seq[5] + switch unit { + case "d": + diff = endArg.Number - startArg.Number + case "md": + smMD := em + if ed < sd { + smMD-- + } + diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1 + case "ym": + diff = float64(em - sm) + if ed < sd { + diff-- + } + if diff < 0 { + diff += 12 + } + case "yd": + syYD := sy + if em < sm || (em == sm && ed < sd) { + syYD++ + } + s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed)) + e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd)) + diff = s - e + } + return diff +} + +// DATEDIF function calculates the number of days, months, or years between +// two dates. The syntax of the function is: +// +// DATEDIF(start_date,end_date,unit) +// +func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF requires 3 number arguments") + } + startArg, endArg := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber() + if startArg.Type != ArgNumber || endArg.Type != ArgNumber { + return startArg + } + if startArg.Number > endArg.Number { + return newErrorFormulaArg(formulaErrorNUM, "start_date > end_date") + } + if startArg.Number == endArg.Number { + return newNumberFormulaArg(0) + } + unit := strings.ToLower(argsList.Back().Value.(formulaArg).Value()) + startDate, endDate := timeFromExcelTime(startArg.Number, false), timeFromExcelTime(endArg.Number, false) + sy, smm, sd := startDate.Date() + ey, emm, ed := endDate.Date() + sm, em, diff := int(smm), int(emm), 0.0 + switch unit { + case "y": + diff = float64(ey - sy) + if em < sm || (em == sm && ed < sd) { + diff-- + } + case "m": + yDiff := ey - sy + mDiff := em - sm + if ed < sd { + mDiff-- + } + if mDiff < 0 { + yDiff-- + mDiff += 12 + } + diff = float64(yDiff*12 + mDiff) + case "d", "md", "ym", "yd": + diff = calcDateDif(unit, diff, []int{ey, sy, em, sm, ed, sd}, startArg, endArg) + default: + return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit") + } + return newNumberFormulaArg(diff) +} + +// isDateOnlyFmt check if the given string matches date-only format regular expressions. +func isDateOnlyFmt(dateString string) bool { + for _, df := range dateOnlyFormats { + subMatch := df.FindStringSubmatch(dateString) + if len(subMatch) > 1 { + return true + } + } + return false +} + +// isTimeOnlyFmt check if the given string matches time-only format regular expressions. +func isTimeOnlyFmt(timeString string) bool { + for _, tf := range timeFormats { + subMatch := tf.FindStringSubmatch(timeString) + if len(subMatch) > 1 { + return true + } + } + return false +} + +// strToTimePatternHandler1 parse and convert the given string in pattern +// hh to the time. +func strToTimePatternHandler1(subMatch []string) (h, m int, s float64, err error) { + h, err = strconv.Atoi(subMatch[0]) + return +} + +// strToTimePatternHandler2 parse and convert the given string in pattern +// hh:mm to the time. +func strToTimePatternHandler2(subMatch []string) (h, m int, s float64, err error) { + if h, err = strconv.Atoi(subMatch[0]); err != nil { + return + } + m, err = strconv.Atoi(subMatch[2]) + return +} + +// strToTimePatternHandler3 parse and convert the given string in pattern +// mm:ss to the time. +func strToTimePatternHandler3(subMatch []string) (h, m int, s float64, err error) { + if m, err = strconv.Atoi(subMatch[0]); err != nil { + return + } + s, err = strconv.ParseFloat(subMatch[2], 64) + return +} + +// strToTimePatternHandler4 parse and convert the given string in pattern +// hh:mm:ss to the time. +func strToTimePatternHandler4(subMatch []string) (h, m int, s float64, err error) { + if h, err = strconv.Atoi(subMatch[0]); err != nil { + return + } + if m, err = strconv.Atoi(subMatch[2]); err != nil { + return + } + s, err = strconv.ParseFloat(subMatch[4], 64) + return +} + +// strToTime parse and convert the given string to the time. +func strToTime(str string) (int, int, float64, bool, bool, formulaArg) { + var subMatch []string + pattern := "" + for key, tf := range timeFormats { + subMatch = tf.FindStringSubmatch(str) + if len(subMatch) > 1 { + pattern = key + break + } + } + if pattern == "" { + return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + dateIsEmpty := subMatch[1] == "" + subMatch = subMatch[49:] + var ( + l = len(subMatch) + last = subMatch[l-1] + am = last == "am" + pm = last == "pm" + hours, minutes int + seconds float64 + err error + ) + if handler, ok := map[string]func(match []string) (int, int, float64, error){ + "hh": strToTimePatternHandler1, + "hh:mm": strToTimePatternHandler2, + "mm:ss": strToTimePatternHandler3, + "hh:mm:ss": strToTimePatternHandler4, + }[pattern]; ok { + if hours, minutes, seconds, err = handler(subMatch); err != nil { + return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + if minutes >= 60 { + return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if am || pm { + if hours > 12 || seconds >= 60 { + return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } else if hours == 12 { + hours = 0 + } + } else if hours >= 24 || seconds >= 10000 { + return 0, 0, 0, false, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return hours, minutes, seconds, pm, dateIsEmpty, newEmptyFormulaArg() +} + +// strToDatePatternHandler1 parse and convert the given string in pattern +// mm/dd/yy to the date. +func strToDatePatternHandler1(subMatch []string) (int, int, int, bool, error) { + var year, month, day int + var err error + if month, err = strconv.Atoi(subMatch[1]); err != nil { + return 0, 0, 0, false, err + } + if day, err = strconv.Atoi(subMatch[3]); err != nil { + return 0, 0, 0, false, err + } + if year, err = strconv.Atoi(subMatch[5]); err != nil { + return 0, 0, 0, false, err + } + if year < 0 || year > 9999 || (year > 99 && year < 1900) { + return 0, 0, 0, false, ErrParameterInvalid + } + return formatYear(year), month, day, subMatch[8] == "", err +} + +// strToDatePatternHandler2 parse and convert the given string in pattern mm +// dd, yy to the date. +func strToDatePatternHandler2(subMatch []string) (int, int, int, bool, error) { + var year, month, day int + var err error + month = month2num[subMatch[1]] + if day, err = strconv.Atoi(subMatch[14]); err != nil { + return 0, 0, 0, false, err + } + if year, err = strconv.Atoi(subMatch[16]); err != nil { + return 0, 0, 0, false, err + } + if year < 0 || year > 9999 || (year > 99 && year < 1900) { + return 0, 0, 0, false, ErrParameterInvalid + } + return formatYear(year), month, day, subMatch[19] == "", err +} + +// strToDatePatternHandler3 parse and convert the given string in pattern +// yy-mm-dd to the date. +func strToDatePatternHandler3(subMatch []string) (int, int, int, bool, error) { + var year, month, day int + v1, err := strconv.Atoi(subMatch[1]) + if err != nil { + return 0, 0, 0, false, err + } + v2, err := strconv.Atoi(subMatch[3]) + if err != nil { + return 0, 0, 0, false, err + } + v3, err := strconv.Atoi(subMatch[5]) + if err != nil { + return 0, 0, 0, false, err + } + if v1 >= 1900 && v1 < 10000 { + year = v1 + month = v2 + day = v3 + } else if v1 > 0 && v1 < 13 { + month = v1 + day = v2 + year = v3 + } else { + return 0, 0, 0, false, ErrParameterInvalid + } + return year, month, day, subMatch[8] == "", err +} + +// strToDatePatternHandler4 parse and convert the given string in pattern +// yy-mmStr-dd, yy to the date. +func strToDatePatternHandler4(subMatch []string) (int, int, int, bool, error) { + var year, month, day int + var err error + if year, err = strconv.Atoi(subMatch[16]); err != nil { + return 0, 0, 0, false, err + } + month = month2num[subMatch[3]] + if day, err = strconv.Atoi(subMatch[1]); err != nil { + return 0, 0, 0, false, err + } + return formatYear(year), month, day, subMatch[19] == "", err +} + +// strToDate parse and convert the given string to the date. +func strToDate(str string) (int, int, int, bool, formulaArg) { + var subMatch []string + pattern := "" + for key, df := range dateFormats { + subMatch = df.FindStringSubmatch(str) + if len(subMatch) > 1 { + pattern = key + break + } + } + if pattern == "" { + return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + var ( + timeIsEmpty bool + year, month, day int + err error + ) + if handler, ok := map[string]func(match []string) (int, int, int, bool, error){ + "mm/dd/yy": strToDatePatternHandler1, + "mm dd, yy": strToDatePatternHandler2, + "yy-mm-dd": strToDatePatternHandler3, + "yy-mmStr-dd": strToDatePatternHandler4, + }[pattern]; ok { + if year, month, day, timeIsEmpty, err = handler(subMatch); err != nil { + return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + if !validateDate(year, month, day) { + return 0, 0, 0, false, newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return year, month, day, timeIsEmpty, newEmptyFormulaArg() +} + +// DATEVALUE function converts a text representation of a date into an Excel +// date. For example, the function converts a text string representing a +// date, into the serial number that represents the date in Excels' date-time +// code. The syntax of the function is: +// +// DATEVALUE(date_text) +// +func (fn *formulaFuncs) DATEVALUE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DATEVALUE requires 1 argument") + } + dateText := argsList.Front().Value.(formulaArg).Value() + if !isDateOnlyFmt(dateText) { + if _, _, _, _, _, err := strToTime(dateText); err.Type == ArgError { + return err + } + } + y, m, d, _, err := strToDate(dateText) + if err.Type == ArgError { + return err + } + return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(y, time.Month(m), d)) + 1) +} + +// DAY function returns the day of a date, represented by a serial number. The +// day is given as an integer ranging from 1 to 31. The syntax of the +// function is: +// +// DAY(serial_number) +// +func (fn *formulaFuncs) DAY(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DAY requires exactly 1 argument") + } + arg := argsList.Front().Value.(formulaArg) + num := arg.ToNumber() + if num.Type != ArgNumber { + dateString := strings.ToLower(arg.Value()) + if !isDateOnlyFmt(dateString) { + if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { + return err + } + } + _, _, day, _, err := strToDate(dateString) + if err.Type == ArgError { + return err + } + return newNumberFormulaArg(float64(day)) + } + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "DAY only accepts positive argument") + } + if num.Number <= 60 { + return newNumberFormulaArg(math.Mod(num.Number, 31.0)) + } + return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Day())) +} + +// DAYS function returns the number of days between two supplied dates. The +// syntax of the function is: +// +// DAYS(end_date,start_date) +// +func (fn *formulaFuncs) DAYS(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "DAYS requires 2 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + end, start := args.List[0], args.List[1] + return newNumberFormulaArg(end.Number - start.Number) +} + +// ISOWEEKNUM function returns the ISO week number of a supplied date. The +// syntax of the function is: +// +// ISOWEEKNUM(date) +// +func (fn *formulaFuncs) ISOWEEKNUM(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISOWEEKNUM requires 1 argument") + } + date := argsList.Front().Value.(formulaArg) + num := date.ToNumber() + weekNum := 0 + if num.Type != ArgNumber { + dateString := strings.ToLower(date.Value()) + if !isDateOnlyFmt(dateString) { + if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { + return err + } + } + y, m, d, _, err := strToDate(dateString) + if err.Type == ArgError { + return err + } + _, weekNum = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC).ISOWeek() + } else { + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + _, weekNum = timeFromExcelTime(num.Number, false).ISOWeek() + } + return newNumberFormulaArg(float64(weekNum)) +} + +// HOUR function returns an integer representing the hour component of a +// supplied Excel time. The syntax of the function is: +// +// HOUR(serial_number) +// +func (fn *formulaFuncs) HOUR(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "HOUR requires exactly 1 argument") + } + date := argsList.Front().Value.(formulaArg) + num := date.ToNumber() + if num.Type != ArgNumber { + timeString := strings.ToLower(date.Value()) + if !isTimeOnlyFmt(timeString) { + _, _, _, _, err := strToDate(timeString) + if err.Type == ArgError { + return err + } + } + h, _, _, pm, _, err := strToTime(timeString) + if err.Type == ArgError { + return err + } + if pm { + h += 12 + } + return newNumberFormulaArg(float64(h)) + } + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "HOUR only accepts positive argument") + } + return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Hour())) +} + +// MINUTE function returns an integer representing the minute component of a +// supplied Excel time. The syntax of the function is: +// +// MINUTE(serial_number) +// +func (fn *formulaFuncs) MINUTE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "MINUTE requires exactly 1 argument") + } + date := argsList.Front().Value.(formulaArg) + num := date.ToNumber() + if num.Type != ArgNumber { + timeString := strings.ToLower(date.Value()) + if !isTimeOnlyFmt(timeString) { + _, _, _, _, err := strToDate(timeString) + if err.Type == ArgError { + return err + } + } + _, m, _, _, _, err := strToTime(timeString) + if err.Type == ArgError { + return err + } + return newNumberFormulaArg(float64(m)) + } + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "MINUTE only accepts positive argument") + } + return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Minute())) +} + +// MONTH function returns the month of a date represented by a serial number. +// The month is given as an integer, ranging from 1 (January) to 12 +// (December). The syntax of the function is: +// +// MONTH(serial_number) +// +func (fn *formulaFuncs) MONTH(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "MONTH requires exactly 1 argument") + } + arg := argsList.Front().Value.(formulaArg) + num := arg.ToNumber() + if num.Type != ArgNumber { + dateString := strings.ToLower(arg.Value()) + if !isDateOnlyFmt(dateString) { + if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { + return err + } + } + _, month, _, _, err := strToDate(dateString) + if err.Type == ArgError { + return err + } + return newNumberFormulaArg(float64(month)) + } + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "MONTH only accepts positive argument") + } + return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Month())) +} + +// YEAR function returns an integer representing the year of a supplied date. +// The syntax of the function is: +// +// YEAR(serial_number) +// +func (fn *formulaFuncs) YEAR(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "YEAR requires exactly 1 argument") + } + arg := argsList.Front().Value.(formulaArg) + num := arg.ToNumber() + if num.Type != ArgNumber { + dateString := strings.ToLower(arg.Value()) + if !isDateOnlyFmt(dateString) { + if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { + return err + } + } + year, _, _, _, err := strToDate(dateString) + if err.Type == ArgError { + return err + } + return newNumberFormulaArg(float64(year)) + } + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "YEAR only accepts positive argument") + } + return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Year())) +} + +// yearFracBasisCond is an implementation of the yearFracBasis1. +func yearFracBasisCond(sy, sm, sd, ey, em, ed int) bool { + return (isLeapYear(sy) && (sm < 2 || (sm == 2 && sd <= 29))) || (isLeapYear(ey) && (em > 2 || (em == 2 && ed == 29))) +} + +// yearFracBasis0 function returns the fraction of a year that between two +// supplied dates in US (NASD) 30/360 type of day. +func yearFracBasis0(startDate, endDate float64) (dayDiff, daysInYear float64) { + startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) + sy, smM, sd := startTime.Date() + ey, emM, ed := endTime.Date() + sm, em := int(smM), int(emM) + if sd == 31 { + sd-- + } + if sd == 30 && ed == 31 { + ed-- + } else if leap := isLeapYear(sy); sm == 2 && ((leap && sd == 29) || (!leap && sd == 28)) { + sd = 30 + if leap := isLeapYear(ey); em == 2 && ((leap && ed == 29) || (!leap && ed == 28)) { + ed = 30 + } + } + dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd)) + daysInYear = 360 + return +} + +// yearFracBasis1 function returns the fraction of a year that between two +// supplied dates in actual type of day. +func yearFracBasis1(startDate, endDate float64) (dayDiff, daysInYear float64) { + startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) + sy, smM, sd := startTime.Date() + ey, emM, ed := endTime.Date() + sm, em := int(smM), int(emM) + dayDiff = endDate - startDate + isYearDifferent := sy != ey + if isYearDifferent && (ey != sy+1 || sm < em || (sm == em && sd < ed)) { + dayCount := 0 + for y := sy; y <= ey; y++ { + dayCount += getYearDays(y, 1) + } + daysInYear = float64(dayCount) / float64(ey-sy+1) + } else { + if !isYearDifferent && isLeapYear(sy) { + daysInYear = 366 + } else { + if isYearDifferent && yearFracBasisCond(sy, sm, sd, ey, em, ed) { + daysInYear = 366 + } else { + daysInYear = 365 + } + } + } + return +} + +// yearFracBasis4 function returns the fraction of a year that between two +// supplied dates in European 30/360 type of day. +func yearFracBasis4(startDate, endDate float64) (dayDiff, daysInYear float64) { + startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) + sy, smM, sd := startTime.Date() + ey, emM, ed := endTime.Date() + sm, em := int(smM), int(emM) + if sd == 31 { + sd-- + } + if ed == 31 { + ed-- + } + dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd)) + daysInYear = 360 + return +} + +// yearFrac is an implementation of the formula function YEARFRAC. +func yearFrac(startDate, endDate float64, basis int) formulaArg { + startTime, endTime := timeFromExcelTime(startDate, false), timeFromExcelTime(endDate, false) + if startTime == endTime { + return newNumberFormulaArg(0) + } + var dayDiff, daysInYear float64 + switch basis { + case 0: + dayDiff, daysInYear = yearFracBasis0(startDate, endDate) + case 1: + dayDiff, daysInYear = yearFracBasis1(startDate, endDate) + case 2: + dayDiff = endDate - startDate + daysInYear = 360 + case 3: + dayDiff = endDate - startDate + daysInYear = 365 + case 4: + dayDiff, daysInYear = yearFracBasis4(startDate, endDate) + default: + return newErrorFormulaArg(formulaErrorNUM, "invalid basis") + } + return newNumberFormulaArg(dayDiff / daysInYear) +} + +// getYearDays return days of the year with specifying the type of day count +// basis to be used. +func getYearDays(year, basis int) int { + switch basis { + case 1: + if isLeapYear(year) { + return 366 + } + return 365 + case 3: + return 365 + default: + return 360 + } +} + +// YEARFRAC function returns the fraction of a year that is represented by the +// number of whole days between two supplied dates. The syntax of the +// function is: +// +// YEARFRAC(start_date,end_date,[basis]) +// +func (fn *formulaFuncs) YEARFRAC(argsList *list.List) formulaArg { + if argsList.Len() != 2 && argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "YEARFRAC requires 3 or 4 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + start, end := args.List[0], args.List[1] + basis := newNumberFormulaArg(0) + if argsList.Len() == 3 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return basis + } + } + return yearFrac(start.Number, end.Number, int(basis.Number)) +} + +// NOW function returns the current date and time. The function receives no +// arguments and therefore. The syntax of the function is: +// +// NOW() +// +func (fn *formulaFuncs) NOW(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "NOW accepts no arguments") + } + now := time.Now() + _, offset := now.Zone() + return newNumberFormulaArg(25569.0 + float64(now.Unix()+int64(offset))/86400) +} + +// SECOND function returns an integer representing the second component of a +// supplied Excel time. The syntax of the function is: +// +// SECOND(serial_number) +// +func (fn *formulaFuncs) SECOND(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "SECOND requires exactly 1 argument") + } + date := argsList.Front().Value.(formulaArg) + num := date.ToNumber() + if num.Type != ArgNumber { + timeString := strings.ToLower(date.Value()) + if !isTimeOnlyFmt(timeString) { + _, _, _, _, err := strToDate(timeString) + if err.Type == ArgError { + return err + } + } + _, _, s, _, _, err := strToTime(timeString) + if err.Type == ArgError { + return err + } + return newNumberFormulaArg(float64(int(s) % 60)) + } + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "SECOND only accepts positive argument") + } + return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Second())) +} + +// TIME function accepts three integer arguments representing hours, minutes +// and seconds, and returns an Excel time. I.e. the function returns the +// decimal value that represents the time in Excel. The syntax of the +// function is: +// +// TIME(hour,minute,second) +// +func (fn *formulaFuncs) TIME(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "TIME requires 3 number arguments") + } + h := argsList.Front().Value.(formulaArg).ToNumber() + m := argsList.Front().Next().Value.(formulaArg).ToNumber() + s := argsList.Back().Value.(formulaArg).ToNumber() + if h.Type != ArgNumber || m.Type != ArgNumber || s.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, "TIME requires 3 number arguments") + } + t := (h.Number*3600 + m.Number*60 + s.Number) / 86400 + if t < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(t) +} + +// TIMEVALUE function converts a text representation of a time, into an Excel +// time. The syntax of the function is: +// +// TIMEVALUE(time_text) +// +func (fn *formulaFuncs) TIMEVALUE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "TIMEVALUE requires exactly 1 argument") + } + date := argsList.Front().Value.(formulaArg) + timeString := strings.ToLower(date.Value()) + if !isTimeOnlyFmt(timeString) { + _, _, _, _, err := strToDate(timeString) + if err.Type == ArgError { + return err + } + } + h, m, s, pm, _, err := strToTime(timeString) + if err.Type == ArgError { + return err + } + if pm { + h += 12 + } + args := list.New() + args.PushBack(newNumberFormulaArg(float64(h))) + args.PushBack(newNumberFormulaArg(float64(m))) + args.PushBack(newNumberFormulaArg(s)) + return fn.TIME(args) +} + +// TODAY function returns the current date. The function has no arguments and +// therefore. The syntax of the function is: +// +// TODAY() +// +func (fn *formulaFuncs) TODAY(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "TODAY accepts no arguments") + } + now := time.Now() + _, offset := now.Zone() + return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), now.Unix()+int64(offset)) + 1) +} + +// makeDate return date as a Unix time, the number of seconds elapsed since +// January 1, 1970 UTC. +func makeDate(y int, m time.Month, d int) int64 { + if y == 1900 && int(m) <= 2 { + d-- + } + date := time.Date(y, m, d, 0, 0, 0, 0, time.UTC) + return date.Unix() +} + +// daysBetween return time interval of the given start timestamp and end +// timestamp. +func daysBetween(startDate, endDate int64) float64 { + return float64(int(0.5 + float64((endDate-startDate)/86400))) +} + +// WEEKDAY function returns an integer representing the day of the week for a +// supplied date. The syntax of the function is: +// +// WEEKDAY(serial_number,[return_type]) +// +func (fn *formulaFuncs) WEEKDAY(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "WEEKDAY requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "WEEKDAY allows at most 2 arguments") + } + sn := argsList.Front().Value.(formulaArg) + num := sn.ToNumber() + weekday, returnType := 0, 1 + if num.Type != ArgNumber { + dateString := strings.ToLower(sn.Value()) + if !isDateOnlyFmt(dateString) { + if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { + return err + } + } + y, m, d, _, err := strToDate(dateString) + if err.Type == ArgError { + return err + } + weekday = int(time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Now().Location()).Weekday()) + } else { + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + weekday = int(timeFromExcelTime(num.Number, false).Weekday()) + } + if argsList.Len() == 2 { + returnTypeArg := argsList.Back().Value.(formulaArg).ToNumber() + if returnTypeArg.Type != ArgNumber { + return returnTypeArg + } + returnType = int(returnTypeArg.Number) + } + if returnType == 2 { + returnType = 11 + } + weekday++ + if returnType == 1 { + return newNumberFormulaArg(float64(weekday)) + } + if returnType == 3 { + return newNumberFormulaArg(float64((weekday + 6 - 1) % 7)) + } + if returnType >= 11 && returnType <= 17 { + return newNumberFormulaArg(float64((weekday+6-(returnType-10))%7 + 1)) + } + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) +} + +// Text Functions + +// CHAR function returns the character relating to a supplied character set +// number (from 1 to 255). syntax of the function is: +// +// CHAR(number) +// +func (fn *formulaFuncs) CHAR(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "CHAR requires 1 argument") + } + arg := argsList.Front().Value.(formulaArg).ToNumber() + if arg.Type != ArgNumber { + return arg + } + num := int(arg.Number) + if num < 0 || num > MaxFieldLength { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return newStringFormulaArg(fmt.Sprintf("%c", num)) +} + +// CLEAN removes all non-printable characters from a supplied text string. The +// syntax of the function is: +// +// CLEAN(text) +// +func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "CLEAN requires 1 argument") + } + b := bytes.Buffer{} + for _, c := range argsList.Front().Value.(formulaArg).String { + if c > 31 { + b.WriteRune(c) + } + } + return newStringFormulaArg(b.String()) +} + +// CODE function converts the first character of a supplied text string into +// the associated numeric character set code used by your computer. The +// syntax of the function is: +// +// CODE(text) +// +func (fn *formulaFuncs) CODE(argsList *list.List) formulaArg { + return fn.code("CODE", argsList) +} + +// code is an implementation of the formula functions CODE and UNICODE. +func (fn *formulaFuncs) code(name string, argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name)) + } + text := argsList.Front().Value.(formulaArg).Value() + if len(text) == 0 { + if name == "CODE" { + return newNumberFormulaArg(0) + } + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return newNumberFormulaArg(float64(text[0])) +} + +// CONCAT function joins together a series of supplied text strings into one +// combined text string. +// +// CONCAT(text1,[text2],...) +// +func (fn *formulaFuncs) CONCAT(argsList *list.List) formulaArg { + return fn.concat("CONCAT", argsList) +} + +// CONCATENATE function joins together a series of supplied text strings into +// one combined text string. +// +// CONCATENATE(text1,[text2],...) +// +func (fn *formulaFuncs) CONCATENATE(argsList *list.List) formulaArg { + return fn.concat("CONCATENATE", argsList) +} + +// concat is an implementation of the formula functions CONCAT and +// CONCATENATE. +func (fn *formulaFuncs) concat(name string, argsList *list.List) formulaArg { + buf := bytes.Buffer{} + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + token := arg.Value.(formulaArg) + switch token.Type { + case ArgString: + buf.WriteString(token.String) + case ArgNumber: + if token.Boolean { + if token.Number == 0 { + buf.WriteString("FALSE") + } else { + buf.WriteString("TRUE") + } + } else { + buf.WriteString(token.Value()) + } + default: + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires arguments to be strings", name)) + } + } + return newStringFormulaArg(buf.String()) +} + +// EXACT function tests if two supplied text strings or values are exactly +// equal and if so, returns TRUE; Otherwise, the function returns FALSE. The +// function is case-sensitive. The syntax of the function is: +// +// EXACT(text1,text2) +// +func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "EXACT requires 2 arguments") + } + text1 := argsList.Front().Value.(formulaArg).Value() + text2 := argsList.Back().Value.(formulaArg).Value() + return newBoolFormulaArg(text1 == text2) +} + +// FIXED function rounds a supplied number to a specified number of decimal +// places and then converts this into text. The syntax of the function is: +// +// FIXED(number,[decimals],[no_commas]) +// +func (fn *formulaFuncs) FIXED(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "FIXED requires at least 1 argument") + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "FIXED allows at most 3 arguments") + } + numArg := argsList.Front().Value.(formulaArg).ToNumber() + if numArg.Type != ArgNumber { + return numArg + } + precision, decimals, noCommas := 0, 0, false + s := strings.Split(argsList.Front().Value.(formulaArg).Value(), ".") + if argsList.Len() == 1 && len(s) == 2 { + precision = len(s[1]) + decimals = len(s[1]) + } + if argsList.Len() >= 2 { + decimalsArg := argsList.Front().Next().Value.(formulaArg).ToNumber() + if decimalsArg.Type != ArgNumber { + return decimalsArg + } + decimals = int(decimalsArg.Number) + } + if argsList.Len() == 3 { + noCommasArg := argsList.Back().Value.(formulaArg).ToBool() + if noCommasArg.Type == ArgError { + return noCommasArg + } + noCommas = noCommasArg.Boolean + } + n := math.Pow(10, float64(decimals)) + r := numArg.Number * n + fixed := float64(int(r+math.Copysign(0.5, r))) / n + if decimals > 0 { + precision = decimals + } + if noCommas { + return newStringFormulaArg(fmt.Sprintf(fmt.Sprintf("%%.%df", precision), fixed)) + } + p := message.NewPrinter(language.English) + return newStringFormulaArg(p.Sprintf(fmt.Sprintf("%%.%df", precision), fixed)) +} + +// FIND function returns the position of a specified character or sub-string +// within a supplied text string. The function is case-sensitive. The syntax +// of the function is: +// +// FIND(find_text,within_text,[start_num]) +// +func (fn *formulaFuncs) FIND(argsList *list.List) formulaArg { + return fn.find("FIND", argsList) +} + +// FINDB counts each double-byte character as 2 when you have enabled the +// editing of a language that supports DBCS and then set it as the default +// language. Otherwise, FINDB counts each character as 1. The syntax of the +// function is: +// +// FINDB(find_text,within_text,[start_num]) +// +func (fn *formulaFuncs) FINDB(argsList *list.List) formulaArg { + return fn.find("FINDB", argsList) +} + +// find is an implementation of the formula functions FIND and FINDB. +func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name)) + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 3 arguments", name)) + } + findText := argsList.Front().Value.(formulaArg).Value() + withinText := argsList.Front().Next().Value.(formulaArg).Value() + startNum, result := 1, 1 + if argsList.Len() == 3 { + numArg := argsList.Back().Value.(formulaArg).ToNumber() + if numArg.Type != ArgNumber { + return numArg + } + if numArg.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + startNum = int(numArg.Number) + } + if findText == "" { + return newNumberFormulaArg(float64(startNum)) + } + for idx := range withinText { + if result < startNum { + result++ + } + if strings.Index(withinText[idx:], findText) == 0 { + return newNumberFormulaArg(float64(result)) + } + result++ + } + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) +} + +// LEFT function returns a specified number of characters from the start of a +// supplied text string. The syntax of the function is: +// +// LEFT(text,[num_chars]) +// +func (fn *formulaFuncs) LEFT(argsList *list.List) formulaArg { + return fn.leftRight("LEFT", argsList) +} + +// LEFTB returns the first character or characters in a text string, based on +// the number of bytes you specify. The syntax of the function is: +// +// LEFTB(text,[num_bytes]) +// +func (fn *formulaFuncs) LEFTB(argsList *list.List) formulaArg { + return fn.leftRight("LEFTB", argsList) +} + +// leftRight is an implementation of the formula functions LEFT, LEFTB, RIGHT, +// RIGHTB. TODO: support DBCS include Japanese, Chinese (Simplified), Chinese +// (Traditional), and Korean. +func (fn *formulaFuncs) leftRight(name string, argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name)) + } + text, numChars := argsList.Front().Value.(formulaArg).Value(), 1 + if argsList.Len() == 2 { + numArg := argsList.Back().Value.(formulaArg).ToNumber() + if numArg.Type != ArgNumber { + return numArg + } + if numArg.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + numChars = int(numArg.Number) + } + if len(text) > numChars { + if name == "LEFT" || name == "LEFTB" { + return newStringFormulaArg(text[:numChars]) + } + return newStringFormulaArg(text[len(text)-numChars:]) + } + return newStringFormulaArg(text) +} + +// LEN returns the length of a supplied text string. The syntax of the +// function is: +// +// LEN(text) +// +func (fn *formulaFuncs) LEN(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "LEN requires 1 string argument") + } + return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String))) +} + +// LENB returns the number of bytes used to represent the characters in a text +// string. LENB counts 2 bytes per character only when a DBCS language is set +// as the default language. Otherwise LENB behaves the same as LEN, counting +// 1 byte per character. The syntax of the function is: +// +// LENB(text) +// +// TODO: the languages that support DBCS include Japanese, Chinese +// (Simplified), Chinese (Traditional), and Korean. +func (fn *formulaFuncs) LENB(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "LENB requires 1 string argument") + } + return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String))) +} + +// LOWER converts all characters in a supplied text string to lower case. The +// syntax of the function is: +// +// LOWER(text) +// +func (fn *formulaFuncs) LOWER(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "LOWER requires 1 argument") + } + return newStringFormulaArg(strings.ToLower(argsList.Front().Value.(formulaArg).String)) +} + +// MID function returns a specified number of characters from the middle of a +// supplied text string. The syntax of the function is: +// +// MID(text,start_num,num_chars) +// +func (fn *formulaFuncs) MID(argsList *list.List) formulaArg { + return fn.mid("MID", argsList) +} + +// MIDB returns a specific number of characters from a text string, starting +// at the position you specify, based on the number of bytes you specify. The +// syntax of the function is: +// +// MID(text,start_num,num_chars) +// +func (fn *formulaFuncs) MIDB(argsList *list.List) formulaArg { + return fn.mid("MIDB", argsList) +} + +// mid is an implementation of the formula functions MID and MIDB. TODO: +// support DBCS include Japanese, Chinese (Simplified), Chinese +// (Traditional), and Korean. +func (fn *formulaFuncs) mid(name string, argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name)) + } + text := argsList.Front().Value.(formulaArg).Value() + startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if startNumArg.Type != ArgNumber { + return startNumArg + } + if numCharsArg.Type != ArgNumber { + return numCharsArg + } + startNum := int(startNumArg.Number) + if startNum < 0 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + textLen := len(text) + if startNum > textLen { + return newStringFormulaArg("") + } + startNum-- + endNum := startNum + int(numCharsArg.Number) + if endNum > textLen+1 { + return newStringFormulaArg(text[startNum:]) + } + return newStringFormulaArg(text[startNum:endNum]) +} + +// PROPER converts all characters in a supplied text string to proper case +// (i.e. all letters that do not immediately follow another letter are set to +// upper case and all other characters are lower case). The syntax of the +// function is: +// +// PROPER(text) +// +func (fn *formulaFuncs) PROPER(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "PROPER requires 1 argument") + } + buf := bytes.Buffer{} + isLetter := false + for _, char := range argsList.Front().Value.(formulaArg).String { + if !isLetter && unicode.IsLetter(char) { + buf.WriteRune(unicode.ToUpper(char)) + } else { + buf.WriteRune(unicode.ToLower(char)) + } + isLetter = unicode.IsLetter(char) + } + return newStringFormulaArg(buf.String()) +} + +// REPLACE function replaces all or part of a text string with another string. +// The syntax of the function is: +// +// REPLACE(old_text,start_num,num_chars,new_text) +// +func (fn *formulaFuncs) REPLACE(argsList *list.List) formulaArg { + return fn.replace("REPLACE", argsList) +} + +// REPLACEB replaces part of a text string, based on the number of bytes you +// specify, with a different text string. +// +// REPLACEB(old_text,start_num,num_chars,new_text) +// +func (fn *formulaFuncs) REPLACEB(argsList *list.List) formulaArg { + return fn.replace("REPLACEB", argsList) +} + +// replace is an implementation of the formula functions REPLACE and REPLACEB. +// TODO: support DBCS include Japanese, Chinese (Simplified), Chinese +// (Traditional), and Korean. +func (fn *formulaFuncs) replace(name string, argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 4 arguments", name)) + } + oldText, newText := argsList.Front().Value.(formulaArg).Value(), argsList.Back().Value.(formulaArg).Value() + startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if startNumArg.Type != ArgNumber { + return startNumArg + } + if numCharsArg.Type != ArgNumber { + return numCharsArg + } + oldTextLen, startIdx := len(oldText), int(startNumArg.Number) + if startIdx > oldTextLen { + startIdx = oldTextLen + 1 + } + endIdx := startIdx + int(numCharsArg.Number) + if endIdx > oldTextLen { + endIdx = oldTextLen + 1 + } + if startIdx < 1 || endIdx < 1 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + result := oldText[:startIdx-1] + newText + oldText[endIdx-1:] + return newStringFormulaArg(result) +} + +// REPT function returns a supplied text string, repeated a specified number +// of times. The syntax of the function is: +// +// REPT(text,number_times) +// +func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "REPT requires 2 arguments") + } + text := argsList.Front().Value.(formulaArg) + if text.Type != ArgString { + return newErrorFormulaArg(formulaErrorVALUE, "REPT requires first argument to be a string") + } + times := argsList.Back().Value.(formulaArg).ToNumber() + if times.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be a number") + } + if times.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be >= 0") + } + if times.Number == 0 { + return newStringFormulaArg("") + } + buf := bytes.Buffer{} + for i := 0; i < int(times.Number); i++ { + buf.WriteString(text.String) + } + return newStringFormulaArg(buf.String()) +} + +// RIGHT function returns a specified number of characters from the end of a +// supplied text string. The syntax of the function is: +// +// RIGHT(text,[num_chars]) +// +func (fn *formulaFuncs) RIGHT(argsList *list.List) formulaArg { + return fn.leftRight("RIGHT", argsList) +} + +// RIGHTB returns the last character or characters in a text string, based on +// the number of bytes you specify. The syntax of the function is: +// +// RIGHTB(text,[num_bytes]) +// +func (fn *formulaFuncs) RIGHTB(argsList *list.List) formulaArg { + return fn.leftRight("RIGHTB", argsList) +} + +// SUBSTITUTE function replaces one or more instances of a given text string, +// within an original text string. The syntax of the function is: +// +// SUBSTITUTE(text,old_text,new_text,[instance_num]) +// +func (fn *formulaFuncs) SUBSTITUTE(argsList *list.List) formulaArg { + if argsList.Len() != 3 && argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "SUBSTITUTE requires 3 or 4 arguments") + } + text, oldText := argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg) + newText, instanceNum := argsList.Front().Next().Next().Value.(formulaArg), 0 + if argsList.Len() == 3 { + return newStringFormulaArg(strings.Replace(text.Value(), oldText.Value(), newText.Value(), -1)) + } + instanceNumArg := argsList.Back().Value.(formulaArg).ToNumber() + if instanceNumArg.Type != ArgNumber { + return instanceNumArg + } + instanceNum = int(instanceNumArg.Number) + if instanceNum < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "instance_num should be > 0") + } + str, oldTextLen, count, chars, pos := text.Value(), len(oldText.Value()), instanceNum, 0, -1 + for { + count-- + index := strings.Index(str, oldText.Value()) + if index == -1 { + pos = -1 + break + } else { + pos = index + chars + if count == 0 { + break + } + idx := oldTextLen + index + chars += idx + str = str[idx:] + } + } + if pos == -1 { + return newStringFormulaArg(text.Value()) + } + pre, post := text.Value()[:pos], text.Value()[pos+oldTextLen:] + return newStringFormulaArg(pre + newText.Value() + post) +} + +// TEXTJOIN function joins together a series of supplied text strings into one +// combined text string. The user can specify a delimiter to add between the +// individual text items, if required. The syntax of the function is: +// +// TEXTJOIN([delimiter],[ignore_empty],text1,[text2],...) +// +func (fn *formulaFuncs) TEXTJOIN(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "TEXTJOIN requires at least 3 arguments") + } + if argsList.Len() > 252 { + return newErrorFormulaArg(formulaErrorVALUE, "TEXTJOIN accepts at most 252 arguments") + } + delimiter := argsList.Front().Value.(formulaArg) + ignoreEmpty := argsList.Front().Next().Value.(formulaArg).ToBool() + if ignoreEmpty.Type != ArgNumber { + return ignoreEmpty + } + args, ok := textJoin(argsList.Front().Next().Next(), []string{}, ignoreEmpty.Number != 0) + if ok.Type != ArgNumber { + return ok + } + result := strings.Join(args, delimiter.Value()) + if len(result) > TotalCellChars { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("TEXTJOIN function exceeds %d characters", TotalCellChars)) + } + return newStringFormulaArg(result) +} + +// textJoin is an implementation of the formula function TEXTJOIN. +func textJoin(arg *list.Element, arr []string, ignoreEmpty bool) ([]string, formulaArg) { + for arg.Next(); arg != nil; arg = arg.Next() { + switch arg.Value.(formulaArg).Type { + case ArgError: + return arr, arg.Value.(formulaArg) + case ArgString: + val := arg.Value.(formulaArg).Value() + if val != "" || !ignoreEmpty { + arr = append(arr, val) + } + case ArgNumber: + arr = append(arr, arg.Value.(formulaArg).Value()) + case ArgMatrix: + for _, row := range arg.Value.(formulaArg).Matrix { + argList := list.New().Init() + for _, ele := range row { + argList.PushBack(ele) + } + if argList.Len() > 0 { + args, _ := textJoin(argList.Front(), []string{}, ignoreEmpty) + arr = append(arr, args...) + } + } + } + } + return arr, newBoolFormulaArg(true) +} + +// TRIM removes extra spaces (i.e. all spaces except for single spaces between +// words or characters) from a supplied text string. The syntax of the +// function is: +// +// TRIM(text) +// +func (fn *formulaFuncs) TRIM(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument") + } + return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).String)) +} + +// UNICHAR returns the Unicode character that is referenced by the given +// numeric value. The syntax of the function is: +// +// UNICHAR(number) +// +func (fn *formulaFuncs) UNICHAR(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "UNICHAR requires 1 argument") + } + numArg := argsList.Front().Value.(formulaArg).ToNumber() + if numArg.Type != ArgNumber { + return numArg + } + if numArg.Number <= 0 || numArg.Number > 55295 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return newStringFormulaArg(string(rune(numArg.Number))) +} + +// UNICODE function returns the code point for the first character of a +// supplied text string. The syntax of the function is: +// +// UNICODE(text) +// +func (fn *formulaFuncs) UNICODE(argsList *list.List) formulaArg { + return fn.code("UNICODE", argsList) +} + +// UPPER converts all characters in a supplied text string to upper case. The +// syntax of the function is: +// +// UPPER(text) +// +func (fn *formulaFuncs) UPPER(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "UPPER requires 1 argument") + } + return newStringFormulaArg(strings.ToUpper(argsList.Front().Value.(formulaArg).String)) +} + +// VALUE function converts a text string into a numeric value. The syntax of +// the function is: +// +// VALUE(text) +// +func (fn *formulaFuncs) VALUE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "VALUE requires 1 argument") + } + text := strings.ReplaceAll(argsList.Front().Value.(formulaArg).Value(), ",", "") + percent := 1.0 + if strings.HasSuffix(text, "%") { + percent, text = 0.01, strings.TrimSuffix(text, "%") + } + decimal := big.Float{} + if _, ok := decimal.SetString(text); ok { + value, _ := decimal.Float64() + return newNumberFormulaArg(value * percent) + } + dateValue, timeValue, errTime, errDate := 0.0, 0.0, false, false + if !isDateOnlyFmt(text) { + h, m, s, _, _, err := strToTime(text) + errTime = err.Type == ArgError + if !errTime { + timeValue = (float64(h)*3600 + float64(m)*60 + s) / 86400 + } + } + y, m, d, _, err := strToDate(text) + errDate = err.Type == ArgError + if !errDate { + dateValue = daysBetween(excelMinTime1900.Unix(), makeDate(y, time.Month(m), d)) + 1 + } + if errTime && errDate { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return newNumberFormulaArg(dateValue + timeValue) +} + +// Conditional Functions + +// IF function tests a supplied condition and returns one result if the +// condition evaluates to TRUE, and another result if the condition evaluates +// to FALSE. The syntax of the function is: +// +// IF(logical_test,value_if_true,value_if_false) +// +func (fn *formulaFuncs) IF(argsList *list.List) formulaArg { + if argsList.Len() == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "IF requires at least 1 argument") + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "IF accepts at most 3 arguments") + } + token := argsList.Front().Value.(formulaArg) + var ( + cond bool + err error + result formulaArg + ) + switch token.Type { + case ArgString: + if cond, err = strconv.ParseBool(token.String); err != nil { + return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + } + if argsList.Len() == 1 { + return newBoolFormulaArg(cond) + } + if cond { + value := argsList.Front().Next().Value.(formulaArg) + switch value.Type { + case ArgNumber: + result = value.ToNumber() + default: + result = newStringFormulaArg(value.String) + } + return result + } + if argsList.Len() == 3 { + value := argsList.Back().Value.(formulaArg) + switch value.Type { + case ArgNumber: + result = value.ToNumber() + default: + result = newStringFormulaArg(value.String) + } + } + } + return result +} + +// Lookup and Reference Functions + +// ADDRESS function takes a row and a column number and returns a cell +// reference as a text string. The syntax of the function is: +// +// ADDRESS(row_num,column_num,[abs_num],[a1],[sheet_text]) +// +func (fn *formulaFuncs) ADDRESS(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "ADDRESS requires at least 2 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "ADDRESS requires at most 5 arguments") + } + rowNum := argsList.Front().Value.(formulaArg).ToNumber() + if rowNum.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if rowNum.Number >= TotalRows { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + colNum := argsList.Front().Next().Value.(formulaArg).ToNumber() + if colNum.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + absNum := newNumberFormulaArg(1) + if argsList.Len() >= 3 { + absNum = argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if absNum.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + if absNum.Number < 1 || absNum.Number > 4 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + a1 := newBoolFormulaArg(true) + if argsList.Len() >= 4 { + a1 = argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool() + if a1.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + var sheetText string + if argsList.Len() == 5 { + sheetText = trimSheetName(argsList.Back().Value.(formulaArg).Value()) + } + if len(sheetText) > 0 { + sheetText = fmt.Sprintf("%s!", sheetText) + } + formatter := addressFmtMaps[fmt.Sprintf("%d_%s", int(absNum.Number), a1.Value())] + addr, err := formatter(int(colNum.Number), int(colNum.Number)) + if err != nil { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return newStringFormulaArg(fmt.Sprintf("%s%s", sheetText, addr)) +} + +// CHOOSE function returns a value from an array, that corresponds to a +// supplied index number (position). The syntax of the function is: +// +// CHOOSE(index_num,value1,[value2],...) +// +func (fn *formulaFuncs) CHOOSE(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires 2 arguments") + } + idx, err := strconv.Atoi(argsList.Front().Value.(formulaArg).String) + if err != nil { + return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires first argument of type number") + } + if argsList.Len() <= idx { + return newErrorFormulaArg(formulaErrorVALUE, "index_num should be <= to the number of values") + } + arg := argsList.Front() + for i := 0; i < idx; i++ { + arg = arg.Next() + } + return arg.Value.(formulaArg) +} + +// deepMatchRune finds whether the text deep matches/satisfies the pattern +// string. +func deepMatchRune(str, pattern []rune, simple bool) bool { + for len(pattern) > 0 { + switch pattern[0] { + default: + if len(str) == 0 || str[0] != pattern[0] { + return false + } + case '?': + if len(str) == 0 && !simple { + return false + } + case '*': + return deepMatchRune(str, pattern[1:], simple) || + (len(str) > 0 && deepMatchRune(str[1:], pattern, simple)) + } + str = str[1:] + pattern = pattern[1:] + } + return len(str) == 0 && len(pattern) == 0 +} + +// matchPattern finds whether the text matches or satisfies the pattern +// string. The pattern supports '*' and '?' wildcards in the pattern string. +func matchPattern(pattern, name string) (matched bool) { + if pattern == "" { + return name == pattern + } + if pattern == "*" { + return true + } + rName, rPattern := make([]rune, 0, len(name)), make([]rune, 0, len(pattern)) + for _, r := range name { + rName = append(rName, r) + } + for _, r := range pattern { + rPattern = append(rPattern, r) + } + return deepMatchRune(rName, rPattern, false) +} + +// compareFormulaArg compares the left-hand sides and the right-hand sides' +// formula arguments by given conditions such as case-sensitive, if exact +// match, and make compare result as formula criteria condition type. +func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte { + if lhs.Type != rhs.Type { + return criteriaErr + } + switch lhs.Type { + case ArgNumber: + if lhs.Number == rhs.Number { + return criteriaEq + } + if lhs.Number < rhs.Number { + return criteriaL + } + return criteriaG + case ArgString: + ls, rs := lhs.String, rhs.String + if !caseSensitive { + ls, rs = strings.ToLower(ls), strings.ToLower(rs) + } + if matchMode.Number == matchModeWildcard { + if matchPattern(rs, ls) { + return criteriaEq + } + } + return map[int]byte{1: criteriaG, -1: criteriaL, 0: criteriaEq}[strings.Compare(ls, rs)] + case ArgEmpty: + return criteriaEq + case ArgList: + return compareFormulaArgList(lhs, rhs, matchMode, caseSensitive) + case ArgMatrix: + return compareFormulaArgMatrix(lhs, rhs, matchMode, caseSensitive) + } + return criteriaErr +} + +// compareFormulaArgList compares the left-hand sides and the right-hand sides +// list type formula arguments. +func compareFormulaArgList(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte { + if len(lhs.List) < len(rhs.List) { + return criteriaL + } + if len(lhs.List) > len(rhs.List) { + return criteriaG + } + for arg := range lhs.List { + criteria := compareFormulaArg(lhs.List[arg], rhs.List[arg], matchMode, caseSensitive) + if criteria != criteriaEq { + return criteria + } + } + return criteriaEq +} + +// compareFormulaArgMatrix compares the left-hand sides and the right-hand sides' +// matrix type formula arguments. +func compareFormulaArgMatrix(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte { + if len(lhs.Matrix) < len(rhs.Matrix) { + return criteriaL + } + if len(lhs.Matrix) > len(rhs.Matrix) { + return criteriaG + } + for i := range lhs.Matrix { + left := lhs.Matrix[i] + right := lhs.Matrix[i] + if len(left) < len(right) { + return criteriaL + } + if len(left) > len(right) { + return criteriaG + } + for arg := range left { + criteria := compareFormulaArg(left[arg], right[arg], matchMode, caseSensitive) + if criteria != criteriaEq { + return criteria + } + } + } + return criteriaEq +} + +// COLUMN function returns the first column number within a supplied reference +// or the number of the current column. The syntax of the function is: +// +// COLUMN([reference]) +// +func (fn *formulaFuncs) COLUMN(argsList *list.List) formulaArg { + if argsList.Len() > 1 { + return newErrorFormulaArg(formulaErrorVALUE, "COLUMN requires at most 1 argument") + } + if argsList.Len() == 1 { + if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 { + return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Col)) + } + if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 { + return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Col)) + } + return newErrorFormulaArg(formulaErrorVALUE, "invalid reference") + } + col, _, _ := CellNameToCoordinates(fn.cell) + return newNumberFormulaArg(float64(col)) +} + +// calcColumnsMinMax calculation min and max value for given formula arguments +// sequence of the formula function COLUMNS. +func calcColumnsMinMax(argsList *list.List) (min, max int) { + if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 { + crs := argsList.Front().Value.(formulaArg).cellRanges + for cr := crs.Front(); cr != nil; cr = cr.Next() { + if min == 0 { + min = cr.Value.(cellRange).From.Col + } + if min > cr.Value.(cellRange).From.Col { + min = cr.Value.(cellRange).From.Col + } + if min > cr.Value.(cellRange).To.Col { + min = cr.Value.(cellRange).To.Col + } + if max < cr.Value.(cellRange).To.Col { + max = cr.Value.(cellRange).To.Col + } + if max < cr.Value.(cellRange).From.Col { + max = cr.Value.(cellRange).From.Col + } + } + } + if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 { + cr := argsList.Front().Value.(formulaArg).cellRefs + for refs := cr.Front(); refs != nil; refs = refs.Next() { + if min == 0 { + min = refs.Value.(cellRef).Col + } + if min > refs.Value.(cellRef).Col { + min = refs.Value.(cellRef).Col + } + if max < refs.Value.(cellRef).Col { + max = refs.Value.(cellRef).Col + } + } + } + return +} + +// COLUMNS function receives an Excel range and returns the number of columns +// that are contained within the range. The syntax of the function is: +// +// COLUMNS(array) +// +func (fn *formulaFuncs) COLUMNS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "COLUMNS requires 1 argument") + } + min, max := calcColumnsMinMax(argsList) + if max == TotalColumns { + return newNumberFormulaArg(float64(TotalColumns)) + } + result := max - min + 1 + if max == min { + if min == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "invalid reference") + } + return newNumberFormulaArg(float64(1)) + } + return newNumberFormulaArg(float64(result)) +} + +// FORMULATEXT function returns a formula as a text string. The syntax of the +// function is: +// +// FORMULATEXT(reference) +// +func (fn *formulaFuncs) FORMULATEXT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "FORMULATEXT requires 1 argument") + } + refs := argsList.Front().Value.(formulaArg).cellRefs + col, row := 0, 0 + if refs != nil && refs.Len() > 0 { + col, row = refs.Front().Value.(cellRef).Col, refs.Front().Value.(cellRef).Row + } + ranges := argsList.Front().Value.(formulaArg).cellRanges + if ranges != nil && ranges.Len() > 0 { + col, row = ranges.Front().Value.(cellRange).From.Col, ranges.Front().Value.(cellRange).From.Row + } + cell, err := CoordinatesToCellName(col, row) + if err != nil { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + formula, _ := fn.f.GetCellFormula(fn.sheet, cell) + return newStringFormulaArg(formula) +} + +// checkHVLookupArgs checking arguments, prepare extract mode, lookup value, +// and data for the formula functions HLOOKUP and VLOOKUP. +func checkHVLookupArgs(name string, argsList *list.List) (idx int, lookupValue, tableArray, matchMode, errArg formulaArg) { + unit := map[string]string{ + "HLOOKUP": "row", + "VLOOKUP": "col", + }[name] + if argsList.Len() < 3 { + errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 3 arguments", name)) + return + } + if argsList.Len() > 4 { + errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at most 4 arguments", name)) + return + } + lookupValue = argsList.Front().Value.(formulaArg) + tableArray = argsList.Front().Next().Value.(formulaArg) + if tableArray.Type != ArgMatrix { + errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires second argument of table array", name)) + return + } + arg := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if arg.Type != ArgNumber { + errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires numeric %s argument", name, unit)) + return + } + idx, matchMode = int(arg.Number)-1, newNumberFormulaArg(matchModeMaxLess) + if argsList.Len() == 4 { + rangeLookup := argsList.Back().Value.(formulaArg).ToBool() + if rangeLookup.Type == ArgError { + errArg = newErrorFormulaArg(formulaErrorVALUE, rangeLookup.Error) + return + } + if rangeLookup.Number == 0 { + matchMode = newNumberFormulaArg(matchModeWildcard) + } + } + return +} + +// HLOOKUP function 'looks up' a given value in the top row of a data array +// (or table), and returns the corresponding value from another row of the +// array. The syntax of the function is: +// +// HLOOKUP(lookup_value,table_array,row_index_num,[range_lookup]) +// +func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg { + rowIdx, lookupValue, tableArray, matchMode, errArg := checkHVLookupArgs("HLOOKUP", argsList) + if errArg.Type == ArgError { + return errArg + } + var matchIdx int + var wasExact bool + if matchMode.Number == matchModeWildcard || len(tableArray.Matrix) == TotalRows { + matchIdx, wasExact = lookupLinearSearch(false, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeLinear)) + } else { + matchIdx, wasExact = lookupBinarySearch(false, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeAscBinary)) + } + if matchIdx == -1 { + return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found") + } + if rowIdx < 0 || rowIdx >= len(tableArray.Matrix) { + return newErrorFormulaArg(formulaErrorNA, "HLOOKUP has invalid row index") + } + row := tableArray.Matrix[rowIdx] + if wasExact || matchMode.Number == matchModeWildcard { + return row[matchIdx] + } + return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found") +} + +// calcMatch returns the position of the value by given match type, criteria +// and lookup array for the formula function MATCH. +func calcMatch(matchType int, criteria *formulaCriteria, lookupArray []formulaArg) formulaArg { + switch matchType { + case 0: + for i, arg := range lookupArray { + if ok, _ := formulaCriteriaEval(arg.Value(), criteria); ok { + return newNumberFormulaArg(float64(i + 1)) + } + } + case -1: + for i, arg := range lookupArray { + if ok, _ := formulaCriteriaEval(arg.Value(), criteria); ok { + return newNumberFormulaArg(float64(i + 1)) + } + if ok, _ := formulaCriteriaEval(arg.Value(), &formulaCriteria{ + Type: criteriaL, Condition: criteria.Condition, + }); ok { + if i == 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(float64(i)) + } + } + case 1: + for i, arg := range lookupArray { + if ok, _ := formulaCriteriaEval(arg.Value(), criteria); ok { + return newNumberFormulaArg(float64(i + 1)) + } + if ok, _ := formulaCriteriaEval(arg.Value(), &formulaCriteria{ + Type: criteriaG, Condition: criteria.Condition, + }); ok { + if i == 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + return newNumberFormulaArg(float64(i)) + } + } + } + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) +} + +// MATCH function looks up a value in an array, and returns the position of +// the value within the array. The user can specify that the function should +// only return a result if an exact match is found, or that the function +// should return the position of the closest match (above or below), if an +// exact match is not found. The syntax of the Match function is: +// +// MATCH(lookup_value,lookup_array,[match_type]) +// +func (fn *formulaFuncs) MATCH(argsList *list.List) formulaArg { + if argsList.Len() != 2 && argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "MATCH requires 1 or 2 arguments") + } + var ( + matchType = 1 + lookupArray []formulaArg + lookupArrayArg = argsList.Front().Next().Value.(formulaArg) + lookupArrayErr = "MATCH arguments lookup_array should be one-dimensional array" + ) + if argsList.Len() == 3 { + matchTypeArg := argsList.Back().Value.(formulaArg).ToNumber() + if matchTypeArg.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, "MATCH requires numeric match_type argument") + } + if matchTypeArg.Number == -1 || matchTypeArg.Number == 0 { + matchType = int(matchTypeArg.Number) + } + } + switch lookupArrayArg.Type { + case ArgMatrix: + if len(lookupArrayArg.Matrix[0]) != 1 { + return newErrorFormulaArg(formulaErrorNA, lookupArrayErr) + } + lookupArray = lookupArrayArg.ToList() + default: + return newErrorFormulaArg(formulaErrorNA, lookupArrayErr) + } + return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg).String), lookupArray) +} + +// TRANSPOSE function 'transposes' an array of cells (i.e. the function copies +// a horizontal range of cells into a vertical range and vice versa). The +// syntax of the function is: +// +// TRANSPOSE(array) +// +func (fn *formulaFuncs) TRANSPOSE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "TRANSPOSE requires 1 argument") + } + args := argsList.Back().Value.(formulaArg).ToList() + rmin, rmax := calcRowsMinMax(argsList) + cmin, cmax := calcColumnsMinMax(argsList) + cols, rows := cmax-cmin+1, rmax-rmin+1 + src := make([][]formulaArg, 0) + for i := 0; i < len(args); i += cols { + src = append(src, args[i:i+cols]) + } + mtx := make([][]formulaArg, cols) + for r, row := range src { + colIdx := r % rows + for c, cell := range row { + rowIdx := c % cols + if len(mtx[rowIdx]) == 0 { + mtx[rowIdx] = make([]formulaArg, rows) + } + mtx[rowIdx][colIdx] = cell + } + } + return newMatrixFormulaArg(mtx) +} + +// lookupLinearSearch sequentially checks each look value of the lookup array until +// a match is found or the whole list has been searched. +func lookupLinearSearch(vertical bool, lookupValue, lookupArray, matchMode, searchMode formulaArg) (int, bool) { + var tableArray []formulaArg + if vertical { + for _, row := range lookupArray.Matrix { + tableArray = append(tableArray, row[0]) + } + } else { + tableArray = lookupArray.Matrix[0] + } + matchIdx, wasExact := -1, false +start: + for i, cell := range tableArray { + lhs := cell + if lookupValue.Type == ArgNumber { + if lhs = cell.ToNumber(); lhs.Type == ArgError { + lhs = cell + } + } else if lookupValue.Type == ArgMatrix { + lhs = lookupArray + } + if compareFormulaArg(lhs, lookupValue, matchMode, false) == criteriaEq { + matchIdx = i + wasExact = true + if searchMode.Number == searchModeLinear { + break start + } + } + if matchMode.Number == matchModeMinGreater || matchMode.Number == matchModeMaxLess { + matchIdx = int(calcMatch(int(matchMode.Number), formulaCriteriaParser(lookupValue.Value()), tableArray).Number) + continue + } + } + return matchIdx, wasExact +} + +// VLOOKUP function 'looks up' a given value in the left-hand column of a +// data array (or table), and returns the corresponding value from another +// column of the array. The syntax of the function is: +// +// VLOOKUP(lookup_value,table_array,col_index_num,[range_lookup]) +// +func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg { + colIdx, lookupValue, tableArray, matchMode, errArg := checkHVLookupArgs("VLOOKUP", argsList) + if errArg.Type == ArgError { + return errArg + } + var matchIdx int + var wasExact bool + if matchMode.Number == matchModeWildcard || len(tableArray.Matrix) == TotalRows { + matchIdx, wasExact = lookupLinearSearch(true, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeLinear)) + } else { + matchIdx, wasExact = lookupBinarySearch(true, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeAscBinary)) + } + if matchIdx == -1 { + return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found") + } + mtx := tableArray.Matrix[matchIdx] + if colIdx < 0 || colIdx >= len(mtx) { + return newErrorFormulaArg(formulaErrorNA, "VLOOKUP has invalid column index") + } + if wasExact || matchMode.Number == matchModeWildcard { + return mtx[colIdx] + } + return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found") +} + +// lookupBinarySearch finds the position of a target value when range lookup +// is TRUE, if the data of table array can't guarantee be sorted, it will +// return wrong result. +func lookupBinarySearch(vertical bool, lookupValue, lookupArray, matchMode, searchMode formulaArg) (matchIdx int, wasExact bool) { + var tableArray []formulaArg + if vertical { + for _, row := range lookupArray.Matrix { + tableArray = append(tableArray, row[0]) + } + } else { + tableArray = lookupArray.Matrix[0] + } + low, high, lastMatchIdx := 0, len(tableArray)-1, -1 + count := high + for low <= high { + mid := low + (high-low)/2 + cell := tableArray[mid] + lhs := cell + if lookupValue.Type == ArgNumber { + if lhs = cell.ToNumber(); lhs.Type == ArgError { + lhs = cell + } + } else if lookupValue.Type == ArgMatrix && vertical { + lhs = lookupArray + } + result := compareFormulaArg(lhs, lookupValue, matchMode, false) + if result == criteriaEq { + matchIdx, wasExact = mid, true + if searchMode.Number == searchModeDescBinary { + matchIdx = count - matchIdx + } + return + } else if result == criteriaG { + high = mid - 1 + } else if result == criteriaL { + matchIdx = mid + if lhs.Value() != "" { + lastMatchIdx = matchIdx + } + low = mid + 1 + } else { + return -1, false + } + } + matchIdx, wasExact = lastMatchIdx, true + return +} + +// checkLookupArgs checking arguments, prepare lookup value, and data for the +// formula function LOOKUP. +func checkLookupArgs(argsList *list.List) (arrayForm bool, lookupValue, lookupVector, errArg formulaArg) { + if argsList.Len() < 2 { + errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at least 2 arguments") + return + } + if argsList.Len() > 3 { + errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments") + return + } + lookupValue = argsList.Front().Value.(formulaArg) + lookupVector = argsList.Front().Next().Value.(formulaArg) + if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList { + errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array") + return + } + arrayForm = lookupVector.Type == ArgMatrix + if arrayForm && len(lookupVector.Matrix) == 0 { + errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires not empty range as second argument") + } + return +} + +// iterateLookupArgs iterate arguments to extract columns and calculate match +// index for the formula function LOOKUP. +func iterateLookupArgs(lookupValue, lookupVector formulaArg) ([]formulaArg, int, bool) { + cols, matchIdx, ok := lookupCol(lookupVector, 0), -1, false + for idx, col := range cols { + lhs := lookupValue + switch col.Type { + case ArgNumber: + lhs = lhs.ToNumber() + if !col.Boolean { + if lhs.Type == ArgError { + lhs = lookupValue + } + } + } + compare := compareFormulaArg(lhs, col, newNumberFormulaArg(matchModeMaxLess), false) + // Find exact match + if compare == criteriaEq { + matchIdx = idx + break + } + // Find the nearest match if lookup value is more than or equal to the first value in lookup vector + if idx == 0 { + ok = compare == criteriaG + } else if ok && compare == criteriaL && matchIdx == -1 { + matchIdx = idx - 1 + } + } + return cols, matchIdx, ok +} + +// index is an implementation of the formula function INDEX. +func (fn *formulaFuncs) index(array formulaArg, rowIdx, colIdx int) formulaArg { + var cells []formulaArg + if array.Type == ArgMatrix { + cellMatrix := array.Matrix + if rowIdx < -1 || rowIdx >= len(cellMatrix) { + return newErrorFormulaArg(formulaErrorREF, "INDEX row_num out of range") + } + if rowIdx == -1 { + if colIdx >= len(cellMatrix[0]) { + return newErrorFormulaArg(formulaErrorREF, "INDEX col_num out of range") + } + var column [][]formulaArg + for _, cells = range cellMatrix { + column = append(column, []formulaArg{cells[colIdx]}) + } + return newMatrixFormulaArg(column) + } + cells = cellMatrix[rowIdx] + } + if colIdx < -1 || colIdx >= len(cells) { + return newErrorFormulaArg(formulaErrorREF, "INDEX col_num out of range") + } + return newListFormulaArg(cells) +} + +// validateMatchMode check the number of match mode if be equal to 0, 1, -1 or +// 2. +func validateMatchMode(mode float64) bool { + return mode == matchModeExact || mode == matchModeMinGreater || mode == matchModeMaxLess || mode == matchModeWildcard +} + +// validateSearchMode check the number of search mode if be equal to 1, -1, 2 +// or -2. +func validateSearchMode(mode float64) bool { + return mode == searchModeLinear || mode == searchModeReverseLinear || mode == searchModeAscBinary || mode == searchModeDescBinary +} + +// prepareXlookupArgs checking and prepare arguments for the formula function +// XLOOKUP. +func (fn *formulaFuncs) prepareXlookupArgs(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "XLOOKUP requires at least 3 arguments") + } + if argsList.Len() > 6 { + return newErrorFormulaArg(formulaErrorVALUE, "XLOOKUP allows at most 6 arguments") + } + lookupValue := argsList.Front().Value.(formulaArg) + lookupArray := argsList.Front().Next().Value.(formulaArg) + returnArray := argsList.Front().Next().Next().Value.(formulaArg) + ifNotFond := newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + matchMode, searchMode := newNumberFormulaArg(matchModeExact), newNumberFormulaArg(searchModeLinear) + if argsList.Len() > 3 { + ifNotFond = argsList.Front().Next().Next().Next().Value.(formulaArg) + } + if argsList.Len() > 4 { + if matchMode = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); matchMode.Type != ArgNumber { + return matchMode + } + } + if argsList.Len() > 5 { + if searchMode = argsList.Back().Value.(formulaArg).ToNumber(); searchMode.Type != ArgNumber { + return searchMode + } + } + if lookupArray.Type != ArgMatrix || returnArray.Type != ArgMatrix { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if !validateMatchMode(matchMode.Number) || !validateSearchMode(searchMode.Number) { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return newListFormulaArg([]formulaArg{lookupValue, lookupArray, returnArray, ifNotFond, matchMode, searchMode}) +} + +// xlookup is an implementation of the formula function XLOOKUP. +func (fn *formulaFuncs) xlookup(lookupRows, lookupCols, returnArrayRows, returnArrayCols, matchIdx int, + condition1, condition2, condition3, condition4 bool, returnArray formulaArg, +) formulaArg { + var result [][]formulaArg + for rowIdx, row := range returnArray.Matrix { + for colIdx, cell := range row { + if condition1 { + if condition2 { + result = append(result, []formulaArg{cell}) + continue + } + if returnArrayRows > 1 && returnArrayCols > 1 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + if condition3 { + if returnArrayCols != lookupCols { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if colIdx == matchIdx { + result = append(result, []formulaArg{cell}) + continue + } + } + if condition4 { + if returnArrayRows != lookupRows { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if rowIdx == matchIdx { + if len(result) == 0 { + result = append(result, []formulaArg{cell}) + continue + } + result[0] = append(result[0], cell) + } + } + } + } + array := newMatrixFormulaArg(result) + cells := array.ToList() + if len(cells) == 1 { + return cells[0] + } + return array +} + +// XLOOKUP function searches a range or an array, and then returns the item +// corresponding to the first match it finds. If no match exists, then +// XLOOKUP can return the closest (approximate) match. The syntax of the +// function is: +// +// XLOOKUP(lookup_value,lookup_array,return_array,[if_not_found],[match_mode],[search_mode]) +// +func (fn *formulaFuncs) XLOOKUP(argsList *list.List) formulaArg { + args := fn.prepareXlookupArgs(argsList) + if args.Type != ArgList { + return args + } + lookupValue, lookupArray, returnArray, ifNotFond, matchMode, searchMode := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5] + lookupRows, lookupCols := len(lookupArray.Matrix), 0 + if lookupRows > 0 { + lookupCols = len(lookupArray.Matrix[0]) + } + if lookupRows != 1 && lookupCols != 1 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + verticalLookup := lookupRows >= lookupCols + var matchIdx int + switch searchMode.Number { + case searchModeLinear, searchModeReverseLinear: + matchIdx, _ = lookupLinearSearch(verticalLookup, lookupValue, lookupArray, matchMode, searchMode) + default: + matchIdx, _ = lookupBinarySearch(verticalLookup, lookupValue, lookupArray, matchMode, searchMode) + } + if matchIdx == -1 { + return ifNotFond + } + returnArrayRows, returnArrayCols := len(returnArray.Matrix), len(returnArray.Matrix[0]) + condition1 := lookupRows == 1 && lookupCols == 1 + condition2 := returnArrayRows == 1 || returnArrayCols == 1 + condition3 := lookupRows == 1 && lookupCols > 1 + condition4 := lookupRows > 1 && lookupCols == 1 + return fn.xlookup(lookupRows, lookupCols, returnArrayRows, returnArrayCols, matchIdx, condition1, condition2, condition3, condition4, returnArray) +} + +// INDEX function returns a reference to a cell that lies in a specified row +// and column of a range of cells. The syntax of the function is: +// +// INDEX(array,row_num,[col_num]) +// +func (fn *formulaFuncs) INDEX(argsList *list.List) formulaArg { + if argsList.Len() < 2 || argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "INDEX requires 2 or 3 arguments") + } + array := argsList.Front().Value.(formulaArg) + if array.Type != ArgMatrix && array.Type != ArgList { + array = newMatrixFormulaArg([][]formulaArg{{array}}) + } + rowArg := argsList.Front().Next().Value.(formulaArg).ToNumber() + if rowArg.Type != ArgNumber { + return rowArg + } + rowIdx, colIdx := int(rowArg.Number)-1, -1 + if argsList.Len() == 3 { + colArg := argsList.Back().Value.(formulaArg).ToNumber() + if colArg.Type != ArgNumber { + return colArg + } + colIdx = int(colArg.Number) - 1 + } + if rowIdx == -1 && colIdx == -1 { + if len(array.ToList()) != 1 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return array.ToList()[0] + } + cells := fn.index(array, rowIdx, colIdx) + if cells.Type != ArgList { + return cells + } + if colIdx == -1 { + return newMatrixFormulaArg([][]formulaArg{cells.List}) + } + return cells.List[colIdx] +} + +// INDIRECT function converts a text string into a cell reference. The syntax +// of the Indirect function is: +// +// INDIRECT(ref_text,[a1]) +// +func (fn *formulaFuncs) INDIRECT(argsList *list.List) formulaArg { + if argsList.Len() != 1 && argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "INDIRECT requires 1 or 2 arguments") + } + refText := argsList.Front().Value.(formulaArg).Value() + a1 := newBoolFormulaArg(true) + if argsList.Len() == 2 { + if a1 = argsList.Back().Value.(formulaArg).ToBool(); a1.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + R1C1ToA1 := func(ref string) (cell string, err error) { + parts := strings.Split(strings.TrimLeft(ref, "R"), "C") + if len(parts) != 2 { + return + } + row, err := strconv.Atoi(parts[0]) + if err != nil { + return + } + col, err := strconv.Atoi(parts[1]) + if err != nil { + return + } + cell, err = CoordinatesToCellName(col, row) + return + } + refs := strings.Split(refText, ":") + fromRef, toRef := refs[0], "" + if len(refs) == 2 { + toRef = refs[1] + } + if a1.Number == 0 { + from, err := R1C1ToA1(refs[0]) + if err != nil { + return newErrorFormulaArg(formulaErrorREF, formulaErrorREF) + } + fromRef = from + if len(refs) == 2 { + to, err := R1C1ToA1(refs[1]) + if err != nil { + return newErrorFormulaArg(formulaErrorREF, formulaErrorREF) + } + toRef = to + } + } + if len(refs) == 1 { + value, err := fn.f.GetCellValue(fn.sheet, fromRef) + if err != nil { + return newErrorFormulaArg(formulaErrorREF, formulaErrorREF) + } + return newStringFormulaArg(value) + } + arg, _ := fn.f.parseReference(fn.sheet, fromRef+":"+toRef) + return arg +} + +// LOOKUP function performs an approximate match lookup in a one-column or +// one-row range, and returns the corresponding value from another one-column +// or one-row range. The syntax of the function is: +// +// LOOKUP(lookup_value,lookup_vector,[result_vector]) +// +func (fn *formulaFuncs) LOOKUP(argsList *list.List) formulaArg { + arrayForm, lookupValue, lookupVector, errArg := checkLookupArgs(argsList) + if errArg.Type == ArgError { + return errArg + } + cols, matchIdx, ok := iterateLookupArgs(lookupValue, lookupVector) + if ok && matchIdx == -1 { + matchIdx = len(cols) - 1 + } + var column []formulaArg + if argsList.Len() == 3 { + column = lookupCol(argsList.Back().Value.(formulaArg), 0) + } else if arrayForm && len(lookupVector.Matrix[0]) > 1 { + column = lookupCol(lookupVector, 1) + } else { + column = cols + } + if matchIdx < 0 || matchIdx >= len(column) { + return newErrorFormulaArg(formulaErrorNA, "LOOKUP no result found") + } + return column[matchIdx] +} + +// lookupCol extract columns for LOOKUP. +func lookupCol(arr formulaArg, idx int) []formulaArg { + col := arr.List + if arr.Type == ArgMatrix { + col = nil + for _, r := range arr.Matrix { + if len(r) > 0 { + col = append(col, r[idx]) + continue + } + col = append(col, newEmptyFormulaArg()) + } + } + return col +} + +// ROW function returns the first row number within a supplied reference or +// the number of the current row. The syntax of the function is: +// +// ROW([reference]) +// +func (fn *formulaFuncs) ROW(argsList *list.List) formulaArg { + if argsList.Len() > 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ROW requires at most 1 argument") + } + if argsList.Len() == 1 { + if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 { + return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Row)) + } + if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 { + return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Row)) + } + return newErrorFormulaArg(formulaErrorVALUE, "invalid reference") + } + _, row, _ := CellNameToCoordinates(fn.cell) + return newNumberFormulaArg(float64(row)) +} + +// calcRowsMinMax calculation min and max value for given formula arguments +// sequence of the formula function ROWS. +func calcRowsMinMax(argsList *list.List) (min, max int) { + if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 { + crs := argsList.Front().Value.(formulaArg).cellRanges + for cr := crs.Front(); cr != nil; cr = cr.Next() { + if min == 0 { + min = cr.Value.(cellRange).From.Row + } + if min > cr.Value.(cellRange).From.Row { + min = cr.Value.(cellRange).From.Row + } + if min > cr.Value.(cellRange).To.Row { + min = cr.Value.(cellRange).To.Row + } + if max < cr.Value.(cellRange).To.Row { + max = cr.Value.(cellRange).To.Row + } + if max < cr.Value.(cellRange).From.Row { + max = cr.Value.(cellRange).From.Row + } + } + } + if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 { + cr := argsList.Front().Value.(formulaArg).cellRefs + for refs := cr.Front(); refs != nil; refs = refs.Next() { + if min == 0 { + min = refs.Value.(cellRef).Row + } + if min > refs.Value.(cellRef).Row { + min = refs.Value.(cellRef).Row + } + if max < refs.Value.(cellRef).Row { + max = refs.Value.(cellRef).Row + } + } + } + return +} + +// ROWS function takes an Excel range and returns the number of rows that are +// contained within the range. The syntax of the function is: +// +// ROWS(array) +// +func (fn *formulaFuncs) ROWS(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ROWS requires 1 argument") + } + min, max := calcRowsMinMax(argsList) + if max == TotalRows { + return newStringFormulaArg(strconv.Itoa(TotalRows)) + } + result := max - min + 1 + if max == min { + if min == 0 { + return newErrorFormulaArg(formulaErrorVALUE, "invalid reference") + } + return newNumberFormulaArg(float64(1)) + } + return newStringFormulaArg(strconv.Itoa(result)) +} + +// Web Functions + +// ENCODEURL function returns a URL-encoded string, replacing certain +// non-alphanumeric characters with the percentage symbol (%) and a +// hexadecimal number. The syntax of the function is: +// +// ENCODEURL(url) +// +func (fn *formulaFuncs) ENCODEURL(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ENCODEURL requires 1 argument") + } + token := argsList.Front().Value.(formulaArg).Value() + return newStringFormulaArg(strings.Replace(url.QueryEscape(token), "+", "%20", -1)) +} + +// Financial Functions + +// validateFrequency check the number of coupon payments per year if be equal to 1, 2 or 4. +func validateFrequency(freq float64) bool { + return freq == 1 || freq == 2 || freq == 4 +} + +// ACCRINT function returns the accrued interest in a security that pays +// periodic interest. The syntax of the function is: +// +// ACCRINT(issue,first_interest,settlement,rate,par,frequency,[basis],[calc_method]) +// +func (fn *formulaFuncs) ACCRINT(argsList *list.List) formulaArg { + if argsList.Len() < 6 { + return newErrorFormulaArg(formulaErrorVALUE, "ACCRINT requires at least 6 arguments") + } + if argsList.Len() > 8 { + return newErrorFormulaArg(formulaErrorVALUE, "ACCRINT allows at most 8 arguments") + } + args := fn.prepareDataValueArgs(3, argsList) + if args.Type != ArgList { + return args + } + issue, settlement := args.List[0], args.List[2] + rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + par := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber || par.Type != ArgNumber || frequency.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if !validateFrequency(frequency.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() >= 7 { + if basis = argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + if argsList.Len() == 8 { + if cm := argsList.Back().Value.(formulaArg).ToBool(); cm.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + frac1 := yearFrac(issue.Number, settlement.Number, int(basis.Number)) + if frac1.Type != ArgNumber { + return frac1 + } + return newNumberFormulaArg(par.Number * rate.Number * frac1.Number) +} + +// ACCRINTM function returns the accrued interest in a security that pays +// interest at maturity. The syntax of the function is: +// +// ACCRINTM(issue,settlement,rate,[par],[basis]) +// +func (fn *formulaFuncs) ACCRINTM(argsList *list.List) formulaArg { + if argsList.Len() != 4 && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "ACCRINTM requires 4 or 5 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + issue, settlement := args.List[0], args.List[1] + if settlement.Number < issue.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + par := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber || par.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if par.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 5 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + frac := yearFrac(issue.Number, settlement.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + return newNumberFormulaArg(frac.Number * rate.Number * par.Number) +} + +// prepareAmorArgs checking and prepare arguments for the formula functions +// AMORDEGRC and AMORLINC. +func (fn *formulaFuncs) prepareAmorArgs(name string, argsList *list.List) formulaArg { + cost := argsList.Front().Value.(formulaArg).ToNumber() + if cost.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires cost to be number argument", name)) + } + if cost.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires cost >= 0", name)) + } + args := list.New().Init() + args.PushBack(argsList.Front().Next().Value.(formulaArg)) + datePurchased := fn.DATEVALUE(args) + if datePurchased.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + args.Init() + args.PushBack(argsList.Front().Next().Next().Value.(formulaArg)) + firstPeriod := fn.DATEVALUE(args) + if firstPeriod.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if firstPeriod.Number < datePurchased.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + salvage := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if salvage.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if salvage.Number < 0 || salvage.Number > cost.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + period := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if period.Type != ArgNumber || period.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + rate := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber || rate.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 7 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + return newListFormulaArg([]formulaArg{cost, datePurchased, firstPeriod, salvage, period, rate, basis}) +} + +// AMORDEGRC function is provided for users of the French accounting system. +// The function calculates the prorated linear depreciation of an asset for a +// specified accounting period. The syntax of the function is: +// +// AMORDEGRC(cost,date_purchased,first_period,salvage,period,rate,[basis]) +// +func (fn *formulaFuncs) AMORDEGRC(argsList *list.List) formulaArg { + if argsList.Len() != 6 && argsList.Len() != 7 { + return newErrorFormulaArg(formulaErrorVALUE, "AMORDEGRC requires 6 or 7 arguments") + } + args := fn.prepareAmorArgs("AMORDEGRC", argsList) + if args.Type != ArgList { + return args + } + cost, datePurchased, firstPeriod, salvage, period, rate, basis := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6] + if rate.Number >= 0.5 { + return newErrorFormulaArg(formulaErrorNUM, "AMORDEGRC requires rate to be < 0.5") + } + assetsLife, amorCoeff := 1/rate.Number, 2.5 + if assetsLife < 3 { + amorCoeff = 1 + } else if assetsLife < 5 { + amorCoeff = 1.5 + } else if assetsLife <= 6 { + amorCoeff = 2 + } + rate.Number *= amorCoeff + frac := yearFrac(datePurchased.Number, firstPeriod.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + nRate := float64(int((frac.Number * cost.Number * rate.Number) + 0.5)) + cost.Number -= nRate + rest := cost.Number - salvage.Number + for n := 0; n < int(period.Number); n++ { + nRate = float64(int((cost.Number * rate.Number) + 0.5)) + rest -= nRate + if rest < 0 { + switch int(period.Number) - n { + case 0: + case 1: + return newNumberFormulaArg(float64(int((cost.Number * 0.5) + 0.5))) + default: + return newNumberFormulaArg(0) + } + } + cost.Number -= nRate + } + return newNumberFormulaArg(nRate) +} + +// AMORLINC function is provided for users of the French accounting system. +// The function calculates the prorated linear depreciation of an asset for a +// specified accounting period. The syntax of the function is: +// +// AMORLINC(cost,date_purchased,first_period,salvage,period,rate,[basis]) +// +func (fn *formulaFuncs) AMORLINC(argsList *list.List) formulaArg { + if argsList.Len() != 6 && argsList.Len() != 7 { + return newErrorFormulaArg(formulaErrorVALUE, "AMORLINC requires 6 or 7 arguments") + } + args := fn.prepareAmorArgs("AMORLINC", argsList) + if args.Type != ArgList { + return args + } + cost, datePurchased, firstPeriod, salvage, period, rate, basis := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6] + frac := yearFrac(datePurchased.Number, firstPeriod.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + rate1 := frac.Number * cost.Number * rate.Number + if period.Number == 0 { + return newNumberFormulaArg(rate1) + } + rate2 := cost.Number * rate.Number + delta := cost.Number - salvage.Number + periods := int((delta - rate1) / rate2) + if int(period.Number) <= periods { + return newNumberFormulaArg(rate2) + } else if int(period.Number)-1 == periods { + return newNumberFormulaArg(delta - rate2*float64(periods) - math.Nextafter(rate1, rate1)) + } + return newNumberFormulaArg(0) +} + +// prepareCouponArgs checking and prepare arguments for the formula functions +// COUPDAYBS, COUPDAYS, COUPDAYSNC, COUPPCD, COUPNUM and COUPNCD. +func (fn *formulaFuncs) prepareCouponArgs(name string, argsList *list.List) formulaArg { + if argsList.Len() != 3 && argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 or 4 arguments", name)) + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + if settlement.Number >= maturity.Number { + return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name)) + } + frequency := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if frequency.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if !validateFrequency(frequency.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 4 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + return newListFormulaArg([]formulaArg{settlement, maturity, frequency, basis}) +} + +// is30BasisMethod determine if the financial day count basis rules is 30/360 +// methods. +func is30BasisMethod(basis int) bool { + return basis == 0 || basis == 4 +} + +// getDaysInMonthRange return the day by given year, month range and day count +// basis. +func getDaysInMonthRange(fromMonth, toMonth int) int { + if fromMonth > toMonth { + return 0 + } + return (toMonth - fromMonth + 1) * 30 +} + +// getDayOnBasis returns the day by given date and day count basis. +func getDayOnBasis(y, m, d, basis int) int { + if !is30BasisMethod(basis) { + return d + } + day := d + dim := getDaysInMonth(y, m) + if day > 30 || d >= dim || day >= dim { + day = 30 + } + return day +} + +// coupdays returns the number of days that base on date range and the day +// count basis to be used. +func coupdays(from, to time.Time, basis int) float64 { + days := 0 + fromY, fromM, fromD := from.Date() + toY, toM, toD := to.Date() + fromDay, toDay := getDayOnBasis(fromY, int(fromM), fromD, basis), getDayOnBasis(toY, int(toM), toD, basis) + if !is30BasisMethod(basis) { + return (daysBetween(excelMinTime1900.Unix(), makeDate(toY, toM, toDay)) + 1) - (daysBetween(excelMinTime1900.Unix(), makeDate(fromY, fromM, fromDay)) + 1) + } + if basis == 0 { + if (int(fromM) == 2 || fromDay < 30) && toD == 31 { + toDay = 31 + } + } else { + if int(fromM) == 2 && fromDay == 30 { + fromDay = getDaysInMonth(fromY, 2) + } + if int(toM) == 2 && toDay == 30 { + toDay = getDaysInMonth(toY, 2) + } + } + if fromY < toY || (fromY == toY && int(fromM) < int(toM)) { + days = 30 - fromDay + 1 + fromD = 1 + fromDay = 1 + date := time.Date(fromY, fromM, fromD, 0, 0, 0, 0, time.UTC).AddDate(0, 1, 0) + if date.Year() < toY { + days += getDaysInMonthRange(int(date.Month()), 12) + date = date.AddDate(0, 13-int(date.Month()), 0) + } + days += getDaysInMonthRange(int(date.Month()), int(toM)-1) + } + if days += toDay - fromDay; days > 0 { + return float64(days) + } + return 0 +} + +// COUPDAYBS function calculates the number of days from the beginning of a +// coupon's period to the settlement date. The syntax of the function is: +// +// COUPDAYBS(settlement,maturity,frequency,[basis]) +// +func (fn *formulaFuncs) COUPDAYBS(argsList *list.List) formulaArg { + args := fn.prepareCouponArgs("COUPDAYBS", argsList) + if args.Type != ArgList { + return args + } + settlement := timeFromExcelTime(args.List[0].Number, false) + pcd := timeFromExcelTime(fn.COUPPCD(argsList).Number, false) + return newNumberFormulaArg(coupdays(pcd, settlement, int(args.List[3].Number))) +} + +// COUPDAYS function calculates the number of days in a coupon period that +// contains the settlement date. The syntax of the function is: +// +// COUPDAYS(settlement,maturity,frequency,[basis]) +// +func (fn *formulaFuncs) COUPDAYS(argsList *list.List) formulaArg { + args := fn.prepareCouponArgs("COUPDAYS", argsList) + if args.Type != ArgList { + return args + } + freq := args.List[2].Number + basis := int(args.List[3].Number) + if basis == 1 { + pcd := timeFromExcelTime(fn.COUPPCD(argsList).Number, false) + next := pcd.AddDate(0, 12/int(freq), 0) + return newNumberFormulaArg(coupdays(pcd, next, basis)) + } + return newNumberFormulaArg(float64(getYearDays(0, basis)) / freq) +} + +// COUPDAYSNC function calculates the number of days from the settlement date +// to the next coupon date. The syntax of the function is: +// +// COUPDAYSNC(settlement,maturity,frequency,[basis]) +// +func (fn *formulaFuncs) COUPDAYSNC(argsList *list.List) formulaArg { + args := fn.prepareCouponArgs("COUPDAYSNC", argsList) + if args.Type != ArgList { + return args + } + settlement := timeFromExcelTime(args.List[0].Number, false) + basis := int(args.List[3].Number) + ncd := timeFromExcelTime(fn.COUPNCD(argsList).Number, false) + return newNumberFormulaArg(coupdays(settlement, ncd, basis)) +} + +// coupons is an implementation of the formula functions COUPNCD and COUPPCD. +func (fn *formulaFuncs) coupons(name string, arg formulaArg) formulaArg { + settlement := timeFromExcelTime(arg.List[0].Number, false) + maturity := timeFromExcelTime(arg.List[1].Number, false) + maturityDays := (maturity.Year()-settlement.Year())*12 + (int(maturity.Month()) - int(settlement.Month())) + coupon := 12 / int(arg.List[2].Number) + mod := maturityDays % coupon + year := settlement.Year() + month := int(settlement.Month()) + if mod == 0 && settlement.Day() >= maturity.Day() { + month += coupon + } else { + month += mod + } + if name != "COUPNCD" { + month -= coupon + } + if month > 11 { + year += 1 + month -= 12 + } else if month < 0 { + year -= 1 + month += 12 + } + day, lastDay := maturity.Day(), time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC) + days := getDaysInMonth(lastDay.Year(), int(lastDay.Month())) + if getDaysInMonth(maturity.Year(), int(maturity.Month())) == maturity.Day() { + day = days + } else if day > 27 && day > days { + day = days + } + return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), makeDate(year, time.Month(month), day)) + 1) +} + +// COUPNCD function calculates the number of coupons payable, between a +// security's settlement date and maturity date, rounded up to the nearest +// whole coupon. The syntax of the function is: +// +// COUPNCD(settlement,maturity,frequency,[basis]) +// +func (fn *formulaFuncs) COUPNCD(argsList *list.List) formulaArg { + args := fn.prepareCouponArgs("COUPNCD", argsList) + if args.Type != ArgList { + return args + } + return fn.coupons("COUPNCD", args) +} + +// COUPNUM function calculates the number of coupons payable, between a +// security's settlement date and maturity date, rounded up to the nearest +// whole coupon. The syntax of the function is: +// +// COUPNUM(settlement,maturity,frequency,[basis]) +// +func (fn *formulaFuncs) COUPNUM(argsList *list.List) formulaArg { + args := fn.prepareCouponArgs("COUPNUM", argsList) + if args.Type != ArgList { + return args + } + frac := yearFrac(args.List[0].Number, args.List[1].Number, 0) + return newNumberFormulaArg(math.Ceil(frac.Number * args.List[2].Number)) +} + +// COUPPCD function returns the previous coupon date, before the settlement +// date for a security. The syntax of the function is: +// +// COUPPCD(settlement,maturity,frequency,[basis]) +// +func (fn *formulaFuncs) COUPPCD(argsList *list.List) formulaArg { + args := fn.prepareCouponArgs("COUPPCD", argsList) + if args.Type != ArgList { + return args + } + return fn.coupons("COUPPCD", args) +} + +// CUMIPMT function calculates the cumulative interest paid on a loan or +// investment, between two specified periods. The syntax of the function is: +// +// CUMIPMT(rate,nper,pv,start_period,end_period,type) +// +func (fn *formulaFuncs) CUMIPMT(argsList *list.List) formulaArg { + return fn.cumip("CUMIPMT", argsList) +} + +// CUMPRINC function calculates the cumulative payment on the principal of a +// loan or investment, between two specified periods. The syntax of the +// function is: +// +// CUMPRINC(rate,nper,pv,start_period,end_period,type) +// +func (fn *formulaFuncs) CUMPRINC(argsList *list.List) formulaArg { + return fn.cumip("CUMPRINC", argsList) +} + +// cumip is an implementation of the formula functions CUMIPMT and CUMPRINC. +func (fn *formulaFuncs) cumip(name string, argsList *list.List) formulaArg { + if argsList.Len() != 6 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 arguments", name)) + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + nper := argsList.Front().Next().Value.(formulaArg).ToNumber() + if nper.Type != ArgNumber { + return nper + } + pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pv.Type != ArgNumber { + return pv + } + start := argsList.Back().Prev().Prev().Value.(formulaArg).ToNumber() + if start.Type != ArgNumber { + return start + } + end := argsList.Back().Prev().Value.(formulaArg).ToNumber() + if end.Type != ArgNumber { + return end + } + typ := argsList.Back().Value.(formulaArg).ToNumber() + if typ.Type != ArgNumber { + return typ + } + if typ.Number != 0 && typ.Number != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if start.Number < 1 || start.Number > end.Number { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + num := 0.0 + for per := start.Number; per <= end.Number; per++ { + args := list.New().Init() + args.PushBack(rate) + args.PushBack(newNumberFormulaArg(per)) + args.PushBack(nper) + args.PushBack(pv) + args.PushBack(newNumberFormulaArg(0)) + args.PushBack(typ) + if name == "CUMIPMT" { + num += fn.IPMT(args).Number + continue + } + num += fn.PPMT(args).Number + } + return newNumberFormulaArg(num) +} + +// calcDbArgsCompare implements common arguments' comparison for DB and DDB. +func calcDbArgsCompare(cost, salvage, life, period formulaArg) bool { + return (cost.Number <= 0) || ((salvage.Number / cost.Number) < 0) || (life.Number <= 0) || (period.Number < 1) +} + +// DB function calculates the depreciation of an asset, using the Fixed +// Declining Balance Method, for each period of the asset's lifetime. The +// syntax of the function is: +// +// DB(cost,salvage,life,period,[month]) +// +func (fn *formulaFuncs) DB(argsList *list.List) formulaArg { + if argsList.Len() < 4 { + return newErrorFormulaArg(formulaErrorVALUE, "DB requires at least 4 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "DB allows at most 5 arguments") + } + cost := argsList.Front().Value.(formulaArg).ToNumber() + if cost.Type != ArgNumber { + return cost + } + salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() + if salvage.Type != ArgNumber { + return salvage + } + life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if life.Type != ArgNumber { + return life + } + period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if period.Type != ArgNumber { + return period + } + month := newNumberFormulaArg(12) + if argsList.Len() == 5 { + if month = argsList.Back().Value.(formulaArg).ToNumber(); month.Type != ArgNumber { + return month + } + } + if cost.Number == 0 { + return newNumberFormulaArg(0) + } + if calcDbArgsCompare(cost, salvage, life, period) || (month.Number < 1) { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + dr := 1 - math.Pow(salvage.Number/cost.Number, 1/life.Number) + dr = math.Round(dr*1000) / 1000 + pd, depreciation := 0.0, 0.0 + for per := 1; per <= int(period.Number); per++ { + if per == 1 { + depreciation = cost.Number * dr * month.Number / 12 + } else if per == int(life.Number+1) { + depreciation = (cost.Number - pd) * dr * (12 - month.Number) / 12 + } else { + depreciation = (cost.Number - pd) * dr + } + pd += depreciation + } + return newNumberFormulaArg(depreciation) +} + +// DDB function calculates the depreciation of an asset, using the Double +// Declining Balance Method, or another specified depreciation rate. The +// syntax of the function is: +// +// DDB(cost,salvage,life,period,[factor]) +// +func (fn *formulaFuncs) DDB(argsList *list.List) formulaArg { + if argsList.Len() < 4 { + return newErrorFormulaArg(formulaErrorVALUE, "DDB requires at least 4 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "DDB allows at most 5 arguments") + } + cost := argsList.Front().Value.(formulaArg).ToNumber() + if cost.Type != ArgNumber { + return cost + } + salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() + if salvage.Type != ArgNumber { + return salvage + } + life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if life.Type != ArgNumber { + return life + } + period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if period.Type != ArgNumber { + return period + } + factor := newNumberFormulaArg(2) + if argsList.Len() == 5 { + if factor = argsList.Back().Value.(formulaArg).ToNumber(); factor.Type != ArgNumber { + return factor + } + } + if cost.Number == 0 { + return newNumberFormulaArg(0) + } + if calcDbArgsCompare(cost, salvage, life, period) || (factor.Number <= 0.0) || (period.Number > life.Number) { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + pd, depreciation := 0.0, 0.0 + for per := 1; per <= int(period.Number); per++ { + depreciation = math.Min((cost.Number-pd)*(factor.Number/life.Number), cost.Number-salvage.Number-pd) + pd += depreciation + } + return newNumberFormulaArg(depreciation) +} + +// prepareDataValueArgs convert first N arguments to data value for the +// formula functions. +func (fn *formulaFuncs) prepareDataValueArgs(n int, argsList *list.List) formulaArg { + l := list.New() + var dataValues []formulaArg + getDateValue := func(arg formulaArg, l *list.List) formulaArg { + switch arg.Type { + case ArgNumber: + break + case ArgString: + num := arg.ToNumber() + if num.Type == ArgNumber { + arg = num + break + } + l.Init() + l.PushBack(arg) + arg = fn.DATEVALUE(l) + if arg.Type == ArgError { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + default: + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + return arg + } + for i, arg := 0, argsList.Front(); i < n; arg = arg.Next() { + dataValue := getDateValue(arg.Value.(formulaArg), l) + if dataValue.Type != ArgNumber { + return dataValue + } + dataValues = append(dataValues, dataValue) + i++ + } + return newListFormulaArg(dataValues) +} + +// DISC function calculates the Discount Rate for a security. The syntax of +// the function is: +// +// DISC(settlement,maturity,pr,redemption,[basis]) +// +func (fn *formulaFuncs) DISC(argsList *list.List) formulaArg { + if argsList.Len() != 4 && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "DISC requires 4 or 5 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + if maturity.Number <= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "DISC requires maturity > settlement") + } + pr := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pr.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if pr.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "DISC requires pr > 0") + } + redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if redemption.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "DISC requires redemption > 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 5 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + return newNumberFormulaArg((redemption.Number - pr.Number) / redemption.Number / frac.Number) +} + +// DOLLARDE function converts a dollar value in fractional notation, into a +// dollar value expressed as a decimal. The syntax of the function is: +// +// DOLLARDE(fractional_dollar,fraction) +// +func (fn *formulaFuncs) DOLLARDE(argsList *list.List) formulaArg { + return fn.dollar("DOLLARDE", argsList) +} + +// DOLLARFR function converts a dollar value in decimal notation, into a +// dollar value that is expressed in fractional notation. The syntax of the +// function is: +// +// DOLLARFR(decimal_dollar,fraction) +// +func (fn *formulaFuncs) DOLLARFR(argsList *list.List) formulaArg { + return fn.dollar("DOLLARFR", argsList) +} + +// dollar is an implementation of the formula functions DOLLARDE and DOLLARFR. +func (fn *formulaFuncs) dollar(name string, argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name)) + } + dollar := argsList.Front().Value.(formulaArg).ToNumber() + if dollar.Type != ArgNumber { + return dollar + } + frac := argsList.Back().Value.(formulaArg).ToNumber() + if frac.Type != ArgNumber { + return frac + } + if frac.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if frac.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + cents := math.Mod(dollar.Number, 1) + if name == "DOLLARDE" { + cents /= frac.Number + cents *= math.Pow(10, math.Ceil(math.Log10(frac.Number))) + } else { + cents *= frac.Number + cents *= math.Pow(10, -math.Ceil(math.Log10(frac.Number))) + } + return newNumberFormulaArg(math.Floor(dollar.Number) + cents) +} + +// prepareDurationArgs checking and prepare arguments for the formula +// functions DURATION and MDURATION. +func (fn *formulaFuncs) prepareDurationArgs(name string, argsList *list.List) formulaArg { + if argsList.Len() != 5 && argsList.Len() != 6 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 5 or 6 arguments", name)) + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + if settlement.Number >= maturity.Number { + return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name)) + } + coupon := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if coupon.Type != ArgNumber { + return coupon + } + if coupon.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires coupon >= 0", name)) + } + yld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if yld.Type != ArgNumber { + return yld + } + if yld.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires yld >= 0", name)) + } + frequency := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if frequency.Type != ArgNumber { + return frequency + } + if !validateFrequency(frequency.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 6 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + return newListFormulaArg([]formulaArg{settlement, maturity, coupon, yld, frequency, basis}) +} + +// duration is an implementation of the formula function DURATION. +func (fn *formulaFuncs) duration(settlement, maturity, coupon, yld, frequency, basis formulaArg) formulaArg { + frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + argumments := list.New().Init() + argumments.PushBack(settlement) + argumments.PushBack(maturity) + argumments.PushBack(frequency) + argumments.PushBack(basis) + coups := fn.COUPNUM(argumments) + duration := 0.0 + p := 0.0 + coupon.Number *= 100 / frequency.Number + yld.Number /= frequency.Number + yld.Number++ + diff := frac.Number*frequency.Number - coups.Number + for t := 1.0; t < coups.Number; t++ { + tDiff := t + diff + add := coupon.Number / math.Pow(yld.Number, tDiff) + p += add + duration += tDiff * add + } + add := (coupon.Number + 100) / math.Pow(yld.Number, coups.Number+diff) + p += add + duration += (coups.Number + diff) * add + duration /= p + duration /= frequency.Number + return newNumberFormulaArg(duration) +} + +// DURATION function calculates the Duration (specifically, the Macaulay +// Duration) of a security that pays periodic interest, assuming a par value +// of $100. The syntax of the function is: +// +// DURATION(settlement,maturity,coupon,yld,frequency,[basis]) +// +func (fn *formulaFuncs) DURATION(argsList *list.List) formulaArg { + args := fn.prepareDurationArgs("DURATION", argsList) + if args.Type != ArgList { + return args + } + return fn.duration(args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]) +} + +// EFFECT function returns the effective annual interest rate for a given +// nominal interest rate and number of compounding periods per year. The +// syntax of the function is: +// +// EFFECT(nominal_rate,npery) +// +func (fn *formulaFuncs) EFFECT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "EFFECT requires 2 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + npery := argsList.Back().Value.(formulaArg).ToNumber() + if npery.Type != ArgNumber { + return npery + } + if rate.Number <= 0 || npery.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(math.Pow(1+rate.Number/npery.Number, npery.Number) - 1) +} + +// FV function calculates the Future Value of an investment with periodic +// constant payments and a constant interest rate. The syntax of the function +// is: +// +// FV(rate,nper,[pmt],[pv],[type]) +// +func (fn *formulaFuncs) FV(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "FV requires at least 3 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "FV allows at most 5 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + nper := argsList.Front().Next().Value.(formulaArg).ToNumber() + if nper.Type != ArgNumber { + return nper + } + pmt := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pmt.Type != ArgNumber { + return pmt + } + pv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0) + if argsList.Len() >= 4 { + if pv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); pv.Type != ArgNumber { + return pv + } + } + if argsList.Len() == 5 { + if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber { + return typ + } + } + if typ.Number != 0 && typ.Number != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if rate.Number != 0 { + return newNumberFormulaArg(-pv.Number*math.Pow(1+rate.Number, nper.Number) - pmt.Number*(1+rate.Number*typ.Number)*(math.Pow(1+rate.Number, nper.Number)-1)/rate.Number) + } + return newNumberFormulaArg(-pv.Number - pmt.Number*nper.Number) +} + +// FVSCHEDULE function calculates the Future Value of an investment with a +// variable interest rate. The syntax of the function is: +// +// FVSCHEDULE(principal,schedule) +// +func (fn *formulaFuncs) FVSCHEDULE(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "FVSCHEDULE requires 2 arguments") + } + pri := argsList.Front().Value.(formulaArg).ToNumber() + if pri.Type != ArgNumber { + return pri + } + principal := pri.Number + for _, arg := range argsList.Back().Value.(formulaArg).ToList() { + if arg.Value() == "" { + continue + } + rate := arg.ToNumber() + if rate.Type != ArgNumber { + return rate + } + principal *= 1 + rate.Number + } + return newNumberFormulaArg(principal) +} + +// INTRATE function calculates the interest rate for a fully invested +// security. The syntax of the function is: +// +// INTRATE(settlement,maturity,investment,redemption,[basis]) +// +func (fn *formulaFuncs) INTRATE(argsList *list.List) formulaArg { + if argsList.Len() != 4 && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "INTRATE requires 4 or 5 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + if maturity.Number <= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "INTRATE requires maturity > settlement") + } + investment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if investment.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if investment.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "INTRATE requires investment > 0") + } + redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if redemption.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "INTRATE requires redemption > 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 5 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + return newNumberFormulaArg((redemption.Number - investment.Number) / investment.Number / frac.Number) +} + +// IPMT function calculates the interest payment, during a specific period of a +// loan or investment that is paid in constant periodic payments, with a +// constant interest rate. The syntax of the function is: +// +// IPMT(rate,per,nper,pv,[fv],[type]) +// +func (fn *formulaFuncs) IPMT(argsList *list.List) formulaArg { + return fn.ipmt("IPMT", argsList) +} + +// calcIpmt is part of the implementation ipmt. +func calcIpmt(name string, typ, per, pmt, pv, rate formulaArg) formulaArg { + capital, interest, principal := pv.Number, 0.0, 0.0 + for i := 1; i <= int(per.Number); i++ { + if typ.Number != 0 && i == 1 { + interest = 0 + } else { + interest = -capital * rate.Number + } + principal = pmt.Number - interest + capital += principal + } + if name == "IPMT" { + return newNumberFormulaArg(interest) + } + return newNumberFormulaArg(principal) +} + +// ipmt is an implementation of the formula functions IPMT and PPMT. +func (fn *formulaFuncs) ipmt(name string, argsList *list.List) formulaArg { + if argsList.Len() < 4 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 4 arguments", name)) + } + if argsList.Len() > 6 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 6 arguments", name)) + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + per := argsList.Front().Next().Value.(formulaArg).ToNumber() + if per.Type != ArgNumber { + return per + } + nper := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if nper.Type != ArgNumber { + return nper + } + pv := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if pv.Type != ArgNumber { + return pv + } + fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0) + if argsList.Len() >= 5 { + if fv = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { + return fv + } + } + if argsList.Len() == 6 { + if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber { + return typ + } + } + if typ.Number != 0 && typ.Number != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if per.Number <= 0 || per.Number > nper.Number { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + args := list.New().Init() + args.PushBack(rate) + args.PushBack(nper) + args.PushBack(pv) + args.PushBack(fv) + args.PushBack(typ) + pmt := fn.PMT(args) + return calcIpmt(name, typ, per, pmt, pv, rate) +} + +// IRR function returns the Internal Rate of Return for a supplied series of +// periodic cash flows (i.e. an initial investment value and a series of net +// income values). The syntax of the function is: +// +// IRR(values,[guess]) +// +func (fn *formulaFuncs) IRR(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IRR requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IRR allows at most 2 arguments") + } + values, guess := argsList.Front().Value.(formulaArg).ToList(), newNumberFormulaArg(0.1) + if argsList.Len() > 1 { + if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber { + return guess + } + } + x1, x2 := newNumberFormulaArg(0), guess + args := list.New().Init() + args.PushBack(x1) + for _, v := range values { + args.PushBack(v) + } + f1 := fn.NPV(args) + args.Front().Value = x2 + f2 := fn.NPV(args) + for i := 0; i < maxFinancialIterations; i++ { + if f1.Number*f2.Number < 0 { + break + } + if math.Abs(f1.Number) < math.Abs(f2.Number) { + x1.Number += 1.6 * (x1.Number - x2.Number) + args.Front().Value = x1 + f1 = fn.NPV(args) + continue + } + x2.Number += 1.6 * (x2.Number - x1.Number) + args.Front().Value = x2 + f2 = fn.NPV(args) + } + if f1.Number*f2.Number > 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + args.Front().Value = x1 + f := fn.NPV(args) + var rtb, dx, xMid, fMid float64 + if f.Number < 0 { + rtb = x1.Number + dx = x2.Number - x1.Number + } else { + rtb = x2.Number + dx = x1.Number - x2.Number + } + for i := 0; i < maxFinancialIterations; i++ { + dx *= 0.5 + xMid = rtb + dx + args.Front().Value = newNumberFormulaArg(xMid) + fMid = fn.NPV(args).Number + if fMid <= 0 { + rtb = xMid + } + if math.Abs(fMid) < financialPrecision || math.Abs(dx) < financialPrecision { + break + } + } + return newNumberFormulaArg(xMid) +} + +// ISPMT function calculates the interest paid during a specific period of a +// loan or investment. The syntax of the function is: +// +// ISPMT(rate,per,nper,pv) +// +func (fn *formulaFuncs) ISPMT(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "ISPMT requires 4 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + per := argsList.Front().Next().Value.(formulaArg).ToNumber() + if per.Type != ArgNumber { + return per + } + nper := argsList.Back().Prev().Value.(formulaArg).ToNumber() + if nper.Type != ArgNumber { + return nper + } + pv := argsList.Back().Value.(formulaArg).ToNumber() + if pv.Type != ArgNumber { + return pv + } + pr, payment, num := pv.Number, pv.Number/nper.Number, 0.0 + for i := 0; i <= int(per.Number); i++ { + num = rate.Number * pr * -1 + pr -= payment + if i == int(nper.Number) { + num = 0 + } + } + return newNumberFormulaArg(num) +} + +// MDURATION function calculates the Modified Macaulay Duration of a security +// that pays periodic interest, assuming a par value of $100. The syntax of +// the function is: +// +// MDURATION(settlement,maturity,coupon,yld,frequency,[basis]) +// +func (fn *formulaFuncs) MDURATION(argsList *list.List) formulaArg { + args := fn.prepareDurationArgs("MDURATION", argsList) + if args.Type != ArgList { + return args + } + duration := fn.duration(args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]) + if duration.Type != ArgNumber { + return duration + } + return newNumberFormulaArg(duration.Number / (1 + args.List[3].Number/args.List[4].Number)) +} + +// MIRR function returns the Modified Internal Rate of Return for a supplied +// series of periodic cash flows (i.e. a set of values, which includes an +// initial investment value and a series of net income values). The syntax of +// the function is: +// +// MIRR(values,finance_rate,reinvest_rate) +// +func (fn *formulaFuncs) MIRR(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "MIRR requires 3 arguments") + } + values := argsList.Front().Value.(formulaArg).ToList() + financeRate := argsList.Front().Next().Value.(formulaArg).ToNumber() + if financeRate.Type != ArgNumber { + return financeRate + } + reinvestRate := argsList.Back().Value.(formulaArg).ToNumber() + if reinvestRate.Type != ArgNumber { + return reinvestRate + } + n, fr, rr, npvPos, npvNeg := len(values), 1+financeRate.Number, 1+reinvestRate.Number, 0.0, 0.0 + for i, v := range values { + val := v.ToNumber() + if val.Number >= 0 { + npvPos += val.Number / math.Pow(rr, float64(i)) + continue + } + npvNeg += val.Number / math.Pow(fr, float64(i)) + } + if npvNeg == 0 || npvPos == 0 || reinvestRate.Number <= -1 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(math.Pow(-npvPos*math.Pow(rr, float64(n))/(npvNeg*rr), 1/(float64(n)-1)) - 1) +} + +// NOMINAL function returns the nominal interest rate for a given effective +// interest rate and number of compounding periods per year. The syntax of +// the function is: +// +// NOMINAL(effect_rate,npery) +// +func (fn *formulaFuncs) NOMINAL(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "NOMINAL requires 2 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + npery := argsList.Back().Value.(formulaArg).ToNumber() + if npery.Type != ArgNumber { + return npery + } + if rate.Number <= 0 || npery.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(npery.Number * (math.Pow(rate.Number+1, 1/npery.Number) - 1)) +} + +// NPER function calculates the number of periods required to pay off a loan, +// for a constant periodic payment and a constant interest rate. The syntax +// of the function is: +// +// NPER(rate,pmt,pv,[fv],[type]) +// +func (fn *formulaFuncs) NPER(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "NPER requires at least 3 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "NPER allows at most 5 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + pmt := argsList.Front().Next().Value.(formulaArg).ToNumber() + if pmt.Type != ArgNumber { + return pmt + } + pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pv.Type != ArgNumber { + return pv + } + fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0) + if argsList.Len() >= 4 { + if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { + return fv + } + } + if argsList.Len() == 5 { + if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber { + return typ + } + } + if typ.Number != 0 && typ.Number != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if pmt.Number == 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if rate.Number != 0 { + p := math.Log((pmt.Number*(1+rate.Number*typ.Number)/rate.Number-fv.Number)/(pv.Number+pmt.Number*(1+rate.Number*typ.Number)/rate.Number)) / math.Log(1+rate.Number) + return newNumberFormulaArg(p) + } + return newNumberFormulaArg((-pv.Number - fv.Number) / pmt.Number) +} + +// NPV function calculates the Net Present Value of an investment, based on a +// supplied discount rate, and a series of future payments and income. The +// syntax of the function is: +// +// NPV(rate,value1,[value2],[value3],...) +// +func (fn *formulaFuncs) NPV(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "NPV requires at least 2 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + val, i := 0.0, 1 + for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() { + num := arg.Value.(formulaArg).ToNumber() + if num.Type != ArgNumber { + continue + } + val += num.Number / math.Pow(1+rate.Number, float64(i)) + i++ + } + return newNumberFormulaArg(val) +} + +// aggrBetween is a part of implementation of the formula function ODDFPRICE. +func aggrBetween(startPeriod, endPeriod float64, initialValue []float64, f func(acc []float64, index float64) []float64) []float64 { + var s []float64 + if startPeriod <= endPeriod { + for i := startPeriod; i <= endPeriod; i++ { + s = append(s, i) + } + } else { + for i := startPeriod; i >= endPeriod; i-- { + s = append(s, i) + } + } + return fold(f, initialValue, s) +} + +// fold is a part of implementation of the formula function ODDFPRICE. +func fold(f func(acc []float64, index float64) []float64, state []float64, source []float64) []float64 { + length, value := len(source), state + for index := 0; length > index; index++ { + value = f(value, source[index]) + } + return value +} + +// changeMonth is a part of implementation of the formula function ODDFPRICE. +func changeMonth(date time.Time, numMonths float64, returnLastMonth bool) time.Time { + offsetDay := 0 + if returnLastMonth && date.Day() == getDaysInMonth(date.Year(), int(date.Month())) { + offsetDay-- + } + newDate := date.AddDate(0, int(numMonths), offsetDay) + if returnLastMonth { + lastDay := getDaysInMonth(newDate.Year(), int(newDate.Month())) + return timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), makeDate(newDate.Year(), newDate.Month(), lastDay))+1, false) + } + return newDate +} + +// datesAggregate is a part of implementation of the formula function +// ODDFPRICE. +func datesAggregate(startDate, endDate time.Time, numMonths float64, f func(pcd, ncd time.Time) float64, acc float64, returnLastMonth bool) (time.Time, time.Time, float64) { + frontDate, trailingDate := startDate, endDate + s1 := frontDate.After(endDate) || frontDate.Equal(endDate) + s2 := endDate.After(frontDate) || endDate.Equal(frontDate) + stop := s2 + if numMonths > 0 { + stop = s1 + } + for !stop { + trailingDate = frontDate + frontDate = changeMonth(frontDate, numMonths, returnLastMonth) + fn := f(frontDate, trailingDate) + acc += fn + s1 = frontDate.After(endDate) || frontDate.Equal(endDate) + s2 = endDate.After(frontDate) || endDate.Equal(frontDate) + stop = s2 + if numMonths > 0 { + stop = s1 + } + } + return frontDate, trailingDate, acc +} + +// coupNumber is a part of implementation of the formula function ODDFPRICE. +func coupNumber(maturity, settlement, numMonths float64) float64 { + maturityTime, settlementTime := timeFromExcelTime(maturity, false), timeFromExcelTime(settlement, false) + my, mm, md := maturityTime.Year(), maturityTime.Month(), maturityTime.Day() + sy, sm, sd := settlementTime.Year(), settlementTime.Month(), settlementTime.Day() + couponsTemp, endOfMonthTemp := 0.0, getDaysInMonth(my, int(mm)) == md + endOfMonth := endOfMonthTemp + if !endOfMonthTemp && mm != 2 && md > 28 && md < getDaysInMonth(my, int(mm)) { + endOfMonth = getDaysInMonth(sy, int(sm)) == sd + } + startDate := changeMonth(settlementTime, 0, endOfMonth) + coupons := couponsTemp + if startDate.After(settlementTime) { + coupons++ + } + date := changeMonth(startDate, numMonths, endOfMonth) + f := func(pcd, ncd time.Time) float64 { + return 1 + } + _, _, result := datesAggregate(date, maturityTime, numMonths, f, coupons, endOfMonth) + return result +} + +// prepareOddfpriceArgs checking and prepare arguments for the formula +// function ODDFPRICE. +func (fn *formulaFuncs) prepareOddfpriceArgs(argsList *list.List) formulaArg { + dateValues := fn.prepareDataValueArgs(4, argsList) + if dateValues.Type != ArgList { + return dateValues + } + settlement, maturity, issue, firstCoupon := dateValues.List[0], dateValues.List[1], dateValues.List[2], dateValues.List[3] + if issue.Number >= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires settlement > issue") + } + if settlement.Number >= firstCoupon.Number { + return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires first_coupon > settlement") + } + if firstCoupon.Number >= maturity.Number { + return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires maturity > first_coupon") + } + rate := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + if rate.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires rate >= 0") + } + yld := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if yld.Type != ArgNumber { + return yld + } + if yld.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires yld >= 0") + } + redemption := argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return redemption + } + if redemption.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires redemption > 0") + } + frequency := argsList.Front().Next().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if frequency.Type != ArgNumber { + return frequency + } + if !validateFrequency(frequency.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 9 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + return newListFormulaArg([]formulaArg{settlement, maturity, issue, firstCoupon, rate, yld, redemption, frequency, basis}) +} + +// ODDFPRICE function calculates the price per $100 face value of a security +// with an odd (short or long) first period. The syntax of the function is: +// +// ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,[basis]) +// +func (fn *formulaFuncs) ODDFPRICE(argsList *list.List) formulaArg { + if argsList.Len() != 8 && argsList.Len() != 9 { + return newErrorFormulaArg(formulaErrorVALUE, "ODDFPRICE requires 8 or 9 arguments") + } + args := fn.prepareOddfpriceArgs(argsList) + if args.Type != ArgList { + return args + } + settlement, maturity, issue, firstCoupon, rate, yld, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7], args.List[8] + if basisArg.Number < 0 || basisArg.Number > 4 { + return newErrorFormulaArg(formulaErrorNUM, "invalid basis") + } + issueTime := timeFromExcelTime(issue.Number, false) + settlementTime := timeFromExcelTime(settlement.Number, false) + maturityTime := timeFromExcelTime(maturity.Number, false) + firstCouponTime := timeFromExcelTime(firstCoupon.Number, false) + basis := int(basisArg.Number) + monthDays := getDaysInMonth(maturityTime.Year(), int(maturityTime.Month())) + returnLastMonth := monthDays == maturityTime.Day() + numMonths := 12 / frequency.Number + numMonthsNeg := -numMonths + mat := changeMonth(maturityTime, numMonthsNeg, returnLastMonth) + pcd, _, _ := datesAggregate(mat, firstCouponTime, numMonthsNeg, func(d1, d2 time.Time) float64 { + return 0 + }, 0, returnLastMonth) + if !pcd.Equal(firstCouponTime) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + fnArgs := list.New().Init() + fnArgs.PushBack(settlement) + fnArgs.PushBack(maturity) + fnArgs.PushBack(frequency) + fnArgs.PushBack(basisArg) + e := fn.COUPDAYS(fnArgs) + n := fn.COUPNUM(fnArgs) + m := frequency.Number + dfc := coupdays(issueTime, firstCouponTime, basis) + if dfc < e.Number { + dsc := coupdays(settlementTime, firstCouponTime, basis) + a := coupdays(issueTime, settlementTime, basis) + x := yld.Number/m + 1 + y := dsc / e.Number + p1 := x + p3 := math.Pow(p1, n.Number-1+y) + term1 := redemption.Number / p3 + term2 := 100 * rate.Number / m * dfc / e.Number / math.Pow(p1, y) + f := func(acc []float64, index float64) []float64 { + return []float64{acc[0] + 100*rate.Number/m/math.Pow(p1, index-1+y)} + } + term3 := aggrBetween(2, math.Floor(n.Number), []float64{0}, f) + p2 := rate.Number / m + term4 := a / e.Number * p2 * 100 + return newNumberFormulaArg(term1 + term2 + term3[0] - term4) + } + fnArgs.Init() + fnArgs.PushBack(issue) + fnArgs.PushBack(firstCoupon) + fnArgs.PushBack(frequency) + nc := fn.COUPNUM(fnArgs) + lastCoupon := firstCoupon.Number + aggrFunc := func(acc []float64, index float64) []float64 { + lastCouponTime := timeFromExcelTime(lastCoupon, false) + earlyCoupon := daysBetween(excelMinTime1900.Unix(), makeDate(lastCouponTime.Year(), time.Month(float64(lastCouponTime.Month())+numMonthsNeg), lastCouponTime.Day())) + 1 + earlyCouponTime := timeFromExcelTime(earlyCoupon, false) + nl := e.Number + if basis == 1 { + nl = coupdays(earlyCouponTime, lastCouponTime, basis) + } + dci := coupdays(issueTime, lastCouponTime, basis) + if index > 1 { + dci = nl + } + startDate := earlyCoupon + if issue.Number > earlyCoupon { + startDate = issue.Number + } + endDate := lastCoupon + if settlement.Number < lastCoupon { + endDate = settlement.Number + } + startDateTime := timeFromExcelTime(startDate, false) + endDateTime := timeFromExcelTime(endDate, false) + a := coupdays(startDateTime, endDateTime, basis) + lastCoupon = earlyCoupon + dcnl := acc[0] + anl := acc[1] + return []float64{dcnl + dci/nl, anl + a/nl} + } + ag := aggrBetween(math.Floor(nc.Number), 1, []float64{0, 0}, aggrFunc) + dcnl, anl := ag[0], ag[1] + dsc := 0.0 + fnArgs.Init() + fnArgs.PushBack(settlement) + fnArgs.PushBack(firstCoupon) + fnArgs.PushBack(frequency) + if basis == 2 || basis == 3 { + d := timeFromExcelTime(fn.COUPNCD(fnArgs).Number, false) + dsc = coupdays(settlementTime, d, basis) + } else { + d := timeFromExcelTime(fn.COUPPCD(fnArgs).Number, false) + a := coupdays(d, settlementTime, basis) + dsc = e.Number - a + } + nq := coupNumber(firstCoupon.Number, settlement.Number, numMonths) + fnArgs.Init() + fnArgs.PushBack(firstCoupon) + fnArgs.PushBack(maturity) + fnArgs.PushBack(frequency) + fnArgs.PushBack(basisArg) + n = fn.COUPNUM(fnArgs) + x := yld.Number/m + 1 + y := dsc / e.Number + p1 := x + p3 := math.Pow(p1, y+nq+n.Number) + term1 := redemption.Number / p3 + term2 := 100 * rate.Number / m * dcnl / math.Pow(p1, nq+y) + f := func(acc []float64, index float64) []float64 { + return []float64{acc[0] + 100*rate.Number/m/math.Pow(p1, index+nq+y)} + } + term3 := aggrBetween(1, math.Floor(n.Number), []float64{0}, f) + term4 := 100 * rate.Number / m * anl + return newNumberFormulaArg(term1 + term2 + term3[0] - term4) +} + +// PDURATION function calculates the number of periods required for an +// investment to reach a specified future value. The syntax of the function +// is: +// +// PDURATION(rate,pv,fv) +// +func (fn *formulaFuncs) PDURATION(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "PDURATION requires 3 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + pv := argsList.Front().Next().Value.(formulaArg).ToNumber() + if pv.Type != ArgNumber { + return pv + } + fv := argsList.Back().Value.(formulaArg).ToNumber() + if fv.Type != ArgNumber { + return fv + } + if rate.Number <= 0 || pv.Number <= 0 || fv.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg((math.Log(fv.Number) - math.Log(pv.Number)) / math.Log(1+rate.Number)) +} + +// PMT function calculates the constant periodic payment required to pay off +// (or partially pay off) a loan or investment, with a constant interest +// rate, over a specified period. The syntax of the function is: +// +// PMT(rate,nper,pv,[fv],[type]) +// +func (fn *formulaFuncs) PMT(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "PMT requires at least 3 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "PMT allows at most 5 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + nper := argsList.Front().Next().Value.(formulaArg).ToNumber() + if nper.Type != ArgNumber { + return nper + } + pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pv.Type != ArgNumber { + return pv + } + fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0) + if argsList.Len() >= 4 { + if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { + return fv + } + } + if argsList.Len() == 5 { + if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber { + return typ + } + } + if typ.Number != 0 && typ.Number != 1 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + if rate.Number != 0 { + p := (-fv.Number - pv.Number*math.Pow(1+rate.Number, nper.Number)) / (1 + rate.Number*typ.Number) / ((math.Pow(1+rate.Number, nper.Number) - 1) / rate.Number) + return newNumberFormulaArg(p) + } + return newNumberFormulaArg((-pv.Number - fv.Number) / nper.Number) +} + +// PPMT function calculates the payment on the principal, during a specific +// period of a loan or investment that is paid in constant periodic payments, +// with a constant interest rate. The syntax of the function is: +// +// PPMT(rate,per,nper,pv,[fv],[type]) +// +func (fn *formulaFuncs) PPMT(argsList *list.List) formulaArg { + return fn.ipmt("PPMT", argsList) +} + +// price is an implementation of the formula function PRICE. +func (fn *formulaFuncs) price(settlement, maturity, rate, yld, redemption, frequency, basis formulaArg) formulaArg { + if basis.Number < 0 || basis.Number > 4 { + return newErrorFormulaArg(formulaErrorNUM, "invalid basis") + } + argsList := list.New().Init() + argsList.PushBack(settlement) + argsList.PushBack(maturity) + argsList.PushBack(frequency) + argsList.PushBack(basis) + e := fn.COUPDAYS(argsList) + dsc := fn.COUPDAYSNC(argsList).Number / e.Number + n := fn.COUPNUM(argsList) + a := fn.COUPDAYBS(argsList) + ret := 0.0 + if n.Number > 1 { + ret = redemption.Number / math.Pow(1+yld.Number/frequency.Number, n.Number-1+dsc) + ret -= 100 * rate.Number / frequency.Number * a.Number / e.Number + t1 := 100 * rate.Number / frequency.Number + t2 := 1 + yld.Number/frequency.Number + for k := 0.0; k < n.Number; k++ { + ret += t1 / math.Pow(t2, k+dsc) + } + } else { + dsc = e.Number - a.Number + t1 := 100*(rate.Number/frequency.Number) + redemption.Number + t2 := (yld.Number/frequency.Number)*(dsc/e.Number) + 1 + t3 := 100 * (rate.Number / frequency.Number) * (a.Number / e.Number) + ret = t1/t2 - t3 + } + return newNumberFormulaArg(ret) +} + +// PRICE function calculates the price, per $100 face value of a security that +// pays periodic interest. The syntax of the function is: +// +// PRICE(settlement,maturity,rate,yld,redemption,frequency,[basis]) +// +func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg { + if argsList.Len() != 6 && argsList.Len() != 7 { + return newErrorFormulaArg(formulaErrorVALUE, "PRICE requires 6 or 7 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + if rate.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires rate >= 0") + } + yld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if yld.Type != ArgNumber { + return yld + } + if yld.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires yld >= 0") + } + redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return redemption + } + if redemption.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption > 0") + } + frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if frequency.Type != ArgNumber { + return frequency + } + if !validateFrequency(frequency.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 7 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + return fn.price(settlement, maturity, rate, yld, redemption, frequency, basis) +} + +// PRICEDISC function calculates the price, per $100 face value of a +// discounted security. The syntax of the function is: +// +// PRICEDISC(settlement,maturity,discount,redemption,[basis]) +// +func (fn *formulaFuncs) PRICEDISC(argsList *list.List) formulaArg { + if argsList.Len() != 4 && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "PRICEDISC requires 4 or 5 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + if maturity.Number <= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires maturity > settlement") + } + discount := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if discount.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if discount.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires discount > 0") + } + redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if redemption.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICEDISC requires redemption > 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 5 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + return newNumberFormulaArg(redemption.Number * (1 - discount.Number*frac.Number)) +} + +// PRICEMAT function calculates the price, per $100 face value of a security +// that pays interest at maturity. The syntax of the function is: +// +// PRICEMAT(settlement,maturity,issue,rate,yld,[basis]) +// +func (fn *formulaFuncs) PRICEMAT(argsList *list.List) formulaArg { + if argsList.Len() != 5 && argsList.Len() != 6 { + return newErrorFormulaArg(formulaErrorVALUE, "PRICEMAT requires 5 or 6 arguments") + } + args := fn.prepareDataValueArgs(3, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity, issue := args.List[0], args.List[1], args.List[2] + if settlement.Number >= maturity.Number { + return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires maturity > settlement") + } + if issue.Number >= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires settlement > issue") + } + rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + if rate.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires rate >= 0") + } + yld := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if yld.Type != ArgNumber { + return yld + } + if yld.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICEMAT requires yld >= 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 6 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + dsm := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + if dsm.Type != ArgNumber { + return dsm + } + dis := yearFrac(issue.Number, settlement.Number, int(basis.Number)) + dim := yearFrac(issue.Number, maturity.Number, int(basis.Number)) + return newNumberFormulaArg(((1+dim.Number*rate.Number)/(1+dsm.Number*yld.Number) - dis.Number*rate.Number) * 100) +} + +// PV function calculates the Present Value of an investment, based on a +// series of future payments. The syntax of the function is: +// +// PV(rate,nper,pmt,[fv],[type]) +// +func (fn *formulaFuncs) PV(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "PV requires at least 3 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "PV allows at most 5 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + nper := argsList.Front().Next().Value.(formulaArg).ToNumber() + if nper.Type != ArgNumber { + return nper + } + pmt := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pmt.Type != ArgNumber { + return pmt + } + fv := newNumberFormulaArg(0) + if argsList.Len() >= 4 { + if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { + return fv + } + } + t := newNumberFormulaArg(0) + if argsList.Len() == 5 { + if t = argsList.Back().Value.(formulaArg).ToNumber(); t.Type != ArgNumber { + return t + } + if t.Number != 0 { + t.Number = 1 + } + } + if rate.Number == 0 { + return newNumberFormulaArg(-pmt.Number*nper.Number - fv.Number) + } + return newNumberFormulaArg((((1-math.Pow(1+rate.Number, nper.Number))/rate.Number)*pmt.Number*(1+rate.Number*t.Number) - fv.Number) / math.Pow(1+rate.Number, nper.Number)) +} + +// rate is an implementation of the formula function RATE. +func (fn *formulaFuncs) rate(nper, pmt, pv, fv, t, guess formulaArg) formulaArg { + maxIter, iter, isClose, epsMax, rate := 100, 0, false, 1e-6, guess.Number + for iter < maxIter && !isClose { + t1 := math.Pow(rate+1, nper.Number) + t2 := math.Pow(rate+1, nper.Number-1) + rt := rate*t.Number + 1 + p0 := pmt.Number * (t1 - 1) + f1 := fv.Number + t1*pv.Number + p0*rt/rate + n1 := nper.Number * t2 * pv.Number + n2 := p0 * rt / math.Pow(rate, 2) + f2 := math.Nextafter(n1, n1) - math.Nextafter(n2, n2) + f3 := (nper.Number*pmt.Number*t2*rt + p0*t.Number) / rate + delta := f1 / (f2 + f3) + if math.Abs(delta) < epsMax { + isClose = true + } + iter++ + rate -= delta + } + return newNumberFormulaArg(rate) +} + +// RATE function calculates the interest rate required to pay off a specified +// amount of a loan, or to reach a target amount on an investment, over a +// given period. The syntax of the function is: +// +// RATE(nper,pmt,pv,[fv],[type],[guess]) +// +func (fn *formulaFuncs) RATE(argsList *list.List) formulaArg { + if argsList.Len() < 3 { + return newErrorFormulaArg(formulaErrorVALUE, "RATE requires at least 3 arguments") + } + if argsList.Len() > 6 { + return newErrorFormulaArg(formulaErrorVALUE, "RATE allows at most 6 arguments") + } + nper := argsList.Front().Value.(formulaArg).ToNumber() + if nper.Type != ArgNumber { + return nper + } + pmt := argsList.Front().Next().Value.(formulaArg).ToNumber() + if pmt.Type != ArgNumber { + return pmt + } + pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pv.Type != ArgNumber { + return pv + } + fv := newNumberFormulaArg(0) + if argsList.Len() >= 4 { + if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber { + return fv + } + } + t := newNumberFormulaArg(0) + if argsList.Len() >= 5 { + if t = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); t.Type != ArgNumber { + return t + } + if t.Number != 0 { + t.Number = 1 + } + } + guess := newNumberFormulaArg(0.1) + if argsList.Len() == 6 { + if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber { + return guess + } + } + return fn.rate(nper, pmt, pv, fv, t, guess) +} + +// RECEIVED function calculates the amount received at maturity for a fully +// invested security. The syntax of the function is: +// +// RECEIVED(settlement,maturity,investment,discount,[basis]) +// +func (fn *formulaFuncs) RECEIVED(argsList *list.List) formulaArg { + if argsList.Len() < 4 { + return newErrorFormulaArg(formulaErrorVALUE, "RECEIVED requires at least 4 arguments") + } + if argsList.Len() > 5 { + return newErrorFormulaArg(formulaErrorVALUE, "RECEIVED allows at most 5 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + investment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if investment.Type != ArgNumber { + return investment + } + discount := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if discount.Type != ArgNumber { + return discount + } + if discount.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "RECEIVED requires discount > 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 5 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + return newNumberFormulaArg(investment.Number / (1 - discount.Number*frac.Number)) +} + +// RRI function calculates the equivalent interest rate for an investment with +// specified present value, future value and duration. The syntax of the +// function is: +// +// RRI(nper,pv,fv) +// +func (fn *formulaFuncs) RRI(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "RRI requires 3 arguments") + } + nper := argsList.Front().Value.(formulaArg).ToNumber() + pv := argsList.Front().Next().Value.(formulaArg).ToNumber() + fv := argsList.Back().Value.(formulaArg).ToNumber() + if nper.Type != ArgNumber || pv.Type != ArgNumber || fv.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if nper.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "RRI requires nper argument to be > 0") + } + if pv.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "RRI requires pv argument to be > 0") + } + if fv.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "RRI requires fv argument to be >= 0") + } + return newNumberFormulaArg(math.Pow(fv.Number/pv.Number, 1/nper.Number) - 1) +} + +// SLN function calculates the straight line depreciation of an asset for one +// period. The syntax of the function is: +// +// SLN(cost,salvage,life) +// +func (fn *formulaFuncs) SLN(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "SLN requires 3 arguments") + } + cost := argsList.Front().Value.(formulaArg).ToNumber() + salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() + life := argsList.Back().Value.(formulaArg).ToNumber() + if cost.Type != ArgNumber || salvage.Type != ArgNumber || life.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if life.Number == 0 { + return newErrorFormulaArg(formulaErrorNUM, "SLN requires life argument to be > 0") + } + return newNumberFormulaArg((cost.Number - salvage.Number) / life.Number) +} + +// SYD function calculates the sum-of-years' digits depreciation for a +// specified period in the lifetime of an asset. The syntax of the function +// is: +// +// SYD(cost,salvage,life,per) +// +func (fn *formulaFuncs) SYD(argsList *list.List) formulaArg { + if argsList.Len() != 4 { + return newErrorFormulaArg(formulaErrorVALUE, "SYD requires 4 arguments") + } + cost := argsList.Front().Value.(formulaArg).ToNumber() + salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() + life := argsList.Back().Prev().Value.(formulaArg).ToNumber() + per := argsList.Back().Value.(formulaArg).ToNumber() + if cost.Type != ArgNumber || salvage.Type != ArgNumber || life.Type != ArgNumber || per.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if life.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "SYD requires life argument to be > 0") + } + if per.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "SYD requires per argument to be > 0") + } + if per.Number > life.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(((cost.Number - salvage.Number) * (life.Number - per.Number + 1) * 2) / (life.Number * (life.Number + 1))) +} + +// TBILLEQ function calculates the bond-equivalent yield for a Treasury Bill. +// The syntax of the function is: +// +// TBILLEQ(settlement,maturity,discount) +// +func (fn *formulaFuncs) TBILLEQ(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "TBILLEQ requires 3 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + dsm := maturity.Number - settlement.Number + if dsm > 365 || maturity.Number <= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + discount := argsList.Back().Value.(formulaArg).ToNumber() + if discount.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if discount.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg((365 * discount.Number) / (360 - discount.Number*dsm)) +} + +// TBILLPRICE function returns the price, per $100 face value, of a Treasury +// Bill. The syntax of the function is: +// +// TBILLPRICE(settlement,maturity,discount) +// +func (fn *formulaFuncs) TBILLPRICE(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "TBILLPRICE requires 3 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + dsm := maturity.Number - settlement.Number + if dsm > 365 || maturity.Number <= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + discount := argsList.Back().Value.(formulaArg).ToNumber() + if discount.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if discount.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(100 * (1 - discount.Number*dsm/360)) +} + +// TBILLYIELD function calculates the yield of a Treasury Bill. The syntax of +// the function is: +// +// TBILLYIELD(settlement,maturity,pr) +// +func (fn *formulaFuncs) TBILLYIELD(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "TBILLYIELD requires 3 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + dsm := maturity.Number - settlement.Number + if dsm > 365 || maturity.Number <= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + pr := argsList.Back().Value.(formulaArg).ToNumber() + if pr.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if pr.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(((100 - pr.Number) / pr.Number) * (360 / dsm)) +} + +// prepareVdbArgs checking and prepare arguments for the formula function +// VDB. +func (fn *formulaFuncs) prepareVdbArgs(argsList *list.List) formulaArg { + cost := argsList.Front().Value.(formulaArg).ToNumber() + if cost.Type != ArgNumber { + return cost + } + if cost.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "VDB requires cost >= 0") + } + salvage := argsList.Front().Next().Value.(formulaArg).ToNumber() + if salvage.Type != ArgNumber { + return salvage + } + if salvage.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "VDB requires salvage >= 0") + } + life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if life.Type != ArgNumber { + return life + } + if life.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "VDB requires life > 0") + } + startPeriod := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if startPeriod.Type != ArgNumber { + return startPeriod + } + if startPeriod.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "VDB requires start_period > 0") + } + endPeriod := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if endPeriod.Type != ArgNumber { + return endPeriod + } + if startPeriod.Number > endPeriod.Number { + return newErrorFormulaArg(formulaErrorNUM, "VDB requires start_period <= end_period") + } + if endPeriod.Number > life.Number { + return newErrorFormulaArg(formulaErrorNUM, "VDB requires end_period <= life") + } + factor := newNumberFormulaArg(2) + if argsList.Len() > 5 { + if factor = argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); factor.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if factor.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, "VDB requires factor >= 0") + } + } + return newListFormulaArg([]formulaArg{cost, salvage, life, startPeriod, endPeriod, factor}) +} + +// vdb is a part of implementation of the formula function VDB. +func (fn *formulaFuncs) vdb(cost, salvage, life, life1, period, factor formulaArg) formulaArg { + var ddb, vdb, sln, term float64 + endInt, cs, nowSln := math.Ceil(period.Number), cost.Number-salvage.Number, false + ddbArgs := list.New() + for i := 1.0; i <= endInt; i++ { + if !nowSln { + ddbArgs.Init() + ddbArgs.PushBack(cost) + ddbArgs.PushBack(salvage) + ddbArgs.PushBack(life) + ddbArgs.PushBack(newNumberFormulaArg(i)) + ddbArgs.PushBack(factor) + ddb = fn.DDB(ddbArgs).Number + sln = cs / (life1.Number - i + 1) + if sln > ddb { + term = sln + nowSln = true + } else { + term = ddb + cs -= ddb + } + } else { + term = sln + } + if i == endInt { + term *= period.Number + 1 - endInt + } + vdb += term + } + return newNumberFormulaArg(vdb) +} + +// VDB function calculates the depreciation of an asset, using the Double +// Declining Balance Method, or another specified depreciation rate, for a +// specified period (including partial periods). The syntax of the function +// is: +// +// VDB(cost,salvage,life,start_period,end_period,[factor],[no_switch]) +// +func (fn *formulaFuncs) VDB(argsList *list.List) formulaArg { + if argsList.Len() < 5 || argsList.Len() > 7 { + return newErrorFormulaArg(formulaErrorVALUE, "VDB requires 5 or 7 arguments") + } + args := fn.prepareVdbArgs(argsList) + if args.Type != ArgList { + return args + } + cost, salvage, life, startPeriod, endPeriod, factor := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5] + noSwitch := newBoolFormulaArg(false) + if argsList.Len() > 6 { + if noSwitch = argsList.Back().Value.(formulaArg).ToBool(); noSwitch.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + startInt, endInt, vdb, ddbArgs := math.Floor(startPeriod.Number), math.Ceil(endPeriod.Number), newNumberFormulaArg(0), list.New() + if noSwitch.Number == 1 { + for i := startInt + 1; i <= endInt; i++ { + ddbArgs.Init() + ddbArgs.PushBack(cost) + ddbArgs.PushBack(salvage) + ddbArgs.PushBack(life) + ddbArgs.PushBack(newNumberFormulaArg(i)) + ddbArgs.PushBack(factor) + term := fn.DDB(ddbArgs) + if i == startInt+1 { + term.Number *= math.Min(endPeriod.Number, startInt+1) - startPeriod.Number + } else if i == endInt { + term.Number *= endPeriod.Number + 1 - endInt + } + vdb.Number += term.Number + } + return vdb + } + life1, part := life, 0.0 + if startPeriod.Number != math.Floor(startPeriod.Number) && factor.Number > 1.0 && startPeriod.Number >= life.Number/2.0 { + part = startPeriod.Number - life.Number/2.0 + startPeriod.Number = life.Number / 2.0 + endPeriod.Number -= part + } + cost.Number -= fn.vdb(cost, salvage, life, life1, startPeriod, factor).Number + return fn.vdb(cost, salvage, life, newNumberFormulaArg(life.Number-startPeriod.Number), newNumberFormulaArg(endPeriod.Number-startPeriod.Number), factor) +} + +// prepareXArgs prepare arguments for the formula function XIRR and XNPV. +func (fn *formulaFuncs) prepareXArgs(values, dates formulaArg) (valuesArg, datesArg []float64, err formulaArg) { + for _, arg := range values.ToList() { + if numArg := arg.ToNumber(); numArg.Type == ArgNumber { + valuesArg = append(valuesArg, numArg.Number) + continue + } + err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + return + } + if len(valuesArg) < 2 { + err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + return + } + args, date := list.New(), 0.0 + for _, arg := range dates.ToList() { + args.Init() + args.PushBack(arg) + dateValue := fn.DATEVALUE(args) + if dateValue.Type != ArgNumber { + err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + return + } + if dateValue.Number < date { + err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + return + } + datesArg = append(datesArg, dateValue.Number) + date = dateValue.Number + } + if len(valuesArg) != len(datesArg) { + err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + return + } + err = newEmptyFormulaArg() + return +} + +// xirr is an implementation of the formula function XIRR. +func (fn *formulaFuncs) xirr(values, dates []float64, guess float64) formulaArg { + positive, negative := false, false + for i := 0; i < len(values); i++ { + if values[i] > 0 { + positive = true + } + if values[i] < 0 { + negative = true + } + } + if !positive || !negative { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + result, epsMax, count, maxIterate, err := guess, 1e-10, 0, 50, false + for { + resultValue := xirrPart1(values, dates, result) + newRate := result - resultValue/xirrPart2(values, dates, result) + epsRate := math.Abs(newRate - result) + result = newRate + count++ + if epsRate <= epsMax || math.Abs(resultValue) <= epsMax { + break + } + if count > maxIterate { + err = true + break + } + } + if err || math.IsNaN(result) || math.IsInf(result, 0) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(result) +} + +// xirrPart1 is a part of implementation of the formula function XIRR. +func xirrPart1(values, dates []float64, rate float64) float64 { + r := rate + 1 + result := values[0] + vlen := len(values) + firstDate := dates[0] + for i := 1; i < vlen; i++ { + result += values[i] / math.Pow(r, (dates[i]-firstDate)/365) + } + return result +} + +// xirrPart2 is a part of implementation of the formula function XIRR. +func xirrPart2(values, dates []float64, rate float64) float64 { + r := rate + 1 + result := 0.0 + vlen := len(values) + firstDate := dates[0] + for i := 1; i < vlen; i++ { + frac := (dates[i] - firstDate) / 365 + result -= frac * values[i] / math.Pow(r, frac+1) + } + return result +} + +// XIRR function returns the Internal Rate of Return for a supplied series of +// cash flows (i.e. a set of values, which includes an initial investment +// value and a series of net income values) occurring at a series of supplied +// dates. The syntax of the function is: +// +// XIRR(values,dates,[guess]) +// +func (fn *formulaFuncs) XIRR(argsList *list.List) formulaArg { + if argsList.Len() != 2 && argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "XIRR requires 2 or 3 arguments") + } + values, dates, err := fn.prepareXArgs(argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg)) + if err.Type != ArgEmpty { + return err + } + guess := newNumberFormulaArg(0) + if argsList.Len() == 3 { + if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if guess.Number <= -1 { + return newErrorFormulaArg(formulaErrorVALUE, "XIRR requires guess > -1") + } + } + return fn.xirr(values, dates, guess.Number) +} + +// XNPV function calculates the Net Present Value for a schedule of cash flows +// that is not necessarily periodic. The syntax of the function is: +// +// XNPV(rate,values,dates) +// +func (fn *formulaFuncs) XNPV(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "XNPV requires 3 arguments") + } + rate := argsList.Front().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + if rate.Number <= 0 { + return newErrorFormulaArg(formulaErrorVALUE, "XNPV requires rate > 0") + } + values, dates, err := fn.prepareXArgs(argsList.Front().Next().Value.(formulaArg), argsList.Back().Value.(formulaArg)) + if err.Type != ArgEmpty { + return err + } + date1, xnpv := dates[0], 0.0 + for idx, value := range values { + xnpv += value / math.Pow(1+rate.Number, (dates[idx]-date1)/365) + } + return newNumberFormulaArg(xnpv) +} + +// yield is an implementation of the formula function YIELD. +func (fn *formulaFuncs) yield(settlement, maturity, rate, pr, redemption, frequency, basis formulaArg) formulaArg { + priceN, yield1, yield2 := newNumberFormulaArg(0), newNumberFormulaArg(0), newNumberFormulaArg(1) + price1 := fn.price(settlement, maturity, rate, yield1, redemption, frequency, basis) + if price1.Type != ArgNumber { + return price1 + } + price2 := fn.price(settlement, maturity, rate, yield2, redemption, frequency, basis) + yieldN := newNumberFormulaArg((yield2.Number - yield1.Number) * 0.5) + for iter := 0; iter < 100 && priceN.Number != pr.Number; iter++ { + priceN = fn.price(settlement, maturity, rate, yieldN, redemption, frequency, basis) + if pr.Number == price1.Number { + return yield1 + } else if pr.Number == price2.Number { + return yield2 + } else if pr.Number == priceN.Number { + return yieldN + } else if pr.Number < price2.Number { + yield2.Number *= 2.0 + price2 = fn.price(settlement, maturity, rate, yield2, redemption, frequency, basis) + yieldN.Number = (yield2.Number - yield1.Number) * 0.5 + } else { + if pr.Number < priceN.Number { + yield1 = yieldN + price1 = priceN + } else { + yield2 = yieldN + price2 = priceN + } + f1 := (yield2.Number - yield1.Number) * ((pr.Number - price2.Number) / (price1.Number - price2.Number)) + yieldN.Number = yield2.Number - math.Nextafter(f1, f1) + } + } + return yieldN +} + +// YIELD function calculates the Yield of a security that pays periodic +// interest. The syntax of the function is: +// +// YIELD(settlement,maturity,rate,pr,redemption,frequency,[basis]) +// +func (fn *formulaFuncs) YIELD(argsList *list.List) formulaArg { + if argsList.Len() != 6 && argsList.Len() != 7 { + return newErrorFormulaArg(formulaErrorVALUE, "YIELD requires 6 or 7 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + if rate.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires rate >= 0") + } + pr := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if pr.Type != ArgNumber { + return pr + } + if pr.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires pr > 0") + } + redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return redemption + } + if redemption.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption >= 0") + } + frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if frequency.Type != ArgNumber { + return frequency + } + if !validateFrequency(frequency.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 7 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + return fn.yield(settlement, maturity, rate, pr, redemption, frequency, basis) +} + +// YIELDDISC function calculates the annual yield of a discounted security. +// The syntax of the function is: +// +// YIELDDISC(settlement,maturity,pr,redemption,[basis]) +// +func (fn *formulaFuncs) YIELDDISC(argsList *list.List) formulaArg { + if argsList.Len() != 4 && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "YIELDDISC requires 4 or 5 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + pr := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pr.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if pr.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "YIELDDISC requires pr > 0") + } + redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if redemption.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "YIELDDISC requires redemption > 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 5 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + return newNumberFormulaArg((redemption.Number/pr.Number - 1) / frac.Number) +} + +// YIELDMAT function calculates the annual yield of a security that pays +// interest at maturity. The syntax of the function is: +// +// YIELDMAT(settlement,maturity,issue,rate,pr,[basis]) +// +func (fn *formulaFuncs) YIELDMAT(argsList *list.List) formulaArg { + if argsList.Len() != 5 && argsList.Len() != 6 { + return newErrorFormulaArg(formulaErrorVALUE, "YIELDMAT requires 5 or 6 arguments") + } + args := fn.prepareDataValueArgs(2, argsList) + if args.Type != ArgList { + return args + } + settlement, maturity := args.List[0], args.List[1] + arg := list.New().Init() + issue := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if issue.Type != ArgNumber { + arg.PushBack(argsList.Front().Next().Next().Value.(formulaArg)) + issue = fn.DATEVALUE(arg) + if issue.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + } + if issue.Number >= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires settlement > issue") + } + rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + if rate.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires rate >= 0") + } + pr := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if pr.Type != ArgNumber { + return pr + } + if pr.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires pr > 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 6 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + dim := yearFrac(issue.Number, maturity.Number, int(basis.Number)) + if dim.Type != ArgNumber { + return dim + } + dis := yearFrac(issue.Number, settlement.Number, int(basis.Number)) + dsm := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + f1 := dim.Number * rate.Number + result := 1 + math.Nextafter(f1, f1) + result /= pr.Number/100 + dis.Number*rate.Number + result-- + result /= dsm.Number + return newNumberFormulaArg(result) +} diff --git a/vendor/github.com/xuri/excelize/v2/calcchain.go b/vendor/github.com/xuri/excelize/v2/calcchain.go new file mode 100644 index 00000000..a1f9c0c5 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/calcchain.go @@ -0,0 +1,80 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/xml" + "io" + "log" +) + +// calcChainReader provides a function to get the pointer to the structure +// after deserialization of xl/calcChain.xml. +func (f *File) calcChainReader() *xlsxCalcChain { + var err error + + if f.CalcChain == nil { + f.CalcChain = new(xlsxCalcChain) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))). + Decode(f.CalcChain); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + } + + return f.CalcChain +} + +// calcChainWriter provides a function to save xl/calcChain.xml after +// serialize structure. +func (f *File) calcChainWriter() { + if f.CalcChain != nil && f.CalcChain.C != nil { + output, _ := xml.Marshal(f.CalcChain) + f.saveFileList(defaultXMLPathCalcChain, output) + } +} + +// deleteCalcChain provides a function to remove cell reference on the +// calculation chain. +func (f *File) deleteCalcChain(index int, axis string) { + calc := f.calcChainReader() + if calc != nil { + calc.C = xlsxCalcChainCollection(calc.C).Filter(func(c xlsxCalcChainC) bool { + return !((c.I == index && c.R == axis) || (c.I == index && axis == "")) + }) + } + if len(calc.C) == 0 { + f.CalcChain = nil + f.Pkg.Delete(defaultXMLPathCalcChain) + content := f.contentTypesReader() + content.Lock() + defer content.Unlock() + for k, v := range content.Overrides { + if v.PartName == "/xl/calcChain.xml" { + content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) + } + } + } +} + +type xlsxCalcChainCollection []xlsxCalcChainC + +// Filter provides a function to filter calculation chain. +func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCalcChainC { + var results []xlsxCalcChainC + for _, v := range c { + if fn(v) { + results = append(results, v) + } + } + return results +} diff --git a/vendor/github.com/xuri/excelize/v2/cell.go b/vendor/github.com/xuri/excelize/v2/cell.go new file mode 100644 index 00000000..b2818e7f --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/cell.go @@ -0,0 +1,1285 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/xml" + "fmt" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +// CellType is the type of cell value type. +type CellType byte + +// Cell value types enumeration. +const ( + CellTypeUnset CellType = iota + CellTypeBool + CellTypeDate + CellTypeError + CellTypeNumber + CellTypeString +) + +const ( + // STCellFormulaTypeArray defined the formula is an array formula. + STCellFormulaTypeArray = "array" + // STCellFormulaTypeDataTable defined the formula is a data table formula. + STCellFormulaTypeDataTable = "dataTable" + // STCellFormulaTypeNormal defined the formula is a regular cell formula. + STCellFormulaTypeNormal = "normal" + // STCellFormulaTypeShared defined the formula is part of a shared formula. + STCellFormulaTypeShared = "shared" +) + +// cellTypes mapping the cell's data type and enumeration. +var cellTypes = map[string]CellType{ + "b": CellTypeBool, + "d": CellTypeDate, + "n": CellTypeNumber, + "e": CellTypeError, + "s": CellTypeString, + "str": CellTypeString, + "inlineStr": CellTypeString, +} + +// GetCellValue provides a function to get formatted value from cell by given +// worksheet name and axis in spreadsheet file. If it is possible to apply a +// format to the cell value, it will do so, if not then an error will be +// returned, along with the raw value of the cell. All cells' values will be +// the same in a merged range. +func (f *File) GetCellValue(sheet, axis string, opts ...Options) (string, error) { + return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { + val, err := c.getValueFrom(f, f.sharedStringsReader(), parseOptions(opts...).RawCellValue) + return val, true, err + }) +} + +// GetCellType provides a function to get the cell's data type by given +// worksheet name and axis in spreadsheet file. +func (f *File) GetCellType(sheet, axis string) (CellType, error) { + var ( + err error + cellTypeStr string + cellType CellType + ) + if cellTypeStr, err = f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { + return c.T, true, nil + }); err != nil { + return CellTypeUnset, err + } + cellType = cellTypes[cellTypeStr] + return cellType, err +} + +// SetCellValue provides a function to set the value of a cell. The specified +// coordinates should not be in the first row of the table, a complex number +// can be set with string text. The following shows the supported data +// types: +// +// int +// int8 +// int16 +// int32 +// int64 +// uint +// uint8 +// uint16 +// uint32 +// uint64 +// float32 +// float64 +// string +// []byte +// time.Duration +// time.Time +// bool +// nil +// +// Note that default date format is m/d/yy h:mm of time.Time type value. You can +// set numbers format by SetCellStyle() method. +func (f *File) SetCellValue(sheet, axis string, value interface{}) error { + var err error + switch v := value.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + err = f.setCellIntFunc(sheet, axis, v) + case float32: + err = f.SetCellFloat(sheet, axis, float64(v), -1, 32) + case float64: + err = f.SetCellFloat(sheet, axis, v, -1, 64) + case string: + err = f.SetCellStr(sheet, axis, v) + case []byte: + err = f.SetCellStr(sheet, axis, string(v)) + case time.Duration: + _, d := setCellDuration(v) + err = f.SetCellDefault(sheet, axis, d) + if err != nil { + return err + } + err = f.setDefaultTimeStyle(sheet, axis, 21) + case time.Time: + err = f.setCellTimeFunc(sheet, axis, v) + case bool: + err = f.SetCellBool(sheet, axis, v) + case nil: + err = f.SetCellDefault(sheet, axis, "") + default: + err = f.SetCellStr(sheet, axis, fmt.Sprint(value)) + } + return err +} + +// String extracts characters from a string item. +func (x xlsxSI) String() string { + if len(x.R) > 0 { + var rows strings.Builder + for _, s := range x.R { + if s.T != nil { + rows.WriteString(s.T.Val) + } + } + return bstrUnmarshal(rows.String()) + } + if x.T != nil { + return bstrUnmarshal(x.T.Val) + } + return "" +} + +// hasValue determine if cell non-blank value. +func (c *xlsxC) hasValue() bool { + return c.S != 0 || c.V != "" || c.F != nil || c.T != "" +} + +// setCellIntFunc is a wrapper of SetCellInt. +func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error { + var err error + switch v := value.(type) { + case int: + err = f.SetCellInt(sheet, axis, v) + case int8: + err = f.SetCellInt(sheet, axis, int(v)) + case int16: + err = f.SetCellInt(sheet, axis, int(v)) + case int32: + err = f.SetCellInt(sheet, axis, int(v)) + case int64: + err = f.SetCellInt(sheet, axis, int(v)) + case uint: + err = f.SetCellInt(sheet, axis, int(v)) + case uint8: + err = f.SetCellInt(sheet, axis, int(v)) + case uint16: + err = f.SetCellInt(sheet, axis, int(v)) + case uint32: + err = f.SetCellInt(sheet, axis, int(v)) + case uint64: + err = f.SetCellInt(sheet, axis, int(v)) + } + return err +} + +// setCellTimeFunc provides a method to process time type of value for +// SetCellValue. +func (f *File) setCellTimeFunc(sheet, axis string, value time.Time) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + cellData, col, row, err := f.prepareCell(ws, axis) + if err != nil { + return err + } + ws.Lock() + cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) + ws.Unlock() + + var isNum bool + cellData.T, cellData.V, isNum, err = setCellTime(value) + if err != nil { + return err + } + if isNum { + _ = f.setDefaultTimeStyle(sheet, axis, 22) + } + return err +} + +// setCellTime prepares cell type and Excel time by given Go time.Time type +// timestamp. +func setCellTime(value time.Time) (t string, b string, isNum bool, err error) { + var excelTime float64 + _, offset := value.In(value.Location()).Zone() + value = value.Add(time.Duration(offset) * time.Second) + if excelTime, err = timeToExcelTime(value); err != nil { + return + } + isNum = excelTime > 0 + if isNum { + t, b = setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64)) + } else { + t, b = setCellDefault(value.Format(time.RFC3339Nano)) + } + return +} + +// setCellDuration prepares cell type and value by given Go time.Duration type +// time duration. +func setCellDuration(value time.Duration) (t string, v string) { + v = strconv.FormatFloat(value.Seconds()/86400.0, 'f', -1, 32) + return +} + +// SetCellInt provides a function to set int type value of a cell by given +// worksheet name, cell coordinates and cell value. +func (f *File) SetCellInt(sheet, axis string, value int) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + cellData, col, row, err := f.prepareCell(ws, axis) + if err != nil { + return err + } + ws.Lock() + defer ws.Unlock() + cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) + cellData.T, cellData.V = setCellInt(value) + return err +} + +// setCellInt prepares cell type and string type cell value by a given +// integer. +func setCellInt(value int) (t string, v string) { + v = strconv.Itoa(value) + return +} + +// SetCellBool provides a function to set bool type value of a cell by given +// worksheet name, cell name and cell value. +func (f *File) SetCellBool(sheet, axis string, value bool) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + cellData, col, row, err := f.prepareCell(ws, axis) + if err != nil { + return err + } + ws.Lock() + defer ws.Unlock() + cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) + cellData.T, cellData.V = setCellBool(value) + return err +} + +// setCellBool prepares cell type and string type cell value by a given +// boolean value. +func setCellBool(value bool) (t string, v string) { + t = "b" + if value { + v = "1" + } else { + v = "0" + } + return +} + +// SetCellFloat sets a floating point value into a cell. The precision parameter +// specifies how many places after the decimal will be shown while -1 is a +// special value that will use as many decimal places as necessary to +// represent the number. bitSize is 32 or 64 depending on if a float32 or +// float64 was originally used for the value. For Example: +// +// var x float32 = 1.325 +// f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32) +// +func (f *File) SetCellFloat(sheet, axis string, value float64, precision, bitSize int) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + cellData, col, row, err := f.prepareCell(ws, axis) + if err != nil { + return err + } + ws.Lock() + defer ws.Unlock() + cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) + cellData.T, cellData.V = setCellFloat(value, precision, bitSize) + return err +} + +// setCellFloat prepares cell type and string type cell value by a given +// float value. +func setCellFloat(value float64, precision, bitSize int) (t string, v string) { + v = strconv.FormatFloat(value, 'f', precision, bitSize) + return +} + +// SetCellStr provides a function to set string type value of a cell. Total +// number of characters that a cell can contain 32767 characters. +func (f *File) SetCellStr(sheet, axis, value string) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + cellData, col, row, err := f.prepareCell(ws, axis) + if err != nil { + return err + } + ws.Lock() + defer ws.Unlock() + cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) + cellData.T, cellData.V, err = f.setCellString(value) + return err +} + +// setCellString provides a function to set string type to shared string +// table. +func (f *File) setCellString(value string) (t, v string, err error) { + if len(value) > TotalCellChars { + value = value[:TotalCellChars] + } + t = "s" + var si int + if si, err = f.setSharedString(value); err != nil { + return + } + v = strconv.Itoa(si) + return +} + +// sharedStringsLoader load shared string table from system temporary file to +// memory, and reset shared string table for reader. +func (f *File) sharedStringsLoader() (err error) { + f.Lock() + defer f.Unlock() + if path, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok { + f.Pkg.Store(defaultXMLPathSharedStrings, f.readBytes(defaultXMLPathSharedStrings)) + f.tempFiles.Delete(defaultXMLPathSharedStrings) + if err = os.Remove(path.(string)); err != nil { + return + } + f.SharedStrings = nil + } + if f.sharedStringTemp != nil { + if err := f.sharedStringTemp.Close(); err != nil { + return err + } + f.tempFiles.Delete(defaultTempFileSST) + f.sharedStringItem, err = nil, os.Remove(f.sharedStringTemp.Name()) + f.sharedStringTemp = nil + } + return +} + +// setSharedString provides a function to add string to the share string table. +func (f *File) setSharedString(val string) (int, error) { + if err := f.sharedStringsLoader(); err != nil { + return 0, err + } + sst := f.sharedStringsReader() + f.Lock() + defer f.Unlock() + if i, ok := f.sharedStringsMap[val]; ok { + return i, nil + } + sst.Count++ + sst.UniqueCount++ + t := xlsxT{Val: val} + _, val, t.Space = setCellStr(val) + sst.SI = append(sst.SI, xlsxSI{T: &t}) + f.sharedStringsMap[val] = sst.UniqueCount - 1 + return sst.UniqueCount - 1, nil +} + +// setCellStr provides a function to set string type to cell. +func setCellStr(value string) (t string, v string, ns xml.Attr) { + if len(value) > TotalCellChars { + value = value[:TotalCellChars] + } + if len(value) > 0 { + prefix, suffix := value[0], value[len(value)-1] + for _, ascii := range []byte{9, 10, 13, 32} { + if prefix == ascii || suffix == ascii { + ns = xml.Attr{ + Name: xml.Name{Space: NameSpaceXML, Local: "space"}, + Value: "preserve", + } + break + } + } + } + t, v = "str", bstrMarshal(value) + return +} + +// SetCellDefault provides a function to set string type value of a cell as +// default format without escaping the cell. +func (f *File) SetCellDefault(sheet, axis, value string) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + cellData, col, row, err := f.prepareCell(ws, axis) + if err != nil { + return err + } + ws.Lock() + defer ws.Unlock() + cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) + cellData.T, cellData.V = setCellDefault(value) + return err +} + +// setCellDefault prepares cell type and string type cell value by a given +// string. +func setCellDefault(value string) (t string, v string) { + if ok, _ := isNumeric(value); !ok { + t = "str" + } + v = value + return +} + +// GetCellFormula provides a function to get formula from cell by given +// worksheet name and axis in XLSX file. +func (f *File) GetCellFormula(sheet, axis string) (string, error) { + return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { + if c.F == nil { + return "", false, nil + } + if c.F.T == STCellFormulaTypeShared && c.F.Si != nil { + return getSharedFormula(x, *c.F.Si, c.R), true, nil + } + return c.F.Content, true, nil + }) +} + +// FormulaOpts can be passed to SetCellFormula to use other formula types. +type FormulaOpts struct { + Type *string // Formula type + Ref *string // Shared formula ref +} + +// SetCellFormula provides a function to set formula on the cell is taken +// according to the given worksheet name (case-sensitive) and cell formula +// settings. The result of the formula cell can be calculated when the +// worksheet is opened by the Office Excel application or can be using +// the "CalcCellValue" function also can get the calculated cell value. If +// the Excel application doesn't calculate the formula automatically when the +// workbook has been opened, please call "UpdateLinkedValue" after setting +// the cell formula functions. +// +// Example 1, set normal formula "=SUM(A1,B1)" for the cell "A3" on "Sheet1": +// +// err := f.SetCellFormula("Sheet1", "A3", "=SUM(A1,B1)") +// +// Example 2, set one-dimensional vertical constant array (row array) formula +// "1,2,3" for the cell "A3" on "Sheet1": +// +// err := f.SetCellFormula("Sheet1", "A3", "={1,2,3}") +// +// Example 3, set one-dimensional horizontal constant array (column array) +// formula '"a","b","c"' for the cell "A3" on "Sheet1": +// +// err := f.SetCellFormula("Sheet1", "A3", "={\"a\",\"b\",\"c\"}") +// +// Example 4, set two-dimensional constant array formula '{1,2,"a","b"}' for +// the cell "A3" on "Sheet1": +// +// formulaType, ref := excelize.STCellFormulaTypeArray, "A3:A3" +// err := f.SetCellFormula("Sheet1", "A3", "={1,2,\"a\",\"b\"}", +// excelize.FormulaOpts{Ref: &ref, Type: &formulaType}) +// +// Example 5, set range array formula "A1:A2" for the cell "A3" on "Sheet1": +// +// formulaType, ref := excelize.STCellFormulaTypeArray, "A3:A3" +// err := f.SetCellFormula("Sheet1", "A3", "=A1:A2", +// excelize.FormulaOpts{Ref: &ref, Type: &formulaType}) +// +// Example 6, set shared formula "=A1+B1" for the cell "C1:C5" +// on "Sheet1", "C1" is the master cell: +// +// formulaType, ref := excelize.STCellFormulaTypeShared, "C1:C5" +// err := f.SetCellFormula("Sheet1", "C1", "=A1+B1", +// excelize.FormulaOpts{Ref: &ref, Type: &formulaType}) +// +// Example 7, set table formula "=SUM(Table1[[A]:[B]])" for the cell "C2" +// on "Sheet1": +// +// package main +// +// import ( +// "fmt" +// +// "github.com/xuri/excelize/v2" +// ) +// +// func main() { +// f := excelize.NewFile() +// for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} { +// if err := f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row); err != nil { +// fmt.Println(err) +// return +// } +// } +// if err := f.AddTable("Sheet1", "A1", "C2", +// `{"table_name":"Table1","table_style":"TableStyleMedium2"}`); err != nil { +// fmt.Println(err) +// return +// } +// formulaType := excelize.STCellFormulaTypeDataTable +// if err := f.SetCellFormula("Sheet1", "C2", "=SUM(Table1[[A]:[B]])", +// excelize.FormulaOpts{Type: &formulaType}); err != nil { +// fmt.Println(err) +// return +// } +// if err := f.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// } +// +func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + cellData, _, _, err := f.prepareCell(ws, axis) + if err != nil { + return err + } + if formula == "" { + cellData.F = nil + f.deleteCalcChain(f.getSheetID(sheet), axis) + return err + } + + if cellData.F != nil { + cellData.F.Content = formula + } else { + cellData.F = &xlsxF{Content: formula} + } + + for _, o := range opts { + if o.Type != nil { + if *o.Type == STCellFormulaTypeDataTable { + return err + } + cellData.F.T = *o.Type + if cellData.F.T == STCellFormulaTypeShared { + if err = ws.setSharedFormula(*o.Ref); err != nil { + return err + } + } + } + if o.Ref != nil { + cellData.F.Ref = *o.Ref + } + } + + return err +} + +// setSharedFormula set shared formula for the cells. +func (ws *xlsxWorksheet) setSharedFormula(ref string) error { + coordinates, err := areaRefToCoordinates(ref) + if err != nil { + return err + } + _ = sortCoordinates(coordinates) + cnt := ws.countSharedFormula() + for c := coordinates[0]; c <= coordinates[2]; c++ { + for r := coordinates[1]; r <= coordinates[3]; r++ { + prepareSheetXML(ws, c, r) + cell := &ws.SheetData.Row[r-1].C[c-1] + if cell.F == nil { + cell.F = &xlsxF{} + } + cell.F.T = STCellFormulaTypeShared + cell.F.Si = &cnt + } + } + return err +} + +// countSharedFormula count shared formula in the given worksheet. +func (ws *xlsxWorksheet) countSharedFormula() (count int) { + for _, row := range ws.SheetData.Row { + for _, cell := range row.C { + if cell.F != nil && cell.F.Si != nil && *cell.F.Si+1 > count { + count = *cell.F.Si + 1 + } + } + } + return +} + +// GetCellHyperLink provides a function to get cell hyperlink by given +// worksheet name and axis. Boolean type value link will be true if the cell +// has a hyperlink and the target is the address of the hyperlink. Otherwise, +// the value of link will be false and the value of the target will be a blank +// string. For example get hyperlink of Sheet1!H6: +// +// link, target, err := f.GetCellHyperLink("Sheet1", "H6") +// +func (f *File) GetCellHyperLink(sheet, axis string) (bool, string, error) { + // Check for correct cell name + if _, _, err := SplitCellName(axis); err != nil { + return false, "", err + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return false, "", err + } + if axis, err = f.mergeCellsParser(ws, axis); err != nil { + return false, "", err + } + if ws.Hyperlinks != nil { + for _, link := range ws.Hyperlinks.Hyperlink { + if link.Ref == axis { + if link.RID != "" { + return true, f.getSheetRelationshipsTargetByID(sheet, link.RID), err + } + return true, link.Location, err + } + } + } + return false, "", err +} + +// HyperlinkOpts can be passed to SetCellHyperlink to set optional hyperlink +// attributes (e.g. display value) +type HyperlinkOpts struct { + Display *string + Tooltip *string +} + +// SetCellHyperLink provides a function to set cell hyperlink by given +// worksheet name and link URL address. LinkType defines two types of +// hyperlink "External" for website or "Location" for moving to one of cell in +// this workbook. Maximum limit hyperlinks in a worksheet is 65530. This +// function is only used to set the hyperlink of the cell and doesn't affect +// the value of the cell. If you need to set the value of the cell, please use +// the other functions such as `SetCellStyle` or `SetSheetRow`. The below is +// example for external link. +// +// if err := f.SetCellHyperLink("Sheet1", "A3", +// "https://github.com/xuri/excelize", "External"); err != nil { +// fmt.Println(err) +// } +// // Set underline and font color style for the cell. +// style, err := f.NewStyle(&excelize.Style{ +// Font: &excelize.Font{Color: "#1265BE", Underline: "single"}, +// }) +// if err != nil { +// fmt.Println(err) +// } +// err = f.SetCellStyle("Sheet1", "A3", "A3", style) +// +// This is another example for "Location": +// +// err := f.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location") +// +func (f *File) SetCellHyperLink(sheet, axis, link, linkType string, opts ...HyperlinkOpts) error { + // Check for correct cell name + if _, _, err := SplitCellName(axis); err != nil { + return err + } + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if axis, err = f.mergeCellsParser(ws, axis); err != nil { + return err + } + + var linkData xlsxHyperlink + + if ws.Hyperlinks == nil { + ws.Hyperlinks = new(xlsxHyperlinks) + } + + if len(ws.Hyperlinks.Hyperlink) > TotalSheetHyperlinks { + return ErrTotalSheetHyperlinks + } + + switch linkType { + case "External": + linkData = xlsxHyperlink{ + Ref: axis, + } + sheetPath := f.sheetMap[trimSheetName(sheet)] + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipHyperLink, link, linkType) + linkData.RID = "rId" + strconv.Itoa(rID) + f.addSheetNameSpace(sheet, SourceRelationship) + case "Location": + linkData = xlsxHyperlink{ + Ref: axis, + Location: link, + } + default: + return fmt.Errorf("invalid link type %q", linkType) + } + + for _, o := range opts { + if o.Display != nil { + linkData.Display = *o.Display + } + if o.Tooltip != nil { + linkData.Tooltip = *o.Tooltip + } + } + + ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink, linkData) + return nil +} + +// GetCellRichText provides a function to get rich text of cell by given +// worksheet. +func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err error) { + ws, err := f.workSheetReader(sheet) + if err != nil { + return + } + cellData, _, _, err := f.prepareCell(ws, cell) + if err != nil { + return + } + siIdx, err := strconv.Atoi(cellData.V) + if nil != err { + return + } + sst := f.sharedStringsReader() + if len(sst.SI) <= siIdx || siIdx < 0 { + return + } + si := sst.SI[siIdx] + for _, v := range si.R { + run := RichTextRun{ + Text: v.T.Val, + } + if nil != v.RPr { + font := Font{Underline: "none"} + font.Bold = v.RPr.B != nil + font.Italic = v.RPr.I != nil + if v.RPr.U != nil { + font.Underline = "single" + if v.RPr.U.Val != nil { + font.Underline = *v.RPr.U.Val + } + } + if v.RPr.RFont != nil && v.RPr.RFont.Val != nil { + font.Family = *v.RPr.RFont.Val + } + if v.RPr.Sz != nil && v.RPr.Sz.Val != nil { + font.Size = *v.RPr.Sz.Val + } + font.Strike = v.RPr.Strike != nil + if nil != v.RPr.Color { + font.Color = strings.TrimPrefix(v.RPr.Color.RGB, "FF") + } + run.Font = &font + } + runs = append(runs, run) + } + return +} + +// newRpr create run properties for the rich text by given font format. +func newRpr(fnt *Font) *xlsxRPr { + rpr := xlsxRPr{} + trueVal := "" + if fnt.Bold { + rpr.B = &trueVal + } + if fnt.Italic { + rpr.I = &trueVal + } + if fnt.Strike { + rpr.Strike = &trueVal + } + if fnt.Underline != "" { + rpr.U = &attrValString{Val: &fnt.Underline} + } + if fnt.Family != "" { + rpr.RFont = &attrValString{Val: &fnt.Family} + } + if fnt.Size > 0.0 { + rpr.Sz = &attrValFloat{Val: &fnt.Size} + } + if fnt.Color != "" { + rpr.Color = &xlsxColor{RGB: getPaletteColor(fnt.Color)} + } + return &rpr +} + +// SetCellRichText provides a function to set cell with rich text by given +// worksheet. For example, set rich text on the A1 cell of the worksheet named +// Sheet1: +// +// package main +// +// import ( +// "fmt" +// +// "github.com/xuri/excelize/v2" +// ) +// +// func main() { +// f := excelize.NewFile() +// if err := f.SetRowHeight("Sheet1", 1, 35); err != nil { +// fmt.Println(err) +// return +// } +// if err := f.SetColWidth("Sheet1", "A", "A", 44); err != nil { +// fmt.Println(err) +// return +// } +// if err := f.SetCellRichText("Sheet1", "A1", []excelize.RichTextRun{ +// { +// Text: "bold", +// Font: &excelize.Font{ +// Bold: true, +// Color: "2354e8", +// Family: "Times New Roman", +// }, +// }, +// { +// Text: " and ", +// Font: &excelize.Font{ +// Family: "Times New Roman", +// }, +// }, +// { +// Text: " italic", +// Font: &excelize.Font{ +// Bold: true, +// Color: "e83723", +// Italic: true, +// Family: "Times New Roman", +// }, +// }, +// { +// Text: "text with color and font-family,", +// Font: &excelize.Font{ +// Bold: true, +// Color: "2354e8", +// Family: "Times New Roman", +// }, +// }, +// { +// Text: "\r\nlarge text with ", +// Font: &excelize.Font{ +// Size: 14, +// Color: "ad23e8", +// }, +// }, +// { +// Text: "strike", +// Font: &excelize.Font{ +// Color: "e89923", +// Strike: true, +// }, +// }, +// { +// Text: " and ", +// Font: &excelize.Font{ +// Size: 14, +// Color: "ad23e8", +// }, +// }, +// { +// Text: "underline.", +// Font: &excelize.Font{ +// Color: "23e833", +// Underline: "single", +// }, +// }, +// }); err != nil { +// fmt.Println(err) +// return +// } +// style, err := f.NewStyle(&excelize.Style{ +// Alignment: &excelize.Alignment{ +// WrapText: true, +// }, +// }) +// if err != nil { +// fmt.Println(err) +// return +// } +// if err := f.SetCellStyle("Sheet1", "A1", "A1", style); err != nil { +// fmt.Println(err) +// return +// } +// if err := f.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// } +// +func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + cellData, col, row, err := f.prepareCell(ws, cell) + if err != nil { + return err + } + if err := f.sharedStringsLoader(); err != nil { + return err + } + cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) + si := xlsxSI{} + sst := f.sharedStringsReader() + var textRuns []xlsxR + totalCellChars := 0 + for _, textRun := range runs { + totalCellChars += len(textRun.Text) + if totalCellChars > TotalCellChars { + return ErrCellCharsLength + } + run := xlsxR{T: &xlsxT{}} + _, run.T.Val, run.T.Space = setCellStr(textRun.Text) + fnt := textRun.Font + if fnt != nil { + run.RPr = newRpr(fnt) + } + textRuns = append(textRuns, run) + } + si.R = textRuns + for idx, strItem := range sst.SI { + if reflect.DeepEqual(strItem, si) { + cellData.T, cellData.V = "s", strconv.Itoa(idx) + return err + } + } + sst.SI = append(sst.SI, si) + sst.Count++ + sst.UniqueCount++ + cellData.T, cellData.V = "s", strconv.Itoa(len(sst.SI)-1) + return err +} + +// SetSheetRow writes an array to row by given worksheet name, starting +// coordinate and a pointer to array type 'slice'. For example, writes an +// array to row 6 start with the cell B6 on Sheet1: +// +// err := f.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2}) +// +func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error { + col, row, err := CellNameToCoordinates(axis) + if err != nil { + return err + } + + // Make sure 'slice' is a Ptr to Slice + v := reflect.ValueOf(slice) + if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice { + return ErrParameterInvalid + } + v = v.Elem() + + for i := 0; i < v.Len(); i++ { + cell, err := CoordinatesToCellName(col+i, row) + // Error should never happen here. But keep checking to early detect regressions + // if it will be introduced in the future. + if err != nil { + return err + } + if err := f.SetCellValue(sheet, cell, v.Index(i).Interface()); err != nil { + return err + } + } + return err +} + +// getCellInfo does common preparation for all SetCell* methods. +func (f *File) prepareCell(ws *xlsxWorksheet, cell string) (*xlsxC, int, int, error) { + var err error + cell, err = f.mergeCellsParser(ws, cell) + if err != nil { + return nil, 0, 0, err + } + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return nil, 0, 0, err + } + + prepareSheetXML(ws, col, row) + ws.Lock() + defer ws.Unlock() + return &ws.SheetData.Row[row-1].C[col-1], col, row, err +} + +// getCellStringFunc does common value extraction workflow for all GetCell* +// methods. Passed function implements specific part of required logic. +func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c *xlsxC) (string, bool, error)) (string, error) { + ws, err := f.workSheetReader(sheet) + if err != nil { + return "", err + } + axis, err = f.mergeCellsParser(ws, axis) + if err != nil { + return "", err + } + _, row, err := CellNameToCoordinates(axis) + if err != nil { + return "", err + } + + ws.Lock() + defer ws.Unlock() + + lastRowNum := 0 + if l := len(ws.SheetData.Row); l > 0 { + lastRowNum = ws.SheetData.Row[l-1].R + } + + // keep in mind: row starts from 1 + if row > lastRowNum { + return "", nil + } + + for rowIdx := range ws.SheetData.Row { + rowData := &ws.SheetData.Row[rowIdx] + if rowData.R != row { + continue + } + for colIdx := range rowData.C { + colData := &rowData.C[colIdx] + if axis != colData.R { + continue + } + val, ok, err := fn(ws, colData) + if err != nil { + return "", err + } + if ok { + return val, nil + } + } + } + return "", nil +} + +// formattedValue provides a function to returns a value after formatted. If +// it is possible to apply a format to the cell value, it will do so, if not +// then an error will be returned, along with the raw value of the cell. +func (f *File) formattedValue(s int, v string, raw bool) string { + if raw { + return v + } + if s == 0 { + return v + } + styleSheet := f.stylesReader() + if s >= len(styleSheet.CellXfs.Xf) { + return v + } + var numFmtID int + if styleSheet.CellXfs.Xf[s].NumFmtID != nil { + numFmtID = *styleSheet.CellXfs.Xf[s].NumFmtID + } + + ok := builtInNumFmtFunc[numFmtID] + if ok != nil { + return ok(v, builtInNumFmt[numFmtID]) + } + if styleSheet == nil || styleSheet.NumFmts == nil { + return v + } + for _, xlsxFmt := range styleSheet.NumFmts.NumFmt { + if xlsxFmt.NumFmtID == numFmtID { + return format(v, xlsxFmt.FormatCode) + } + } + return v +} + +// prepareCellStyle provides a function to prepare style index of cell in +// worksheet by given column index and style index. +func (f *File) prepareCellStyle(ws *xlsxWorksheet, col, row, style int) int { + if ws.Cols != nil && style == 0 { + for _, c := range ws.Cols.Col { + if c.Min <= col && col <= c.Max && c.Style != 0 { + return c.Style + } + } + } + for rowIdx := range ws.SheetData.Row { + if styleID := ws.SheetData.Row[rowIdx].S; style == 0 && styleID != 0 { + return styleID + } + } + return style +} + +// mergeCellsParser provides a function to check merged cells in worksheet by +// given axis. +func (f *File) mergeCellsParser(ws *xlsxWorksheet, axis string) (string, error) { + axis = strings.ToUpper(axis) + if ws.MergeCells != nil { + for i := 0; i < len(ws.MergeCells.Cells); i++ { + if ws.MergeCells.Cells[i] == nil { + ws.MergeCells.Cells = append(ws.MergeCells.Cells[:i], ws.MergeCells.Cells[i+1:]...) + i-- + continue + } + ok, err := f.checkCellInArea(axis, ws.MergeCells.Cells[i].Ref) + if err != nil { + return axis, err + } + if ok { + axis = strings.Split(ws.MergeCells.Cells[i].Ref, ":")[0] + } + } + } + return axis, nil +} + +// checkCellInArea provides a function to determine if a given coordinate is +// within an area. +func (f *File) checkCellInArea(cell, area string) (bool, error) { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return false, err + } + + if rng := strings.Split(area, ":"); len(rng) != 2 { + return false, err + } + coordinates, err := areaRefToCoordinates(area) + if err != nil { + return false, err + } + + return cellInRef([]int{col, row}, coordinates), err +} + +// cellInRef provides a function to determine if a given range is within a +// range. +func cellInRef(cell, ref []int) bool { + return cell[0] >= ref[0] && cell[0] <= ref[2] && cell[1] >= ref[1] && cell[1] <= ref[3] +} + +// isOverlap find if the given two rectangles overlap or not. +func isOverlap(rect1, rect2 []int) bool { + return cellInRef([]int{rect1[0], rect1[1]}, rect2) || + cellInRef([]int{rect1[2], rect1[1]}, rect2) || + cellInRef([]int{rect1[0], rect1[3]}, rect2) || + cellInRef([]int{rect1[2], rect1[3]}, rect2) || + cellInRef([]int{rect2[0], rect2[1]}, rect1) || + cellInRef([]int{rect2[2], rect2[1]}, rect1) || + cellInRef([]int{rect2[0], rect2[3]}, rect1) || + cellInRef([]int{rect2[2], rect2[3]}, rect1) +} + +// parseSharedFormula generate dynamic part of shared formula for target cell +// by given column and rows distance and origin shared formula. +func parseSharedFormula(dCol, dRow int, orig []byte) (res string, start int) { + var ( + end int + stringLiteral bool + ) + for end = 0; end < len(orig); end++ { + c := orig[end] + if c == '"' { + stringLiteral = !stringLiteral + } + if stringLiteral { + continue // Skip characters in quotes + } + if c >= 'A' && c <= 'Z' || c == '$' { + res += string(orig[start:end]) + start = end + end++ + foundNum := false + for ; end < len(orig); end++ { + idc := orig[end] + if idc >= '0' && idc <= '9' || idc == '$' { + foundNum = true + } else if idc >= 'A' && idc <= 'Z' { + if foundNum { + break + } + } else { + break + } + } + if foundNum { + cellID := string(orig[start:end]) + res += shiftCell(cellID, dCol, dRow) + start = end + } + } + } + return +} + +// getSharedFormula find a cell contains the same formula as another cell, +// the "shared" value can be used for the t attribute and the si attribute can +// be used to refer to the cell containing the formula. Two formulas are +// considered to be the same when their respective representations in +// R1C1-reference notation, are the same. +// +// Note that this function not validate ref tag to check the cell whether in +// allow area, and always return origin shared formula. +func getSharedFormula(ws *xlsxWorksheet, si int, axis string) string { + for _, r := range ws.SheetData.Row { + for _, c := range r.C { + if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si != nil && *c.F.Si == si { + col, row, _ := CellNameToCoordinates(axis) + sharedCol, sharedRow, _ := CellNameToCoordinates(c.R) + dCol := col - sharedCol + dRow := row - sharedRow + orig := []byte(c.F.Content) + res, start := parseSharedFormula(dCol, dRow, orig) + if start < len(orig) { + res += string(orig[start:]) + } + return res + } + } + } + return "" +} + +// shiftCell returns the cell shifted according to dCol and dRow taking into +// consideration absolute references with dollar sign ($) +func shiftCell(cellID string, dCol, dRow int) string { + fCol, fRow, _ := CellNameToCoordinates(cellID) + signCol, signRow := "", "" + if strings.Index(cellID, "$") == 0 { + signCol = "$" + } else { + // Shift column + fCol += dCol + } + if strings.LastIndex(cellID, "$") > 0 { + signRow = "$" + } else { + // Shift row + fRow += dRow + } + colName, _ := ColumnNumberToName(fCol) + return signCol + colName + signRow + strconv.Itoa(fRow) +} diff --git a/vendor/github.com/xuri/excelize/v2/chart.go b/vendor/github.com/xuri/excelize/v2/chart.go new file mode 100644 index 00000000..8f521fa0 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/chart.go @@ -0,0 +1,1034 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "strconv" + "strings" +) + +// This section defines the currently supported chart types. +const ( + Area = "area" + AreaStacked = "areaStacked" + AreaPercentStacked = "areaPercentStacked" + Area3D = "area3D" + Area3DStacked = "area3DStacked" + Area3DPercentStacked = "area3DPercentStacked" + Bar = "bar" + BarStacked = "barStacked" + BarPercentStacked = "barPercentStacked" + Bar3DClustered = "bar3DClustered" + Bar3DStacked = "bar3DStacked" + Bar3DPercentStacked = "bar3DPercentStacked" + Bar3DConeClustered = "bar3DConeClustered" + Bar3DConeStacked = "bar3DConeStacked" + Bar3DConePercentStacked = "bar3DConePercentStacked" + Bar3DPyramidClustered = "bar3DPyramidClustered" + Bar3DPyramidStacked = "bar3DPyramidStacked" + Bar3DPyramidPercentStacked = "bar3DPyramidPercentStacked" + Bar3DCylinderClustered = "bar3DCylinderClustered" + Bar3DCylinderStacked = "bar3DCylinderStacked" + Bar3DCylinderPercentStacked = "bar3DCylinderPercentStacked" + Col = "col" + ColStacked = "colStacked" + ColPercentStacked = "colPercentStacked" + Col3D = "col3D" + Col3DClustered = "col3DClustered" + Col3DStacked = "col3DStacked" + Col3DPercentStacked = "col3DPercentStacked" + Col3DCone = "col3DCone" + Col3DConeClustered = "col3DConeClustered" + Col3DConeStacked = "col3DConeStacked" + Col3DConePercentStacked = "col3DConePercentStacked" + Col3DPyramid = "col3DPyramid" + Col3DPyramidClustered = "col3DPyramidClustered" + Col3DPyramidStacked = "col3DPyramidStacked" + Col3DPyramidPercentStacked = "col3DPyramidPercentStacked" + Col3DCylinder = "col3DCylinder" + Col3DCylinderClustered = "col3DCylinderClustered" + Col3DCylinderStacked = "col3DCylinderStacked" + Col3DCylinderPercentStacked = "col3DCylinderPercentStacked" + Doughnut = "doughnut" + Line = "line" + Pie = "pie" + Pie3D = "pie3D" + PieOfPieChart = "pieOfPie" + BarOfPieChart = "barOfPie" + Radar = "radar" + Scatter = "scatter" + Surface3D = "surface3D" + WireframeSurface3D = "wireframeSurface3D" + Contour = "contour" + WireframeContour = "wireframeContour" + Bubble = "bubble" + Bubble3D = "bubble3D" +) + +// This section defines the default value of chart properties. +var ( + chartView3DRotX = map[string]int{ + Area: 0, + AreaStacked: 0, + AreaPercentStacked: 0, + Area3D: 15, + Area3DStacked: 15, + Area3DPercentStacked: 15, + Bar: 0, + BarStacked: 0, + BarPercentStacked: 0, + Bar3DClustered: 15, + Bar3DStacked: 15, + Bar3DPercentStacked: 15, + Bar3DConeClustered: 15, + Bar3DConeStacked: 15, + Bar3DConePercentStacked: 15, + Bar3DPyramidClustered: 15, + Bar3DPyramidStacked: 15, + Bar3DPyramidPercentStacked: 15, + Bar3DCylinderClustered: 15, + Bar3DCylinderStacked: 15, + Bar3DCylinderPercentStacked: 15, + Col: 0, + ColStacked: 0, + ColPercentStacked: 0, + Col3D: 15, + Col3DClustered: 15, + Col3DStacked: 15, + Col3DPercentStacked: 15, + Col3DCone: 15, + Col3DConeClustered: 15, + Col3DConeStacked: 15, + Col3DConePercentStacked: 15, + Col3DPyramid: 15, + Col3DPyramidClustered: 15, + Col3DPyramidStacked: 15, + Col3DPyramidPercentStacked: 15, + Col3DCylinder: 15, + Col3DCylinderClustered: 15, + Col3DCylinderStacked: 15, + Col3DCylinderPercentStacked: 15, + Doughnut: 0, + Line: 0, + Pie: 0, + Pie3D: 30, + PieOfPieChart: 0, + BarOfPieChart: 0, + Radar: 0, + Scatter: 0, + Surface3D: 15, + WireframeSurface3D: 15, + Contour: 90, + WireframeContour: 90, + } + chartView3DRotY = map[string]int{ + Area: 0, + AreaStacked: 0, + AreaPercentStacked: 0, + Area3D: 20, + Area3DStacked: 20, + Area3DPercentStacked: 20, + Bar: 0, + BarStacked: 0, + BarPercentStacked: 0, + Bar3DClustered: 20, + Bar3DStacked: 20, + Bar3DPercentStacked: 20, + Bar3DConeClustered: 20, + Bar3DConeStacked: 20, + Bar3DConePercentStacked: 20, + Bar3DPyramidClustered: 20, + Bar3DPyramidStacked: 20, + Bar3DPyramidPercentStacked: 20, + Bar3DCylinderClustered: 20, + Bar3DCylinderStacked: 20, + Bar3DCylinderPercentStacked: 20, + Col: 0, + ColStacked: 0, + ColPercentStacked: 0, + Col3D: 20, + Col3DClustered: 20, + Col3DStacked: 20, + Col3DPercentStacked: 20, + Col3DCone: 20, + Col3DConeClustered: 20, + Col3DConeStacked: 20, + Col3DConePercentStacked: 20, + Col3DPyramid: 20, + Col3DPyramidClustered: 20, + Col3DPyramidStacked: 20, + Col3DPyramidPercentStacked: 20, + Col3DCylinder: 20, + Col3DCylinderClustered: 20, + Col3DCylinderStacked: 20, + Col3DCylinderPercentStacked: 20, + Doughnut: 0, + Line: 0, + Pie: 0, + Pie3D: 0, + PieOfPieChart: 0, + BarOfPieChart: 0, + Radar: 0, + Scatter: 0, + Surface3D: 20, + WireframeSurface3D: 20, + Contour: 0, + WireframeContour: 0, + } + plotAreaChartOverlap = map[string]int{ + BarStacked: 100, + BarPercentStacked: 100, + ColStacked: 100, + ColPercentStacked: 100, + } + chartView3DPerspective = map[string]int{ + Contour: 0, + WireframeContour: 0, + } + chartView3DRAngAx = map[string]int{ + Area: 0, + AreaStacked: 0, + AreaPercentStacked: 0, + Area3D: 1, + Area3DStacked: 1, + Area3DPercentStacked: 1, + Bar: 0, + BarStacked: 0, + BarPercentStacked: 0, + Bar3DClustered: 1, + Bar3DStacked: 1, + Bar3DPercentStacked: 1, + Bar3DConeClustered: 1, + Bar3DConeStacked: 1, + Bar3DConePercentStacked: 1, + Bar3DPyramidClustered: 1, + Bar3DPyramidStacked: 1, + Bar3DPyramidPercentStacked: 1, + Bar3DCylinderClustered: 1, + Bar3DCylinderStacked: 1, + Bar3DCylinderPercentStacked: 1, + Col: 0, + ColStacked: 0, + ColPercentStacked: 0, + Col3D: 1, + Col3DClustered: 1, + Col3DStacked: 1, + Col3DPercentStacked: 1, + Col3DCone: 1, + Col3DConeClustered: 1, + Col3DConeStacked: 1, + Col3DConePercentStacked: 1, + Col3DPyramid: 1, + Col3DPyramidClustered: 1, + Col3DPyramidStacked: 1, + Col3DPyramidPercentStacked: 1, + Col3DCylinder: 1, + Col3DCylinderClustered: 1, + Col3DCylinderStacked: 1, + Col3DCylinderPercentStacked: 1, + Doughnut: 0, + Line: 0, + Pie: 0, + Pie3D: 0, + PieOfPieChart: 0, + BarOfPieChart: 0, + Radar: 0, + Scatter: 0, + Surface3D: 0, + WireframeSurface3D: 0, + Contour: 0, + Bubble: 0, + Bubble3D: 0, + } + chartLegendPosition = map[string]string{ + "bottom": "b", + "left": "l", + "right": "r", + "top": "t", + "top_right": "tr", + } + chartValAxNumFmtFormatCode = map[string]string{ + Area: "General", + AreaStacked: "General", + AreaPercentStacked: "0%", + Area3D: "General", + Area3DStacked: "General", + Area3DPercentStacked: "0%", + Bar: "General", + BarStacked: "General", + BarPercentStacked: "0%", + Bar3DClustered: "General", + Bar3DStacked: "General", + Bar3DPercentStacked: "0%", + Bar3DConeClustered: "General", + Bar3DConeStacked: "General", + Bar3DConePercentStacked: "0%", + Bar3DPyramidClustered: "General", + Bar3DPyramidStacked: "General", + Bar3DPyramidPercentStacked: "0%", + Bar3DCylinderClustered: "General", + Bar3DCylinderStacked: "General", + Bar3DCylinderPercentStacked: "0%", + Col: "General", + ColStacked: "General", + ColPercentStacked: "0%", + Col3D: "General", + Col3DClustered: "General", + Col3DStacked: "General", + Col3DPercentStacked: "0%", + Col3DCone: "General", + Col3DConeClustered: "General", + Col3DConeStacked: "General", + Col3DConePercentStacked: "0%", + Col3DPyramid: "General", + Col3DPyramidClustered: "General", + Col3DPyramidStacked: "General", + Col3DPyramidPercentStacked: "0%", + Col3DCylinder: "General", + Col3DCylinderClustered: "General", + Col3DCylinderStacked: "General", + Col3DCylinderPercentStacked: "0%", + Doughnut: "General", + Line: "General", + Pie: "General", + Pie3D: "General", + PieOfPieChart: "General", + BarOfPieChart: "General", + Radar: "General", + Scatter: "General", + Surface3D: "General", + WireframeSurface3D: "General", + Contour: "General", + WireframeContour: "General", + Bubble: "General", + Bubble3D: "General", + } + chartValAxCrossBetween = map[string]string{ + Area: "midCat", + AreaStacked: "midCat", + AreaPercentStacked: "midCat", + Area3D: "midCat", + Area3DStacked: "midCat", + Area3DPercentStacked: "midCat", + Bar: "between", + BarStacked: "between", + BarPercentStacked: "between", + Bar3DClustered: "between", + Bar3DStacked: "between", + Bar3DPercentStacked: "between", + Bar3DConeClustered: "between", + Bar3DConeStacked: "between", + Bar3DConePercentStacked: "between", + Bar3DPyramidClustered: "between", + Bar3DPyramidStacked: "between", + Bar3DPyramidPercentStacked: "between", + Bar3DCylinderClustered: "between", + Bar3DCylinderStacked: "between", + Bar3DCylinderPercentStacked: "between", + Col: "between", + ColStacked: "between", + ColPercentStacked: "between", + Col3D: "between", + Col3DClustered: "between", + Col3DStacked: "between", + Col3DPercentStacked: "between", + Col3DCone: "between", + Col3DConeClustered: "between", + Col3DConeStacked: "between", + Col3DConePercentStacked: "between", + Col3DPyramid: "between", + Col3DPyramidClustered: "between", + Col3DPyramidStacked: "between", + Col3DPyramidPercentStacked: "between", + Col3DCylinder: "between", + Col3DCylinderClustered: "between", + Col3DCylinderStacked: "between", + Col3DCylinderPercentStacked: "between", + Doughnut: "between", + Line: "between", + Pie: "between", + Pie3D: "between", + PieOfPieChart: "between", + BarOfPieChart: "between", + Radar: "between", + Scatter: "between", + Surface3D: "midCat", + WireframeSurface3D: "midCat", + Contour: "midCat", + WireframeContour: "midCat", + Bubble: "midCat", + Bubble3D: "midCat", + } + plotAreaChartGrouping = map[string]string{ + Area: "standard", + AreaStacked: "stacked", + AreaPercentStacked: "percentStacked", + Area3D: "standard", + Area3DStacked: "stacked", + Area3DPercentStacked: "percentStacked", + Bar: "clustered", + BarStacked: "stacked", + BarPercentStacked: "percentStacked", + Bar3DClustered: "clustered", + Bar3DStacked: "stacked", + Bar3DPercentStacked: "percentStacked", + Bar3DConeClustered: "clustered", + Bar3DConeStacked: "stacked", + Bar3DConePercentStacked: "percentStacked", + Bar3DPyramidClustered: "clustered", + Bar3DPyramidStacked: "stacked", + Bar3DPyramidPercentStacked: "percentStacked", + Bar3DCylinderClustered: "clustered", + Bar3DCylinderStacked: "stacked", + Bar3DCylinderPercentStacked: "percentStacked", + Col: "clustered", + ColStacked: "stacked", + ColPercentStacked: "percentStacked", + Col3D: "standard", + Col3DClustered: "clustered", + Col3DStacked: "stacked", + Col3DPercentStacked: "percentStacked", + Col3DCone: "standard", + Col3DConeClustered: "clustered", + Col3DConeStacked: "stacked", + Col3DConePercentStacked: "percentStacked", + Col3DPyramid: "standard", + Col3DPyramidClustered: "clustered", + Col3DPyramidStacked: "stacked", + Col3DPyramidPercentStacked: "percentStacked", + Col3DCylinder: "standard", + Col3DCylinderClustered: "clustered", + Col3DCylinderStacked: "stacked", + Col3DCylinderPercentStacked: "percentStacked", + Line: "standard", + } + plotAreaChartBarDir = map[string]string{ + Bar: "bar", + BarStacked: "bar", + BarPercentStacked: "bar", + Bar3DClustered: "bar", + Bar3DStacked: "bar", + Bar3DPercentStacked: "bar", + Bar3DConeClustered: "bar", + Bar3DConeStacked: "bar", + Bar3DConePercentStacked: "bar", + Bar3DPyramidClustered: "bar", + Bar3DPyramidStacked: "bar", + Bar3DPyramidPercentStacked: "bar", + Bar3DCylinderClustered: "bar", + Bar3DCylinderStacked: "bar", + Bar3DCylinderPercentStacked: "bar", + Col: "col", + ColStacked: "col", + ColPercentStacked: "col", + Col3D: "col", + Col3DClustered: "col", + Col3DStacked: "col", + Col3DPercentStacked: "col", + Col3DCone: "col", + Col3DConeStacked: "col", + Col3DConeClustered: "col", + Col3DConePercentStacked: "col", + Col3DPyramid: "col", + Col3DPyramidClustered: "col", + Col3DPyramidStacked: "col", + Col3DPyramidPercentStacked: "col", + Col3DCylinder: "col", + Col3DCylinderClustered: "col", + Col3DCylinderStacked: "col", + Col3DCylinderPercentStacked: "col", + Line: "standard", + } + orientation = map[bool]string{ + true: "maxMin", + false: "minMax", + } + catAxPos = map[bool]string{ + true: "t", + false: "b", + } + valAxPos = map[bool]string{ + true: "r", + false: "l", + } + valTickLblPos = map[string]string{ + Contour: "none", + WireframeContour: "none", + } +) + +// parseFormatChartSet provides a function to parse the format settings of the +// chart with default value. +func parseFormatChartSet(formatSet string) (*formatChart, error) { + format := formatChart{ + Dimension: formatChartDimension{ + Width: 480, + Height: 290, + }, + Format: formatPicture{ + FPrintsWithSheet: true, + FLocksWithSheet: false, + NoChangeAspect: false, + OffsetX: 0, + OffsetY: 0, + XScale: 1.0, + YScale: 1.0, + }, + Legend: formatChartLegend{ + Position: "bottom", + ShowLegendKey: false, + }, + Title: formatChartTitle{ + Name: " ", + }, + VaryColors: true, + ShowBlanksAs: "gap", + } + err := json.Unmarshal([]byte(formatSet), &format) + return &format, err +} + +// AddChart provides the method to add chart in a sheet by given chart format +// set (such as offset, scale, aspect ratio setting and print settings) and +// properties set. For example, create 3D clustered column chart with data +// Sheet1!$E$1:$L$15: +// +// package main +// +// import ( +// "fmt" +// +// "github.com/xuri/excelize/v2" +// ) +// +// func main() { +// categories := map[string]string{ +// "A2": "Small", "A3": "Normal", "A4": "Large", +// "B1": "Apple", "C1": "Orange", "D1": "Pear"} +// values := map[string]int{ +// "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} +// f := excelize.NewFile() +// for k, v := range categories { +// f.SetCellValue("Sheet1", k, v) +// } +// for k, v := range values { +// f.SetCellValue("Sheet1", k, v) +// } +// if err := f.AddChart("Sheet1", "E1", `{ +// "type": "col3DClustered", +// "series": [ +// { +// "name": "Sheet1!$A$2", +// "categories": "Sheet1!$B$1:$D$1", +// "values": "Sheet1!$B$2:$D$2" +// }, +// { +// "name": "Sheet1!$A$3", +// "categories": "Sheet1!$B$1:$D$1", +// "values": "Sheet1!$B$3:$D$3" +// }, +// { +// "name": "Sheet1!$A$4", +// "categories": "Sheet1!$B$1:$D$1", +// "values": "Sheet1!$B$4:$D$4" +// }], +// "title": +// { +// "name": "Fruit 3D Clustered Column Chart" +// }, +// "legend": +// { +// "none": false, +// "position": "bottom", +// "show_legend_key": false +// }, +// "plotarea": +// { +// "show_bubble_size": true, +// "show_cat_name": false, +// "show_leader_lines": false, +// "show_percent": true, +// "show_series_name": true, +// "show_val": true +// }, +// "show_blanks_as": "zero", +// "x_axis": +// { +// "reverse_order": true +// }, +// "y_axis": +// { +// "maximum": 7.5, +// "minimum": 0.5 +// } +// }`); err != nil { +// fmt.Println(err) +// return +// } +// // Save spreadsheet by the given path. +// if err := f.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// } +// +// The following shows the type of chart supported by excelize: +// +// Type | Chart +// -----------------------------+------------------------------ +// area | 2D area chart +// areaStacked | 2D stacked area chart +// areaPercentStacked | 2D 100% stacked area chart +// area3D | 3D area chart +// area3DStacked | 3D stacked area chart +// area3DPercentStacked | 3D 100% stacked area chart +// bar | 2D clustered bar chart +// barStacked | 2D stacked bar chart +// barPercentStacked | 2D 100% stacked bar chart +// bar3DClustered | 3D clustered bar chart +// bar3DStacked | 3D stacked bar chart +// bar3DPercentStacked | 3D 100% stacked bar chart +// bar3DConeClustered | 3D cone clustered bar chart +// bar3DConeStacked | 3D cone stacked bar chart +// bar3DConePercentStacked | 3D cone percent bar chart +// bar3DPyramidClustered | 3D pyramid clustered bar chart +// bar3DPyramidStacked | 3D pyramid stacked bar chart +// bar3DPyramidPercentStacked | 3D pyramid percent stacked bar chart +// bar3DCylinderClustered | 3D cylinder clustered bar chart +// bar3DCylinderStacked | 3D cylinder stacked bar chart +// bar3DCylinderPercentStacked | 3D cylinder percent stacked bar chart +// col | 2D clustered column chart +// colStacked | 2D stacked column chart +// colPercentStacked | 2D 100% stacked column chart +// col3DClustered | 3D clustered column chart +// col3D | 3D column chart +// col3DStacked | 3D stacked column chart +// col3DPercentStacked | 3D 100% stacked column chart +// col3DCone | 3D cone column chart +// col3DConeClustered | 3D cone clustered column chart +// col3DConeStacked | 3D cone stacked column chart +// col3DConePercentStacked | 3D cone percent stacked column chart +// col3DPyramid | 3D pyramid column chart +// col3DPyramidClustered | 3D pyramid clustered column chart +// col3DPyramidStacked | 3D pyramid stacked column chart +// col3DPyramidPercentStacked | 3D pyramid percent stacked column chart +// col3DCylinder | 3D cylinder column chart +// col3DCylinderClustered | 3D cylinder clustered column chart +// col3DCylinderStacked | 3D cylinder stacked column chart +// col3DCylinderPercentStacked | 3D cylinder percent stacked column chart +// doughnut | doughnut chart +// line | line chart +// pie | pie chart +// pie3D | 3D pie chart +// pieOfPie | pie of pie chart +// barOfPie | bar of pie chart +// radar | radar chart +// scatter | scatter chart +// surface3D | 3D surface chart +// wireframeSurface3D | 3D wireframe surface chart +// contour | contour chart +// wireframeContour | wireframe contour chart +// bubble | bubble chart +// bubble3D | 3D bubble chart +// +// In Excel a chart series is a collection of information that defines which data is plotted such as values, axis labels and formatting. +// +// The series options that can be set are: +// +// name +// categories +// values +// line +// marker +// +// name: Set the name for the series. The name is displayed in the chart legend and in the formula bar. The name property is optional and if it isn't supplied it will default to Series 1..n. The name can also be a formula such as Sheet1!$A$1 +// +// categories: This sets the chart category labels. The category is more or less the same as the X axis. In most chart types the categories property is optional and the chart will just assume a sequential series from 1..n. +// +// values: This is the most important property of a series and is the only mandatory option for every chart object. This option links the chart with the worksheet data that it displays. +// +// line: This sets the line format of the line chart. The line property is optional and if it isn't supplied it will default style. The options that can be set is width. The range of width is 0.25pt - 999pt. If the value of width is outside the range, the default width of the line is 2pt. +// +// marker: This sets the marker of the line chart and scatter chart. The range of optional field 'size' is 2-72 (default value is 5). The enumeration value of optional field 'symbol' are (default value is 'auto'): +// +// circle +// dash +// diamond +// dot +// none +// picture +// plus +// square +// star +// triangle +// x +// auto +// +// Set properties of the chart legend. The options that can be set are: +// +// none +// position +// show_legend_key +// +// none: Specified if show the legend without overlapping the chart. The default value is 'false'. +// +// position: Set the position of the chart legend. The default legend position is right. This parameter only takes effect when 'none' is false. The available positions are: +// +// top +// bottom +// left +// right +// top_right +// +// show_legend_key: Set the legend keys shall be shown in data labels. The default value is false. +// +// Set properties of the chart title. The properties that can be set are: +// +// title +// +// name: Set the name (title) for the chart. The name is displayed above the chart. The name can also be a formula such as Sheet1!$A$1 or a list with a sheetname. The name property is optional. The default is to have no chart title. +// +// Specifies how blank cells are plotted on the chart by show_blanks_as. The default value is gap. The options that can be set are: +// +// gap +// span +// zero +// +// gap: Specifies that blank values shall be left as a gap. +// +// span: Specifies that blank values shall be spanned with a line. +// +// zero: Specifies that blank values shall be treated as zero. +// +// Specifies that each data marker in the series has a different color by vary_colors. The default value is true. +// +// Set chart offset, scale, aspect ratio setting and print settings by format, same as function AddPicture. +// +// Set the position of the chart plot area by plotarea. The properties that can be set are: +// +// show_bubble_size +// show_cat_name +// show_leader_lines +// show_percent +// show_series_name +// show_val +// +// show_bubble_size: Specifies the bubble size shall be shown in a data label. The show_bubble_size property is optional. The default value is false. +// +// show_cat_name: Specifies that the category name shall be shown in the data label. The show_cat_name property is optional. The default value is true. +// +// show_leader_lines: Specifies leader lines shall be shown for data labels. The show_leader_lines property is optional. The default value is false. +// +// show_percent: Specifies that the percentage shall be shown in a data label. The show_percent property is optional. The default value is false. +// +// show_series_name: Specifies that the series name shall be shown in a data label. The show_series_name property is optional. The default value is false. +// +// show_val: Specifies that the value shall be shown in a data label. The show_val property is optional. The default value is false. +// +// Set the primary horizontal and vertical axis options by x_axis and y_axis. The properties of x_axis that can be set are: +// +// none +// major_grid_lines +// minor_grid_lines +// tick_label_skip +// reverse_order +// maximum +// minimum +// +// The properties of y_axis that can be set are: +// +// none +// major_grid_lines +// minor_grid_lines +// major_unit +// reverse_order +// maximum +// minimum +// +// none: Disable axes. +// +// major_grid_lines: Specifies major gridlines. +// +// minor_grid_lines: Specifies minor gridlines. +// +// major_unit: Specifies the distance between major ticks. Shall contain a positive floating-point number. The major_unit property is optional. The default value is auto. +// +// tick_label_skip: Specifies how many tick labels to skip between label that is drawn. The tick_label_skip property is optional. The default value is auto. +// +// reverse_order: Specifies that the categories or values on reverse order (orientation of the chart). The reverse_order property is optional. The default value is false. +// +// maximum: Specifies that the fixed maximum, 0 is auto. The maximum property is optional. The default value is auto. +// +// minimum: Specifies that the fixed minimum, 0 is auto. The minimum property is optional. The default value is auto. +// +// Set chart size by dimension property. The dimension property is optional. The default width is 480, and height is 290. +// +// combo: Specifies the create a chart that combines two or more chart types +// in a single chart. For example, create a clustered column - line chart with +// data Sheet1!$E$1:$L$15: +// +// package main +// +// import ( +// "fmt" +// +// "github.com/xuri/excelize/v2" +// ) +// +// func main() { +// categories := map[string]string{ +// "A2": "Small", "A3": "Normal", "A4": "Large", +// "B1": "Apple", "C1": "Orange", "D1": "Pear"} +// values := map[string]int{ +// "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} +// f := excelize.NewFile() +// for k, v := range categories { +// f.SetCellValue("Sheet1", k, v) +// } +// for k, v := range values { +// f.SetCellValue("Sheet1", k, v) +// } +// if err := f.AddChart("Sheet1", "E1", `{ +// "type": "col", +// "series": [ +// { +// "name": "Sheet1!$A$2", +// "categories": "", +// "values": "Sheet1!$B$2:$D$2" +// }, +// { +// "name": "Sheet1!$A$3", +// "categories": "Sheet1!$B$1:$D$1", +// "values": "Sheet1!$B$3:$D$3" +// }], +// "format": +// { +// "x_scale": 1.0, +// "y_scale": 1.0, +// "x_offset": 15, +// "y_offset": 10, +// "print_obj": true, +// "lock_aspect_ratio": false, +// "locked": false +// }, +// "title": +// { +// "name": "Clustered Column - Line Chart" +// }, +// "legend": +// { +// "position": "left", +// "show_legend_key": false +// }, +// "plotarea": +// { +// "show_bubble_size": true, +// "show_cat_name": false, +// "show_leader_lines": false, +// "show_percent": true, +// "show_series_name": true, +// "show_val": true +// } +// }`, `{ +// "type": "line", +// "series": [ +// { +// "name": "Sheet1!$A$4", +// "categories": "Sheet1!$B$1:$D$1", +// "values": "Sheet1!$B$4:$D$4", +// "marker": +// { +// "symbol": "none", +// "size": 10 +// } +// }], +// "format": +// { +// "x_scale": 1, +// "y_scale": 1, +// "x_offset": 15, +// "y_offset": 10, +// "print_obj": true, +// "lock_aspect_ratio": false, +// "locked": false +// }, +// "legend": +// { +// "position": "right", +// "show_legend_key": false +// }, +// "plotarea": +// { +// "show_bubble_size": true, +// "show_cat_name": false, +// "show_leader_lines": false, +// "show_percent": true, +// "show_series_name": true, +// "show_val": true +// } +// }`); err != nil { +// fmt.Println(err) +// return +// } +// // Save spreadsheet file by the given path. +// if err := f.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// } +// +func (f *File) AddChart(sheet, cell, format string, combo ...string) error { + // Read sheet data. + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + formatSet, comboCharts, err := f.getFormatChart(format, combo) + if err != nil { + return err + } + // Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. + drawingID := f.countDrawings() + 1 + chartID := f.countCharts() + 1 + drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML) + drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" + drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") + err = f.addDrawingChart(sheet, drawingXML, cell, formatSet.Dimension.Width, formatSet.Dimension.Height, drawingRID, &formatSet.Format) + if err != nil { + return err + } + f.addChart(formatSet, comboCharts) + f.addContentTypePart(chartID, "chart") + f.addContentTypePart(drawingID, "drawings") + f.addSheetNameSpace(sheet, SourceRelationship) + return err +} + +// AddChartSheet provides the method to create a chartsheet by given chart +// format set (such as offset, scale, aspect ratio setting and print settings) +// and properties set. In Excel a chartsheet is a worksheet that only contains +// a chart. +func (f *File) AddChartSheet(sheet, format string, combo ...string) error { + // Check if the worksheet already exists + if f.GetSheetIndex(sheet) != -1 { + return ErrExistsWorksheet + } + formatSet, comboCharts, err := f.getFormatChart(format, combo) + if err != nil { + return err + } + cs := xlsxChartsheet{ + SheetViews: &xlsxChartsheetViews{ + SheetView: []*xlsxChartsheetView{{ZoomScaleAttr: 100, ZoomToFitAttr: true}}, + }, + } + f.SheetCount++ + wb := f.workbookReader() + sheetID := 0 + for _, v := range wb.Sheets.Sheet { + if v.SheetID > sheetID { + sheetID = v.SheetID + } + } + sheetID++ + path := "xl/chartsheets/sheet" + strconv.Itoa(sheetID) + ".xml" + f.sheetMap[trimSheetName(sheet)] = path + f.Sheet.Store(path, nil) + drawingID := f.countDrawings() + 1 + chartID := f.countCharts() + 1 + drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + f.prepareChartSheetDrawing(&cs, drawingID, sheet) + drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" + drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") + f.addSheetDrawingChart(drawingXML, drawingRID, &formatSet.Format) + f.addChart(formatSet, comboCharts) + f.addContentTypePart(chartID, "chart") + f.addContentTypePart(sheetID, "chartsheet") + f.addContentTypePart(drawingID, "drawings") + // Update workbook.xml.rels + rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "") + // Update workbook.xml + f.setWorkbook(sheet, sheetID, rID) + chartsheet, _ := xml.Marshal(cs) + f.addSheetNameSpace(sheet, NameSpaceSpreadSheet) + f.saveFileList(path, replaceRelationshipsBytes(f.replaceNameSpaceBytes(path, chartsheet))) + return err +} + +// getFormatChart provides a function to check format set of the chart and +// create chart format. +func (f *File) getFormatChart(format string, combo []string) (*formatChart, []*formatChart, error) { + var comboCharts []*formatChart + formatSet, err := parseFormatChartSet(format) + if err != nil { + return formatSet, comboCharts, err + } + for _, comboFormat := range combo { + comboChart, err := parseFormatChartSet(comboFormat) + if err != nil { + return formatSet, comboCharts, err + } + if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok { + return formatSet, comboCharts, newUnsupportedChartType(comboChart.Type) + } + comboCharts = append(comboCharts, comboChart) + } + if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok { + return formatSet, comboCharts, newUnsupportedChartType(formatSet.Type) + } + return formatSet, comboCharts, err +} + +// DeleteChart provides a function to delete chart in XLSX by given worksheet +// and cell name. +func (f *File) DeleteChart(sheet, cell string) (err error) { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return + } + col-- + row-- + ws, err := f.workSheetReader(sheet) + if err != nil { + return + } + if ws.Drawing == nil { + return + } + drawingXML := strings.Replace(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl", -1) + return f.deleteDrawing(col, row, drawingXML, "Chart") +} + +// countCharts provides a function to get chart files count storage in the +// folder xl/charts. +func (f *File) countCharts() int { + count := 0 + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/charts/chart") { + count++ + } + return true + }) + return count +} + +// ptToEMUs provides a function to convert pt to EMUs, 1 pt = 12700 EMUs. The +// range of pt is 0.25pt - 999pt. If the value of pt is outside the range, the +// default EMUs will be returned. +func (f *File) ptToEMUs(pt float64) int { + if 0.25 > pt || pt > 999 { + return 25400 + } + return int(12700 * pt) +} diff --git a/vendor/github.com/xuri/excelize/v2/col.go b/vendor/github.com/xuri/excelize/v2/col.go new file mode 100644 index 00000000..ee1a4074 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/col.go @@ -0,0 +1,723 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/xml" + "math" + "strconv" + "strings" + + "github.com/mohae/deepcopy" +) + +// Define the default cell size and EMU unit of measurement. +const ( + defaultColWidth float64 = 9.140625 + defaultColWidthPixels float64 = 64 + defaultRowHeight float64 = 15 + defaultRowHeightPixels float64 = 20 + EMU int = 9525 +) + +// Cols defines an iterator to a sheet +type Cols struct { + err error + curCol, totalCols, totalRows, stashCol int + rawCellValue bool + sheet string + f *File + sheetXML []byte +} + +// GetCols return all the columns in a sheet by given worksheet name (case-sensitive). For example: +// +// cols, err := f.GetCols("Sheet1") +// if err != nil { +// fmt.Println(err) +// return +// } +// for _, col := range cols { +// for _, rowCell := range col { +// fmt.Print(rowCell, "\t") +// } +// fmt.Println() +// } +// +func (f *File) GetCols(sheet string, opts ...Options) ([][]string, error) { + cols, err := f.Cols(sheet) + if err != nil { + return nil, err + } + results := make([][]string, 0, 64) + for cols.Next() { + col, _ := cols.Rows(opts...) + results = append(results, col) + } + return results, nil +} + +// Next will return true if the next column is found. +func (cols *Cols) Next() bool { + cols.curCol++ + return cols.curCol <= cols.totalCols +} + +// Error will return an error when the error occurs. +func (cols *Cols) Error() error { + return cols.err +} + +// Rows return the current column's row values. +func (cols *Cols) Rows(opts ...Options) ([]string, error) { + var ( + err error + inElement string + cellCol, cellRow int + rows []string + ) + if cols.stashCol >= cols.curCol { + return rows, err + } + cols.rawCellValue = parseOptions(opts...).RawCellValue + d := cols.f.sharedStringsReader() + decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML)) + for { + token, _ := decoder.Token() + if token == nil { + break + } + switch xmlElement := token.(type) { + case xml.StartElement: + inElement = xmlElement.Name.Local + if inElement == "row" { + cellCol = 0 + cellRow++ + attrR, _ := attrValToInt("r", xmlElement.Attr) + if attrR != 0 { + cellRow = attrR + } + } + if inElement == "c" { + cellCol++ + for _, attr := range xmlElement.Attr { + if attr.Name.Local == "r" { + if cellCol, cellRow, err = CellNameToCoordinates(attr.Value); err != nil { + return rows, err + } + } + } + blank := cellRow - len(rows) + for i := 1; i < blank; i++ { + rows = append(rows, "") + } + if cellCol == cols.curCol { + colCell := xlsxC{} + _ = decoder.DecodeElement(&colCell, &xmlElement) + val, _ := colCell.getValueFrom(cols.f, d, cols.rawCellValue) + rows = append(rows, val) + } + } + case xml.EndElement: + if xmlElement.Name.Local == "sheetData" { + return rows, err + } + } + } + return rows, err +} + +// columnXMLIterator defined runtime use field for the worksheet column SAX parser. +type columnXMLIterator struct { + err error + cols Cols + cellCol, curRow, row int +} + +// columnXMLHandler parse the column XML element of the worksheet. +func columnXMLHandler(colIterator *columnXMLIterator, xmlElement *xml.StartElement) { + colIterator.err = nil + inElement := xmlElement.Name.Local + if inElement == "row" { + colIterator.row++ + for _, attr := range xmlElement.Attr { + if attr.Name.Local == "r" { + if colIterator.curRow, colIterator.err = strconv.Atoi(attr.Value); colIterator.err != nil { + return + } + colIterator.row = colIterator.curRow + } + } + colIterator.cols.totalRows = colIterator.row + colIterator.cellCol = 0 + } + if inElement == "c" { + colIterator.cellCol++ + for _, attr := range xmlElement.Attr { + if attr.Name.Local == "r" { + if colIterator.cellCol, _, colIterator.err = CellNameToCoordinates(attr.Value); colIterator.err != nil { + return + } + } + } + if colIterator.cellCol > colIterator.cols.totalCols { + colIterator.cols.totalCols = colIterator.cellCol + } + } +} + +// Cols returns a columns iterator, used for streaming reading data for a +// worksheet with a large data. For example: +// +// cols, err := f.Cols("Sheet1") +// if err != nil { +// fmt.Println(err) +// return +// } +// for cols.Next() { +// col, err := cols.Rows() +// if err != nil { +// fmt.Println(err) +// } +// for _, rowCell := range col { +// fmt.Print(rowCell, "\t") +// } +// fmt.Println() +// } +// +func (f *File) Cols(sheet string) (*Cols, error) { + name, ok := f.sheetMap[trimSheetName(sheet)] + if !ok { + return nil, ErrSheetNotExist{sheet} + } + if ws, ok := f.Sheet.Load(name); ok && ws != nil { + worksheet := ws.(*xlsxWorksheet) + worksheet.Lock() + defer worksheet.Unlock() + output, _ := xml.Marshal(worksheet) + f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) + } + var colIterator columnXMLIterator + colIterator.cols.sheetXML = f.readBytes(name) + decoder := f.xmlNewDecoder(bytes.NewReader(colIterator.cols.sheetXML)) + for { + token, _ := decoder.Token() + if token == nil { + break + } + switch xmlElement := token.(type) { + case xml.StartElement: + columnXMLHandler(&colIterator, &xmlElement) + if colIterator.err != nil { + return &colIterator.cols, colIterator.err + } + case xml.EndElement: + if xmlElement.Name.Local == "sheetData" { + colIterator.cols.f = f + colIterator.cols.sheet = trimSheetName(sheet) + return &colIterator.cols, nil + } + } + } + return &colIterator.cols, nil +} + +// GetColVisible provides a function to get visible of a single column by given +// worksheet name and column name. For example, get visible state of column D +// in Sheet1: +// +// visible, err := f.GetColVisible("Sheet1", "D") +// +func (f *File) GetColVisible(sheet, col string) (bool, error) { + colNum, err := ColumnNameToNumber(col) + if err != nil { + return true, err + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return false, err + } + if ws.Cols == nil { + return true, err + } + visible := true + for c := range ws.Cols.Col { + colData := &ws.Cols.Col[c] + if colData.Min <= colNum && colNum <= colData.Max { + visible = !colData.Hidden + } + } + return visible, err +} + +// SetColVisible provides a function to set visible columns by given worksheet +// name, columns range and visibility. +// +// For example hide column D on Sheet1: +// +// err := f.SetColVisible("Sheet1", "D", false) +// +// Hide the columns from D to F (included): +// +// err := f.SetColVisible("Sheet1", "D:F", false) +// +func (f *File) SetColVisible(sheet, columns string, visible bool) error { + start, end, err := f.parseColRange(columns) + if err != nil { + return err + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + colData := xlsxCol{ + Min: start, + Max: end, + Width: defaultColWidth, // default width + Hidden: !visible, + CustomWidth: true, + } + if ws.Cols == nil { + cols := xlsxCols{} + cols.Col = append(cols.Col, colData) + ws.Cols = &cols + return nil + } + ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { + fc.BestFit = c.BestFit + fc.Collapsed = c.Collapsed + fc.CustomWidth = c.CustomWidth + fc.OutlineLevel = c.OutlineLevel + fc.Phonetic = c.Phonetic + fc.Style = c.Style + fc.Width = c.Width + return fc + }) + return nil +} + +// GetColOutlineLevel provides a function to get outline level of a single +// column by given worksheet name and column name. For example, get outline +// level of column D in Sheet1: +// +// level, err := f.GetColOutlineLevel("Sheet1", "D") +// +func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) { + level := uint8(0) + colNum, err := ColumnNameToNumber(col) + if err != nil { + return level, err + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return 0, err + } + if ws.Cols == nil { + return level, err + } + for c := range ws.Cols.Col { + colData := &ws.Cols.Col[c] + if colData.Min <= colNum && colNum <= colData.Max { + level = colData.OutlineLevel + } + } + return level, err +} + +// parseColRange parse and convert column range with column name to the column number. +func (f *File) parseColRange(columns string) (start, end int, err error) { + colsTab := strings.Split(columns, ":") + start, err = ColumnNameToNumber(colsTab[0]) + if err != nil { + return + } + end = start + if len(colsTab) == 2 { + if end, err = ColumnNameToNumber(colsTab[1]); err != nil { + return + } + } + if end < start { + start, end = end, start + } + return +} + +// SetColOutlineLevel provides a function to set outline level of a single +// column by given worksheet name and column name. The value of parameter +// 'level' is 1-7. For example, set outline level of column D in Sheet1 to 2: +// +// err := f.SetColOutlineLevel("Sheet1", "D", 2) +// +func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error { + if level > 7 || level < 1 { + return ErrOutlineLevel + } + colNum, err := ColumnNameToNumber(col) + if err != nil { + return err + } + colData := xlsxCol{ + Min: colNum, + Max: colNum, + OutlineLevel: level, + CustomWidth: true, + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if ws.Cols == nil { + cols := xlsxCols{} + cols.Col = append(cols.Col, colData) + ws.Cols = &cols + return err + } + ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { + fc.BestFit = c.BestFit + fc.Collapsed = c.Collapsed + fc.CustomWidth = c.CustomWidth + fc.Hidden = c.Hidden + fc.Phonetic = c.Phonetic + fc.Style = c.Style + fc.Width = c.Width + return fc + }) + return err +} + +// SetColStyle provides a function to set style of columns by given worksheet +// name, columns range and style ID. Note that this will overwrite the +// existing styles for the columns, it won't append or merge style with +// existing styles. +// +// For example set style of column H on Sheet1: +// +// err = f.SetColStyle("Sheet1", "H", style) +// +// Set style of columns C:F on Sheet1: +// +// err = f.SetColStyle("Sheet1", "C:F", style) +// +func (f *File) SetColStyle(sheet, columns string, styleID int) error { + start, end, err := f.parseColRange(columns) + if err != nil { + return err + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if ws.Cols == nil { + ws.Cols = &xlsxCols{} + } + ws.Cols.Col = flatCols(xlsxCol{ + Min: start, + Max: end, + Width: defaultColWidth, + Style: styleID, + }, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { + fc.BestFit = c.BestFit + fc.Collapsed = c.Collapsed + fc.CustomWidth = c.CustomWidth + fc.Hidden = c.Hidden + fc.OutlineLevel = c.OutlineLevel + fc.Phonetic = c.Phonetic + fc.Width = c.Width + return fc + }) + if rows := len(ws.SheetData.Row); rows > 0 { + for col := start; col <= end; col++ { + from, _ := CoordinatesToCellName(col, 1) + to, _ := CoordinatesToCellName(col, rows) + err = f.SetCellStyle(sheet, from, to, styleID) + } + } + return err +} + +// SetColWidth provides a function to set the width of a single column or +// multiple columns. For example: +// +// f := excelize.NewFile() +// err := f.SetColWidth("Sheet1", "A", "H", 20) +// +func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error { + min, err := ColumnNameToNumber(startCol) + if err != nil { + return err + } + max, err := ColumnNameToNumber(endCol) + if err != nil { + return err + } + if width > MaxColumnWidth { + return ErrColumnWidth + } + if min > max { + min, max = max, min + } + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + col := xlsxCol{ + Min: min, + Max: max, + Width: width, + CustomWidth: true, + } + if ws.Cols == nil { + cols := xlsxCols{} + cols.Col = append(cols.Col, col) + ws.Cols = &cols + return err + } + ws.Cols.Col = flatCols(col, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { + fc.BestFit = c.BestFit + fc.Collapsed = c.Collapsed + fc.Hidden = c.Hidden + fc.OutlineLevel = c.OutlineLevel + fc.Phonetic = c.Phonetic + fc.Style = c.Style + return fc + }) + return err +} + +// flatCols provides a method for the column's operation functions to flatten +// and check the worksheet columns. +func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) []xlsxCol { + var fc []xlsxCol + for i := col.Min; i <= col.Max; i++ { + c := deepcopy.Copy(col).(xlsxCol) + c.Min, c.Max = i, i + fc = append(fc, c) + } + inFlat := func(colID int, cols []xlsxCol) (int, bool) { + for idx, c := range cols { + if c.Max == colID && c.Min == colID { + return idx, true + } + } + return -1, false + } + for _, column := range cols { + for i := column.Min; i <= column.Max; i++ { + if idx, ok := inFlat(i, fc); ok { + fc[idx] = replacer(fc[idx], column) + continue + } + c := deepcopy.Copy(column).(xlsxCol) + c.Min, c.Max = i, i + fc = append(fc, c) + } + } + return fc +} + +// positionObjectPixels calculate the vertices that define the position of a +// graphical object within the worksheet in pixels. +// +// +------------+------------+ +// | A | B | +// +-----+------------+------------+ +// | |(x1,y1) | | +// | 1 |(A1)._______|______ | +// | | | | | +// | | | | | +// +-----+----| OBJECT |-----+ +// | | | | | +// | 2 | |______________. | +// | | | (B2)| +// | | | (x2,y2)| +// +-----+------------+------------+ +// +// Example of an object that covers some area from cell A1 to B2. +// +// Based on the width and height of the object we need to calculate 8 vars: +// +// colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2. +// +// We also calculate the absolute x and y position of the top left vertex of +// the object. This is required for images. +// +// The width and height of the cells that the object occupies can be +// variable and have to be taken into account. +// +// The values of col_start and row_start are passed in from the calling +// function. The values of col_end and row_end are calculated by +// subtracting the width and height of the object from the width and +// height of the underlying cells. +// +// colStart # Col containing upper left corner of object. +// x1 # Distance to left side of object. +// +// rowStart # Row containing top left corner of object. +// y1 # Distance to top of object. +// +// colEnd # Col containing lower right corner of object. +// x2 # Distance to right side of object. +// +// rowEnd # Row containing bottom right corner of object. +// y2 # Distance to bottom of object. +// +// width # Width of object frame. +// height # Height of object frame. +// +func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, height int) (int, int, int, int, int, int) { + // Adjust start column for offsets that are greater than the col width. + for x1 >= f.getColWidth(sheet, col) { + x1 -= f.getColWidth(sheet, col) + col++ + } + + // Adjust start row for offsets that are greater than the row height. + for y1 >= f.getRowHeight(sheet, row) { + y1 -= f.getRowHeight(sheet, row) + row++ + } + + // Initialized end cell to the same as the start cell. + colEnd, rowEnd := col, row + + width += x1 + height += y1 + + // Subtract the underlying cell widths to find end cell of the object. + for width >= f.getColWidth(sheet, colEnd+1) { + colEnd++ + width -= f.getColWidth(sheet, colEnd) + } + + // Subtract the underlying cell heights to find end cell of the object. + for height >= f.getRowHeight(sheet, rowEnd+1) { + rowEnd++ + height -= f.getRowHeight(sheet, rowEnd) + } + + // The end vertices are whatever is left from the width and height. + x2 := width + y2 := height + return col, row, colEnd, rowEnd, x2, y2 +} + +// getColWidth provides a function to get column width in pixels by given +// sheet name and column number. +func (f *File) getColWidth(sheet string, col int) int { + ws, _ := f.workSheetReader(sheet) + if ws.Cols != nil { + var width float64 + for _, v := range ws.Cols.Col { + if v.Min <= col && col <= v.Max { + width = v.Width + } + } + if width != 0 { + return int(convertColWidthToPixels(width)) + } + } + // Optimization for when the column widths haven't changed. + return int(defaultColWidthPixels) +} + +// GetColWidth provides a function to get column width by given worksheet name +// and column name. +func (f *File) GetColWidth(sheet, col string) (float64, error) { + colNum, err := ColumnNameToNumber(col) + if err != nil { + return defaultColWidth, err + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return defaultColWidth, err + } + if ws.Cols != nil { + var width float64 + for _, v := range ws.Cols.Col { + if v.Min <= colNum && colNum <= v.Max { + width = v.Width + } + } + if width != 0 { + return width, err + } + } + // Optimization for when the column widths haven't changed. + return defaultColWidth, err +} + +// InsertCol provides a function to insert a new column before given column +// index. For example, create a new column before column C in Sheet1: +// +// err := f.InsertCol("Sheet1", "C") +// +func (f *File) InsertCol(sheet, col string) error { + num, err := ColumnNameToNumber(col) + if err != nil { + return err + } + return f.adjustHelper(sheet, columns, num, 1) +} + +// RemoveCol provides a function to remove single column by given worksheet +// name and column index. For example, remove column C in Sheet1: +// +// err := f.RemoveCol("Sheet1", "C") +// +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. +func (f *File) RemoveCol(sheet, col string) error { + num, err := ColumnNameToNumber(col) + if err != nil { + return err + } + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + for rowIdx := range ws.SheetData.Row { + rowData := &ws.SheetData.Row[rowIdx] + for colIdx := range rowData.C { + colName, _, _ := SplitCellName(rowData.C[colIdx].R) + if colName == col { + rowData.C = append(rowData.C[:colIdx], rowData.C[colIdx+1:]...)[:len(rowData.C)-1] + break + } + } + } + return f.adjustHelper(sheet, columns, num, -1) +} + +// convertColWidthToPixels provides function to convert the width of a cell +// from user's units to pixels. Excel rounds the column width to the nearest +// pixel. If the width hasn't been set by the user we use the default value. +// If the column is hidden it has a value of zero. +func convertColWidthToPixels(width float64) float64 { + var padding float64 = 5 + var pixels float64 + var maxDigitWidth float64 = 7 + if width == 0 { + return pixels + } + if width < 1 { + pixels = (width * 12) + 0.5 + return math.Ceil(pixels) + } + pixels = (width*maxDigitWidth + 0.5) + padding + return math.Ceil(pixels) +} diff --git a/vendor/github.com/xuri/excelize/v2/comment.go b/vendor/github.com/xuri/excelize/v2/comment.go new file mode 100644 index 00000000..a7c1415b --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/comment.go @@ -0,0 +1,376 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" + "io" + "log" + "path/filepath" + "strconv" + "strings" +) + +// parseFormatCommentsSet provides a function to parse the format settings of +// the comment with default value. +func parseFormatCommentsSet(formatSet string) (*formatComment, error) { + format := formatComment{ + Author: "Author:", + Text: " ", + } + err := json.Unmarshal([]byte(formatSet), &format) + return &format, err +} + +// GetComments retrieves all comments and returns a map of worksheet name to +// the worksheet comments. +func (f *File) GetComments() (comments map[string][]Comment) { + comments = map[string][]Comment{} + for n, path := range f.sheetMap { + target := f.getSheetComments(filepath.Base(path)) + if target == "" { + continue + } + if !strings.HasPrefix(target, "/") { + target = "xl" + strings.TrimPrefix(target, "..") + } + if d := f.commentsReader(strings.TrimPrefix(target, "/")); d != nil { + var sheetComments []Comment + for _, comment := range d.CommentList.Comment { + sheetComment := Comment{} + if comment.AuthorID < len(d.Authors.Author) { + sheetComment.Author = d.Authors.Author[comment.AuthorID] + } + sheetComment.Ref = comment.Ref + sheetComment.AuthorID = comment.AuthorID + if comment.Text.T != nil { + sheetComment.Text += *comment.Text.T + } + for _, text := range comment.Text.R { + if text.T != nil { + sheetComment.Text += text.T.Val + } + } + sheetComments = append(sheetComments, sheetComment) + } + comments[n] = sheetComments + } + } + return +} + +// getSheetComments provides the method to get the target comment reference by +// given worksheet file path. +func (f *File) getSheetComments(sheetFile string) string { + rels := "xl/worksheets/_rels/" + sheetFile + ".rels" + if sheetRels := f.relsReader(rels); sheetRels != nil { + sheetRels.Lock() + defer sheetRels.Unlock() + for _, v := range sheetRels.Relationships { + if v.Type == SourceRelationshipComments { + return v.Target + } + } + } + return "" +} + +// AddComment provides the method to add comment in a sheet by given worksheet +// index, cell and format set (such as author and text). Note that the max +// author length is 255 and the max text length is 32512. For example, add a +// comment in Sheet1!$A$30: +// +// err := f.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`) +// +func (f *File) AddComment(sheet, cell, format string) error { + formatSet, err := parseFormatCommentsSet(format) + if err != nil { + return err + } + // Read sheet data. + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + commentID := f.countComments() + 1 + drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml" + sheetRelationshipsComments := "../comments" + strconv.Itoa(commentID) + ".xml" + sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml" + if ws.LegacyDrawing != nil { + // The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml. + sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID) + commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml")) + drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1) + } else { + // Add first comment for given sheet. + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "") + f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "") + f.addSheetNameSpace(sheet, SourceRelationship) + f.addSheetLegacyDrawing(sheet, rID) + } + commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml" + var colCount int + for i, l := range strings.Split(formatSet.Text, "\n") { + if ll := len(l); ll > colCount { + if i == 0 { + ll += len(formatSet.Author) + } + colCount = ll + } + } + err = f.addDrawingVML(commentID, drawingVML, cell, strings.Count(formatSet.Text, "\n")+1, colCount) + if err != nil { + return err + } + f.addComment(commentsXML, cell, formatSet) + f.addContentTypePart(commentID, "comments") + return err +} + +// addDrawingVML provides a function to create comment as +// xl/drawings/vmlDrawing%d.vml by given commit ID and cell. +func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) error { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return err + } + yAxis := col - 1 + xAxis := row - 1 + vml := f.VMLDrawing[drawingVML] + if vml == nil { + vml = &vmlDrawing{ + XMLNSv: "urn:schemas-microsoft-com:vml", + XMLNSo: "urn:schemas-microsoft-com:office:office", + XMLNSx: "urn:schemas-microsoft-com:office:excel", + XMLNSmv: "http://macVmlSchemaUri", + Shapelayout: &xlsxShapelayout{ + Ext: "edit", + IDmap: &xlsxIDmap{ + Ext: "edit", + Data: commentID, + }, + }, + Shapetype: &xlsxShapetype{ + ID: "_x0000_t202", + Coordsize: "21600,21600", + Spt: 202, + Path: "m0,0l0,21600,21600,21600,21600,0xe", + Stroke: &xlsxStroke{ + Joinstyle: "miter", + }, + VPath: &vPath{ + Gradientshapeok: "t", + Connecttype: "rect", + }, + }, + } + } + sp := encodeShape{ + Fill: &vFill{ + Color2: "#fbfe82", + Angle: -180, + Type: "gradient", + Fill: &oFill{ + Ext: "view", + Type: "gradientUnscaled", + }, + }, + Shadow: &vShadow{ + On: "t", + Color: "black", + Obscured: "t", + }, + Path: &vPath{ + Connecttype: "none", + }, + Textbox: &vTextbox{ + Style: "mso-direction-alt:auto", + Div: &xlsxDiv{ + Style: "text-align:left", + }, + }, + ClientData: &xClientData{ + ObjectType: "Note", + Anchor: fmt.Sprintf( + "%d, 23, %d, 0, %d, %d, %d, 5", + 1+yAxis, 1+xAxis, 2+yAxis+lineCount, colCount+yAxis, 2+xAxis+lineCount), + AutoFill: "True", + Row: xAxis, + Column: yAxis, + }, + } + s, _ := xml.Marshal(sp) + shape := xlsxShape{ + ID: "_x0000_s1025", + Type: "#_x0000_t202", + Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden", + Fillcolor: "#fbf6d6", + Strokecolor: "#edeaa1", + Val: string(s[13 : len(s)-14]), + } + d := f.decodeVMLDrawingReader(drawingVML) + if d != nil { + for _, v := range d.Shape { + s := xlsxShape{ + ID: "_x0000_s1025", + Type: "#_x0000_t202", + Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden", + Fillcolor: "#fbf6d6", + Strokecolor: "#edeaa1", + Val: v.Val, + } + vml.Shape = append(vml.Shape, s) + } + } + vml.Shape = append(vml.Shape, shape) + f.VMLDrawing[drawingVML] = vml + return err +} + +// addComment provides a function to create chart as xl/comments%d.xml by +// given cell and format sets. +func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) { + a := formatSet.Author + t := formatSet.Text + if len(a) > MaxFieldLength { + a = a[:MaxFieldLength] + } + if len(t) > 32512 { + t = t[:32512] + } + comments := f.commentsReader(commentsXML) + authorID := 0 + if comments == nil { + comments = &xlsxComments{Authors: xlsxAuthor{Author: []string{formatSet.Author}}} + } + if inStrSlice(comments.Authors.Author, formatSet.Author, true) == -1 { + comments.Authors.Author = append(comments.Authors.Author, formatSet.Author) + authorID = len(comments.Authors.Author) - 1 + } + defaultFont := f.GetDefaultFont() + bold := "" + cmt := xlsxComment{ + Ref: cell, + AuthorID: authorID, + Text: xlsxText{ + R: []xlsxR{ + { + RPr: &xlsxRPr{ + B: &bold, + Sz: &attrValFloat{Val: float64Ptr(9)}, + Color: &xlsxColor{ + Indexed: 81, + }, + RFont: &attrValString{Val: stringPtr(defaultFont)}, + Family: &attrValInt{Val: intPtr(2)}, + }, + T: &xlsxT{Val: a}, + }, + { + RPr: &xlsxRPr{ + Sz: &attrValFloat{Val: float64Ptr(9)}, + Color: &xlsxColor{ + Indexed: 81, + }, + RFont: &attrValString{Val: stringPtr(defaultFont)}, + Family: &attrValInt{Val: intPtr(2)}, + }, + T: &xlsxT{Val: t}, + }, + }, + }, + } + comments.CommentList.Comment = append(comments.CommentList.Comment, cmt) + f.Comments[commentsXML] = comments +} + +// countComments provides a function to get comments files count storage in +// the folder xl. +func (f *File) countComments() int { + c1, c2 := 0, 0 + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/comments") { + c1++ + } + return true + }) + for rel := range f.Comments { + if strings.Contains(rel, "xl/comments") { + c2++ + } + } + if c1 < c2 { + return c2 + } + return c1 +} + +// decodeVMLDrawingReader provides a function to get the pointer to the +// structure after deserialization of xl/drawings/vmlDrawing%d.xml. +func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing { + var err error + + if f.DecodeVMLDrawing[path] == nil { + c, ok := f.Pkg.Load(path) + if ok && c != nil { + f.DecodeVMLDrawing[path] = new(decodeVmlDrawing) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))). + Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + } + } + return f.DecodeVMLDrawing[path] +} + +// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml +// after serialize structure. +func (f *File) vmlDrawingWriter() { + for path, vml := range f.VMLDrawing { + if vml != nil { + v, _ := xml.Marshal(vml) + f.Pkg.Store(path, v) + } + } +} + +// commentsReader provides a function to get the pointer to the structure +// after deserialization of xl/comments%d.xml. +func (f *File) commentsReader(path string) *xlsxComments { + var err error + if f.Comments[path] == nil { + content, ok := f.Pkg.Load(path) + if ok && content != nil { + f.Comments[path] = new(xlsxComments) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). + Decode(f.Comments[path]); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + } + } + return f.Comments[path] +} + +// commentsWriter provides a function to save xl/comments%d.xml after +// serialize structure. +func (f *File) commentsWriter() { + for path, c := range f.Comments { + if c != nil { + v, _ := xml.Marshal(c) + f.saveFileList(path, v) + } + } +} diff --git a/vendor/github.com/xuri/excelize/v2/crypt.go b/vendor/github.com/xuri/excelize/v2/crypt.go new file mode 100644 index 00000000..da9feb4a --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/crypt.go @@ -0,0 +1,664 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/md5" + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "encoding/binary" + "encoding/xml" + "hash" + "reflect" + "strings" + + "github.com/richardlehane/mscfb" + "golang.org/x/crypto/md4" + "golang.org/x/crypto/ripemd160" + "golang.org/x/text/encoding/unicode" +) + +var ( + blockKey = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption + blockKeyHmacKey = []byte{0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6} + blockKeyHmacValue = []byte{0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33} + blockKeyVerifierHashInput = []byte{0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79} + blockKeyVerifierHashValue = []byte{0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e} + packageOffset = 8 // First 8 bytes are the size of the stream + packageEncryptionChunkSize = 4096 + iterCount = 50000 + sheetProtectionSpinCount = 1e5 + oleIdentifier = []byte{ + 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1, + } +) + +// Encryption specifies the encryption structure, streams, and storages are +// required when encrypting ECMA-376 documents. +type Encryption struct { + XMLName xml.Name `xml:"encryption"` + KeyData KeyData `xml:"keyData"` + DataIntegrity DataIntegrity `xml:"dataIntegrity"` + KeyEncryptors KeyEncryptors `xml:"keyEncryptors"` +} + +// KeyData specifies the cryptographic attributes used to encrypt the data. +type KeyData struct { + SaltSize int `xml:"saltSize,attr"` + BlockSize int `xml:"blockSize,attr"` + KeyBits int `xml:"keyBits,attr"` + HashSize int `xml:"hashSize,attr"` + CipherAlgorithm string `xml:"cipherAlgorithm,attr"` + CipherChaining string `xml:"cipherChaining,attr"` + HashAlgorithm string `xml:"hashAlgorithm,attr"` + SaltValue string `xml:"saltValue,attr"` +} + +// DataIntegrity specifies the encrypted copies of the salt and hash values +// used to help ensure that the integrity of the encrypted data has not been +// compromised. +type DataIntegrity struct { + EncryptedHmacKey string `xml:"encryptedHmacKey,attr"` + EncryptedHmacValue string `xml:"encryptedHmacValue,attr"` +} + +// KeyEncryptors specifies the key encryptors used to encrypt the data. +type KeyEncryptors struct { + KeyEncryptor []KeyEncryptor `xml:"keyEncryptor"` +} + +// KeyEncryptor specifies that the schema used by this encryptor is the schema +// specified for password-based encryptors. +type KeyEncryptor struct { + XMLName xml.Name `xml:"keyEncryptor"` + URI string `xml:"uri,attr"` + EncryptedKey EncryptedKey `xml:"encryptedKey"` +} + +// EncryptedKey used to generate the encrypting key. +type EncryptedKey struct { + XMLName xml.Name `xml:"http://schemas.microsoft.com/office/2006/keyEncryptor/password encryptedKey"` + SpinCount int `xml:"spinCount,attr"` + EncryptedVerifierHashInput string `xml:"encryptedVerifierHashInput,attr"` + EncryptedVerifierHashValue string `xml:"encryptedVerifierHashValue,attr"` + EncryptedKeyValue string `xml:"encryptedKeyValue,attr"` + KeyData +} + +// StandardEncryptionHeader structure is used by ECMA-376 document encryption +// [ECMA-376] and Office binary document RC4 CryptoAPI encryption, to specify +// encryption properties for an encrypted stream. +type StandardEncryptionHeader struct { + Flags uint32 + SizeExtra uint32 + AlgID uint32 + AlgIDHash uint32 + KeySize uint32 + ProviderType uint32 + Reserved1 uint32 + Reserved2 uint32 + CspName string +} + +// StandardEncryptionVerifier structure is used by Office Binary Document RC4 +// CryptoAPI Encryption and ECMA-376 Document Encryption. Every usage of this +// structure MUST specify the hashing algorithm and encryption algorithm used +// in the EncryptionVerifier structure. +type StandardEncryptionVerifier struct { + SaltSize uint32 + Salt []byte + EncryptedVerifier []byte + VerifierHashSize uint32 + EncryptedVerifierHash []byte +} + +// Decrypt API decrypts the CFB file format with ECMA-376 agile encryption and +// standard encryption. Support cryptographic algorithm: MD4, MD5, RIPEMD-160, +// SHA1, SHA256, SHA384 and SHA512 currently. +func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) { + doc, err := mscfb.New(bytes.NewReader(raw)) + if err != nil { + return + } + encryptionInfoBuf, encryptedPackageBuf := extractPart(doc) + mechanism, err := encryptionMechanism(encryptionInfoBuf) + if err != nil || mechanism == "extensible" { + return + } + switch mechanism { + case "agile": + return agileDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt) + case "standard": + return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt) + default: + err = ErrUnsupportedEncryptMechanism + } + return +} + +// Encrypt API encrypt data with the password. +func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) { + // Generate a random key to use to encrypt the document. Excel uses 32 bytes. We'll use the password to encrypt this key. + packageKey, _ := randomBytes(32) + keyDataSaltValue, _ := randomBytes(16) + keyEncryptors, _ := randomBytes(16) + encryptionInfo := Encryption{ + KeyData: KeyData{ + BlockSize: 16, + KeyBits: len(packageKey) * 8, + HashSize: 64, + CipherAlgorithm: "AES", + CipherChaining: "ChainingModeCBC", + HashAlgorithm: "SHA512", + SaltValue: base64.StdEncoding.EncodeToString(keyDataSaltValue), + }, + KeyEncryptors: KeyEncryptors{ + KeyEncryptor: []KeyEncryptor{{ + EncryptedKey: EncryptedKey{ + SpinCount: 100000, KeyData: KeyData{ + CipherAlgorithm: "AES", + CipherChaining: "ChainingModeCBC", + HashAlgorithm: "SHA512", + HashSize: 64, + BlockSize: 16, + KeyBits: 256, + SaltValue: base64.StdEncoding.EncodeToString(keyEncryptors), + }, + }, + }}, + }, + } + + // Package Encryption + + // Encrypt package using the package key. + encryptedPackage, err := cryptPackage(true, packageKey, raw, encryptionInfo) + if err != nil { + return + } + + // Data Integrity + + // Create the data integrity fields used by clients for integrity checks. + // Generate a random array of bytes to use in HMAC. The docs say to use the same length as the key salt, but Excel seems to use 64. + hmacKey, _ := randomBytes(64) + if err != nil { + return + } + // Create an initialization vector using the package encryption info and the appropriate block key. + hmacKeyIV, err := createIV(blockKeyHmacKey, encryptionInfo) + if err != nil { + return + } + // Use the package key and the IV to encrypt the HMAC key. + encryptedHmacKey, _ := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, packageKey, hmacKeyIV, hmacKey) + // Create the HMAC. + h := hmac.New(sha512.New, append(hmacKey, encryptedPackage...)) + for _, buf := range [][]byte{hmacKey, encryptedPackage} { + _, _ = h.Write(buf) + } + hmacValue := h.Sum(nil) + // Generate an initialization vector for encrypting the resulting HMAC value. + hmacValueIV, err := createIV(blockKeyHmacValue, encryptionInfo) + if err != nil { + return + } + // Encrypt the value. + encryptedHmacValue, _ := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, packageKey, hmacValueIV, hmacValue) + // Put the encrypted key and value on the encryption info. + encryptionInfo.DataIntegrity.EncryptedHmacKey = base64.StdEncoding.EncodeToString(encryptedHmacKey) + encryptionInfo.DataIntegrity.EncryptedHmacValue = base64.StdEncoding.EncodeToString(encryptedHmacValue) + + // Key Encryption + + // Convert the password to an encryption key. + key, err := convertPasswdToKey(opt.Password, blockKey, encryptionInfo) + if err != nil { + return + } + // Encrypt the package key with the encryption key. + encryptedKeyValue, _ := crypt(true, encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.CipherAlgorithm, encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.CipherChaining, key, keyEncryptors, packageKey) + encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedKeyValue = base64.StdEncoding.EncodeToString(encryptedKeyValue) + + // Verifier hash + + // Create a random byte array for hashing. + verifierHashInput, _ := randomBytes(16) + // Create an encryption key from the password for the input. + verifierHashInputKey, err := convertPasswdToKey(opt.Password, blockKeyVerifierHashInput, encryptionInfo) + if err != nil { + return + } + // Use the key to encrypt the verifier input. + encryptedVerifierHashInput, err := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, verifierHashInputKey, keyEncryptors, verifierHashInput) + if err != nil { + return + } + encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedVerifierHashInput = base64.StdEncoding.EncodeToString(encryptedVerifierHashInput) + // Create a hash of the input. + verifierHashValue := hashing(encryptionInfo.KeyData.HashAlgorithm, verifierHashInput) + // Create an encryption key from the password for the hash. + verifierHashValueKey, err := convertPasswdToKey(opt.Password, blockKeyVerifierHashValue, encryptionInfo) + if err != nil { + return + } + // Use the key to encrypt the hash value. + encryptedVerifierHashValue, err := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, verifierHashValueKey, keyEncryptors, verifierHashValue) + if err != nil { + return + } + encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedVerifierHashValue = base64.StdEncoding.EncodeToString(encryptedVerifierHashValue) + // Marshal the encryption info buffer. + encryptionInfoBuffer, err := xml.Marshal(encryptionInfo) + if err != nil { + return + } + // TODO: Create a new CFB. + _, _ = encryptedPackage, encryptionInfoBuffer + err = ErrEncrypt + return +} + +// extractPart extract data from storage by specified part name. +func extractPart(doc *mscfb.Reader) (encryptionInfoBuf, encryptedPackageBuf []byte) { + for entry, err := doc.Next(); err == nil; entry, err = doc.Next() { + switch entry.Name { + case "EncryptionInfo": + buf := make([]byte, entry.Size) + i, _ := doc.Read(buf) + if i > 0 { + encryptionInfoBuf = buf + } + case "EncryptedPackage": + buf := make([]byte, entry.Size) + i, _ := doc.Read(buf) + if i > 0 { + encryptedPackageBuf = buf + } + } + } + return +} + +// encryptionMechanism parse password-protected documents created mechanism. +func encryptionMechanism(buffer []byte) (mechanism string, err error) { + if len(buffer) < 4 { + err = ErrUnknownEncryptMechanism + return + } + versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[:2]), binary.LittleEndian.Uint16(buffer[2:4]) + if versionMajor == 4 && versionMinor == 4 { + mechanism = "agile" + return + } else if (2 <= versionMajor && versionMajor <= 4) && versionMinor == 2 { + mechanism = "standard" + return + } else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 { + mechanism = "extensible" + } + err = ErrUnsupportedEncryptMechanism + return +} + +// ECMA-376 Standard Encryption + +// standardDecrypt decrypt the CFB file format with ECMA-376 standard encryption. +func standardDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) ([]byte, error) { + encryptionHeaderSize := binary.LittleEndian.Uint32(encryptionInfoBuf[8:12]) + block := encryptionInfoBuf[12 : 12+encryptionHeaderSize] + header := StandardEncryptionHeader{ + Flags: binary.LittleEndian.Uint32(block[:4]), + SizeExtra: binary.LittleEndian.Uint32(block[4:8]), + AlgID: binary.LittleEndian.Uint32(block[8:12]), + AlgIDHash: binary.LittleEndian.Uint32(block[12:16]), + KeySize: binary.LittleEndian.Uint32(block[16:20]), + ProviderType: binary.LittleEndian.Uint32(block[20:24]), + Reserved1: binary.LittleEndian.Uint32(block[24:28]), + Reserved2: binary.LittleEndian.Uint32(block[28:32]), + CspName: string(block[32:]), + } + block = encryptionInfoBuf[12+encryptionHeaderSize:] + algIDMap := map[uint32]string{ + 0x0000660E: "AES-128", + 0x0000660F: "AES-192", + 0x00006610: "AES-256", + } + algorithm := "AES" + _, ok := algIDMap[header.AlgID] + if !ok { + algorithm = "RC4" + } + verifier := standardEncryptionVerifier(algorithm, block) + secretKey, err := standardConvertPasswdToKey(header, verifier, opt) + if err != nil { + return nil, err + } + // decrypted data + x := encryptedPackageBuf[8:] + blob, err := aes.NewCipher(secretKey) + if err != nil { + return nil, err + } + decrypted := make([]byte, len(x)) + size := 16 + for bs, be := 0, size; bs < len(x); bs, be = bs+size, be+size { + blob.Decrypt(decrypted[bs:be], x[bs:be]) + } + return decrypted, err +} + +// standardEncryptionVerifier extract ECMA-376 standard encryption verifier. +func standardEncryptionVerifier(algorithm string, blob []byte) StandardEncryptionVerifier { + verifier := StandardEncryptionVerifier{ + SaltSize: binary.LittleEndian.Uint32(blob[:4]), + Salt: blob[4:20], + EncryptedVerifier: blob[20:36], + VerifierHashSize: binary.LittleEndian.Uint32(blob[36:40]), + } + if algorithm == "RC4" { + verifier.EncryptedVerifierHash = blob[40:60] + } else if algorithm == "AES" { + verifier.EncryptedVerifierHash = blob[40:72] + } + return verifier +} + +// standardConvertPasswdToKey generate intermediate key from given password. +func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier StandardEncryptionVerifier, opt *Options) ([]byte, error) { + encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() + passwordBuffer, err := encoder.Bytes([]byte(opt.Password)) + if err != nil { + return nil, err + } + key := hashing("sha1", verifier.Salt, passwordBuffer) + for i := 0; i < iterCount; i++ { + iterator := createUInt32LEBuffer(i, 4) + key = hashing("sha1", iterator, key) + } + var block int + hFinal := hashing("sha1", key, createUInt32LEBuffer(block, 4)) + cbRequiredKeyLength := int(header.KeySize) / 8 + cbHash := sha1.Size + buf1 := bytes.Repeat([]byte{0x36}, 64) + buf1 = append(standardXORBytes(hFinal, buf1[:cbHash]), buf1[cbHash:]...) + x1 := hashing("sha1", buf1) + buf2 := bytes.Repeat([]byte{0x5c}, 64) + buf2 = append(standardXORBytes(hFinal, buf2[:cbHash]), buf2[cbHash:]...) + x2 := hashing("sha1", buf2) + x3 := append(x1, x2...) + keyDerived := x3[:cbRequiredKeyLength] + return keyDerived, err +} + +// standardXORBytes perform XOR operations for two bytes slice. +func standardXORBytes(a, b []byte) []byte { + r := make([][2]byte, len(a)) + for i, e := range a { + r[i] = [2]byte{e, b[i]} + } + buf := make([]byte, len(a)) + for p, q := range r { + buf[p] = q[0] ^ q[1] + } + return buf +} + +// ECMA-376 Agile Encryption + +// agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption. +// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256, +// SHA384 and SHA512. +func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (packageBuf []byte, err error) { + var encryptionInfo Encryption + if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil { + return + } + // Convert the password into an encryption key. + key, err := convertPasswdToKey(opt.Password, blockKey, encryptionInfo) + if err != nil { + return + } + // Use the key to decrypt the package key. + encryptedKey := encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey + saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue) + if err != nil { + return + } + encryptedKeyValue, err := base64.StdEncoding.DecodeString(encryptedKey.EncryptedKeyValue) + if err != nil { + return + } + packageKey, _ := crypt(false, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, key, saltValue, encryptedKeyValue) + // Use the package key to decrypt the package. + return cryptPackage(false, packageKey, encryptedPackageBuf, encryptionInfo) +} + +// convertPasswdToKey convert the password into an encryption key. +func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (key []byte, err error) { + var b bytes.Buffer + saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue) + if err != nil { + return + } + b.Write(saltValue) + encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() + passwordBuffer, err := encoder.Bytes([]byte(passwd)) + if err != nil { + return + } + b.Write(passwordBuffer) + // Generate the initial hash. + key = hashing(encryption.KeyData.HashAlgorithm, b.Bytes()) + // Now regenerate until spin count. + for i := 0; i < encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SpinCount; i++ { + iterator := createUInt32LEBuffer(i, 4) + key = hashing(encryption.KeyData.HashAlgorithm, iterator, key) + } + // Now generate the final hash. + key = hashing(encryption.KeyData.HashAlgorithm, key, blockKey) + // Truncate or pad as needed to get to length of keyBits. + keyBytes := encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.KeyBits / 8 + if len(key) < keyBytes { + tmp := make([]byte, 0x36) + key = append(key, tmp...) + } else if len(key) > keyBytes { + key = key[:keyBytes] + } + return +} + +// hashing data by specified hash algorithm. +func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) { + hashMap := map[string]hash.Hash{ + "md4": md4.New(), + "md5": md5.New(), + "ripemd-160": ripemd160.New(), + "sha1": sha1.New(), + "sha256": sha256.New(), + "sha384": sha512.New384(), + "sha512": sha512.New(), + } + handler, ok := hashMap[strings.ToLower(hashAlgorithm)] + if !ok { + return key + } + for _, buf := range buffer { + _, _ = handler.Write(buf) + } + key = handler.Sum(nil) + return key +} + +// createUInt32LEBuffer create buffer with little endian 32-bit unsigned +// integer. +func createUInt32LEBuffer(value int, bufferSize int) []byte { + buf := make([]byte, bufferSize) + binary.LittleEndian.PutUint32(buf, uint32(value)) + return buf +} + +// parseEncryptionInfo parse the encryption info XML into an object. +func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err error) { + err = xml.Unmarshal(encryptionInfo, &encryption) + return +} + +// crypt encrypt / decrypt input by given cipher algorithm, cipher chaining, +// key and initialization vector. +func crypt(encrypt bool, cipherAlgorithm, cipherChaining string, key, iv, input []byte) (packageKey []byte, err error) { + block, err := aes.NewCipher(key) + if err != nil { + return input, err + } + var stream cipher.BlockMode + if encrypt { + stream = cipher.NewCBCEncrypter(block, iv) + } else { + stream = cipher.NewCBCDecrypter(block, iv) + } + stream.CryptBlocks(input, input) + return input, nil +} + +// cryptPackage encrypt / decrypt package by given packageKey and encryption +// info. +func cryptPackage(encrypt bool, packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) { + encryptedKey, offset := encryption.KeyData, packageOffset + if encrypt { + offset = 0 + } + var i, start, end int + var iv, outputChunk []byte + for end < len(input) { + start = end + end = start + packageEncryptionChunkSize + + if end > len(input) { + end = len(input) + } + // Grab the next chunk + var inputChunk []byte + if (end + offset) < len(input) { + inputChunk = input[start+offset : end+offset] + } else { + inputChunk = input[start+offset : end] + } + + // Pad the chunk if it is not an integer multiple of the block size + remainder := len(inputChunk) % encryptedKey.BlockSize + if remainder != 0 { + inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...) + } + // Create the initialization vector + iv, err = createIV(i, encryption) + if err != nil { + return + } + // Encrypt/decrypt the chunk and add it to the array + outputChunk, err = crypt(encrypt, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, packageKey, iv, inputChunk) + if err != nil { + return + } + outputChunks = append(outputChunks, outputChunk...) + i++ + } + if encrypt { + outputChunks = append(createUInt32LEBuffer(len(input), 8), outputChunks...) + } + return +} + +// createIV create an initialization vector (IV). +func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) { + encryptedKey := encryption.KeyData + // Create the block key from the current index + var blockKeyBuf []byte + if reflect.TypeOf(blockKey).Kind() == reflect.Int { + blockKeyBuf = createUInt32LEBuffer(blockKey.(int), 4) + } else { + blockKeyBuf = blockKey.([]byte) + } + saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue) + if err != nil { + return nil, err + } + // Create the initialization vector by hashing the salt with the block key. + // Truncate or pad as needed to meet the block size. + iv := hashing(encryptedKey.HashAlgorithm, append(saltValue, blockKeyBuf...)) + if len(iv) < encryptedKey.BlockSize { + tmp := make([]byte, 0x36) + iv = append(iv, tmp...) + } else if len(iv) > encryptedKey.BlockSize { + iv = iv[:encryptedKey.BlockSize] + } + return iv, nil +} + +// randomBytes returns securely generated random bytes. It will return an +// error if the system's secure random number generator fails to function +// correctly, in which case the caller should not continue. +func randomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + return b, err +} + +// ISO Write Protection Method + +// genISOPasswdHash implements the ISO password hashing algorithm by given +// plaintext password, name of the cryptographic hash algorithm, salt value +// and spin count. +func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashValue, saltValue string, err error) { + if len(passwd) < 1 || len(passwd) > MaxFieldLength { + err = ErrPasswordLengthInvalid + return + } + algorithmName, ok := map[string]string{ + "MD4": "md4", + "MD5": "md5", + "SHA-1": "sha1", + "SHA-256": "sha256", + "SHA-384": "sha384", + "SHA-512": "sha512", + }[hashAlgorithm] + if !ok { + err = ErrUnsupportedHashAlgorithm + return + } + var b bytes.Buffer + s, _ := randomBytes(16) + if salt != "" { + if s, err = base64.StdEncoding.DecodeString(salt); err != nil { + return + } + } + b.Write(s) + encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() + passwordBuffer, _ := encoder.Bytes([]byte(passwd)) + b.Write(passwordBuffer) + // Generate the initial hash. + key := hashing(algorithmName, b.Bytes()) + // Now regenerate until spin count. + for i := 0; i < spinCount; i++ { + iterator := createUInt32LEBuffer(i, 4) + key = hashing(algorithmName, key, iterator) + } + hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s) + return +} diff --git a/vendor/github.com/xuri/excelize/v2/datavalidation.go b/vendor/github.com/xuri/excelize/v2/datavalidation.go new file mode 100644 index 00000000..4df2c505 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/datavalidation.go @@ -0,0 +1,336 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "fmt" + "math" + "strings" + "unicode/utf16" +) + +// DataValidationType defined the type of data validation. +type DataValidationType int + +// Data validation types. +const ( + _DataValidationType = iota + typeNone // inline use + DataValidationTypeCustom + DataValidationTypeDate + DataValidationTypeDecimal + typeList // inline use + DataValidationTypeTextLength + DataValidationTypeTime + // DataValidationTypeWhole Integer + DataValidationTypeWhole +) + +// DataValidationErrorStyle defined the style of data validation error alert. +type DataValidationErrorStyle int + +// Data validation error styles. +const ( + _ DataValidationErrorStyle = iota + DataValidationErrorStyleStop + DataValidationErrorStyleWarning + DataValidationErrorStyleInformation +) + +// Data validation error styles. +const ( + styleStop = "stop" + styleWarning = "warning" + styleInformation = "information" +) + +// DataValidationOperator operator enum. +type DataValidationOperator int + +// Data validation operators. +const ( + _DataValidationOperator = iota + DataValidationOperatorBetween + DataValidationOperatorEqual + DataValidationOperatorGreaterThan + DataValidationOperatorGreaterThanOrEqual + DataValidationOperatorLessThan + DataValidationOperatorLessThanOrEqual + DataValidationOperatorNotBetween + DataValidationOperatorNotEqual +) + +// formulaEscaper mimics the Excel escaping rules for data validation, +// which converts `"` to `""` instead of `"`. +var formulaEscaper = strings.NewReplacer( + `&`, `&`, + `<`, `<`, + `>`, `>`, + `"`, `""`, +) + +// NewDataValidation return data validation struct. +func NewDataValidation(allowBlank bool) *DataValidation { + return &DataValidation{ + AllowBlank: allowBlank, + ShowErrorMessage: false, + ShowInputMessage: false, + } +} + +// SetError set error notice. +func (dd *DataValidation) SetError(style DataValidationErrorStyle, title, msg string) { + dd.Error = &msg + dd.ErrorTitle = &title + strStyle := styleStop + switch style { + case DataValidationErrorStyleStop: + strStyle = styleStop + case DataValidationErrorStyleWarning: + strStyle = styleWarning + case DataValidationErrorStyleInformation: + strStyle = styleInformation + + } + dd.ShowErrorMessage = true + dd.ErrorStyle = &strStyle +} + +// SetInput set prompt notice. +func (dd *DataValidation) SetInput(title, msg string) { + dd.ShowInputMessage = true + dd.PromptTitle = &title + dd.Prompt = &msg +} + +// SetDropList data validation list. +func (dd *DataValidation) SetDropList(keys []string) error { + formula := strings.Join(keys, ",") + if MaxFieldLength < len(utf16.Encode([]rune(formula))) { + return ErrDataValidationFormulaLength + } + dd.Formula1 = fmt.Sprintf(`"%s"`, formulaEscaper.Replace(formula)) + dd.Type = convDataValidationType(typeList) + return nil +} + +// SetRange provides function to set data validation range in drop list, only +// accepts int, float64, or string data type formula argument. +func (dd *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o DataValidationOperator) error { + var formula1, formula2 string + switch v := f1.(type) { + case int: + formula1 = fmt.Sprintf("%d", v) + case float64: + if math.Abs(v) > math.MaxFloat32 { + return ErrDataValidationRange + } + formula1 = fmt.Sprintf("%.17g", v) + case string: + formula1 = fmt.Sprintf("%s", v) + default: + return ErrParameterInvalid + } + switch v := f2.(type) { + case int: + formula2 = fmt.Sprintf("%d", v) + case float64: + if math.Abs(v) > math.MaxFloat32 { + return ErrDataValidationRange + } + formula2 = fmt.Sprintf("%.17g", v) + case string: + formula2 = fmt.Sprintf("%s", v) + default: + return ErrParameterInvalid + } + dd.Formula1, dd.Formula2 = formula1, formula2 + dd.Type = convDataValidationType(t) + dd.Operator = convDataValidationOperator(o) + return nil +} + +// SetSqrefDropList provides set data validation on a range with source +// reference range of the worksheet by given data validation object and +// worksheet name. The data validation object can be created by +// NewDataValidation function. For example, set data validation on +// Sheet1!A7:B8 with validation criteria source Sheet1!E1:E3 settings, create +// in-cell dropdown by allowing list source: +// +// dvRange := excelize.NewDataValidation(true) +// dvRange.Sqref = "A7:B8" +// dvRange.SetSqrefDropList("$E$1:$E$3") +// f.AddDataValidation("Sheet1", dvRange) +// +func (dd *DataValidation) SetSqrefDropList(sqref string) { + dd.Formula1 = fmt.Sprintf("%s", sqref) + dd.Type = convDataValidationType(typeList) +} + +// SetSqref provides function to set data validation range in drop list. +func (dd *DataValidation) SetSqref(sqref string) { + if dd.Sqref == "" { + dd.Sqref = sqref + } else { + dd.Sqref = fmt.Sprintf("%s %s", dd.Sqref, sqref) + } +} + +// convDataValidationType get excel data validation type. +func convDataValidationType(t DataValidationType) string { + typeMap := map[DataValidationType]string{ + typeNone: "none", + DataValidationTypeCustom: "custom", + DataValidationTypeDate: "date", + DataValidationTypeDecimal: "decimal", + typeList: "list", + DataValidationTypeTextLength: "textLength", + DataValidationTypeTime: "time", + DataValidationTypeWhole: "whole", + } + + return typeMap[t] +} + +// convDataValidationOperator get excel data validation operator. +func convDataValidationOperator(o DataValidationOperator) string { + typeMap := map[DataValidationOperator]string{ + DataValidationOperatorBetween: "between", + DataValidationOperatorEqual: "equal", + DataValidationOperatorGreaterThan: "greaterThan", + DataValidationOperatorGreaterThanOrEqual: "greaterThanOrEqual", + DataValidationOperatorLessThan: "lessThan", + DataValidationOperatorLessThanOrEqual: "lessThanOrEqual", + DataValidationOperatorNotBetween: "notBetween", + DataValidationOperatorNotEqual: "notEqual", + } + + return typeMap[o] +} + +// AddDataValidation provides set data validation on a range of the worksheet +// by given data validation object and worksheet name. The data validation +// object can be created by NewDataValidation function. +// +// Example 1, set data validation on Sheet1!A1:B2 with validation criteria +// settings, show error alert after invalid data is entered with "Stop" style +// and custom title "error body": +// +// dvRange := excelize.NewDataValidation(true) +// dvRange.Sqref = "A1:B2" +// dvRange.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorBetween) +// dvRange.SetError(excelize.DataValidationErrorStyleStop, "error title", "error body") +// err := f.AddDataValidation("Sheet1", dvRange) +// +// Example 2, set data validation on Sheet1!A3:B4 with validation criteria +// settings, and show input message when cell is selected: +// +// dvRange = excelize.NewDataValidation(true) +// dvRange.Sqref = "A3:B4" +// dvRange.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorGreaterThan) +// dvRange.SetInput("input title", "input body") +// err = f.AddDataValidation("Sheet1", dvRange) +// +// Example 3, set data validation on Sheet1!A5:B6 with validation criteria +// settings, create in-cell dropdown by allowing list source: +// +// dvRange = excelize.NewDataValidation(true) +// dvRange.Sqref = "A5:B6" +// dvRange.SetDropList([]string{"1", "2", "3"}) +// err = f.AddDataValidation("Sheet1", dvRange) +// +func (f *File) AddDataValidation(sheet string, dv *DataValidation) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if nil == ws.DataValidations { + ws.DataValidations = new(xlsxDataValidations) + } + ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, dv) + ws.DataValidations.Count = len(ws.DataValidations.DataValidation) + return err +} + +// DeleteDataValidation delete data validation by given worksheet name and +// reference sequence. +func (f *File) DeleteDataValidation(sheet, sqref string) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if ws.DataValidations == nil { + return nil + } + delCells, err := f.flatSqref(sqref) + if err != nil { + return err + } + dv := ws.DataValidations + for i := 0; i < len(dv.DataValidation); i++ { + var applySqref []string + colCells, err := f.flatSqref(dv.DataValidation[i].Sqref) + if err != nil { + return err + } + for col, cells := range delCells { + for _, cell := range cells { + idx := inCoordinates(colCells[col], cell) + if idx != -1 { + colCells[col] = append(colCells[col][:idx], colCells[col][idx+1:]...) + } + } + } + for _, col := range colCells { + applySqref = append(applySqref, f.squashSqref(col)...) + } + dv.DataValidation[i].Sqref = strings.Join(applySqref, " ") + if len(applySqref) == 0 { + dv.DataValidation = append(dv.DataValidation[:i], dv.DataValidation[i+1:]...) + i-- + } + } + dv.Count = len(dv.DataValidation) + if dv.Count == 0 { + ws.DataValidations = nil + } + return nil +} + +// squashSqref generates cell reference sequence by given cells coordinates list. +func (f *File) squashSqref(cells [][]int) []string { + if len(cells) == 1 { + cell, _ := CoordinatesToCellName(cells[0][0], cells[0][1]) + return []string{cell} + } else if len(cells) == 0 { + return []string{} + } + var res []string + l, r := 0, 0 + for i := 1; i < len(cells); i++ { + if cells[i][0] == cells[r][0] && cells[i][1]-cells[r][1] > 1 { + curr, _ := f.coordinatesToAreaRef(append(cells[l], cells[r]...)) + if l == r { + curr, _ = CoordinatesToCellName(cells[l][0], cells[l][1]) + } + res = append(res, curr) + l, r = i, i + } else { + r++ + } + } + curr, _ := f.coordinatesToAreaRef(append(cells[l], cells[r]...)) + if l == r { + curr, _ = CoordinatesToCellName(cells[l][0], cells[l][1]) + } + return append(res, curr) +} diff --git a/vendor/github.com/xuri/excelize/v2/date.go b/vendor/github.com/xuri/excelize/v2/date.go new file mode 100644 index 00000000..83d23ccf --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/date.go @@ -0,0 +1,214 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "math" + "time" +) + +const ( + nanosInADay = float64((24 * time.Hour) / time.Nanosecond) + dayNanoseconds = 24 * time.Hour + maxDuration = 290 * 364 * dayNanoseconds + roundEpsilon = 1e-9 +) + +var ( + daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} + excel1900Epoc = time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC) + excel1904Epoc = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) + excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC) + excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond) +) + +// timeToExcelTime provides a function to convert time to Excel time. +func timeToExcelTime(t time.Time) (float64, error) { + // TODO in future this should probably also handle date1904 and like TimeFromExcelTime + + if t.Before(excelMinTime1900) { + return 0.0, nil + } + + tt := t + diff := t.Sub(excelMinTime1900) + result := float64(0) + + for diff >= maxDuration { + result += float64(maxDuration / dayNanoseconds) + tt = tt.Add(-maxDuration) + diff = tt.Sub(excelMinTime1900) + } + + rem := diff % dayNanoseconds + result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds) + + // Excel dates after 28th February 1900 are actually one day out. + // Excel behaves as though the date 29th February 1900 existed, which it didn't. + // Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet + // program that had the majority market share at the time; Lotus 1-2-3. + // https://www.myonlinetraininghub.com/excel-date-and-time + if t.After(excelBuggyPeriodStart) { + result += 1.0 + } + return result, nil +} + +// shiftJulianToNoon provides a function to process julian date to noon. +func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) { + switch { + case -0.5 < julianFraction && julianFraction < 0.5: + julianFraction += 0.5 + case julianFraction >= 0.5: + julianDays++ + julianFraction -= 0.5 + case julianFraction <= -0.5: + julianDays-- + julianFraction += 1.5 + } + return julianDays, julianFraction +} + +// fractionOfADay provides a function to return the integer values for hour, +// minutes, seconds and nanoseconds that comprised a given fraction of a day. +// values would round to 1 us. +func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) { + const ( + c1us = 1e3 + c1s = 1e9 + c1day = 24 * 60 * 60 * c1s + ) + + frac := int64(c1day*fraction + c1us/2) + nanoseconds = int((frac%c1s)/c1us) * c1us + frac /= c1s + seconds = int(frac % 60) + frac /= 60 + minutes = int(frac % 60) + hours = int(frac / 60) + return +} + +// julianDateToGregorianTime provides a function to convert julian date to +// gregorian time. +func julianDateToGregorianTime(part1, part2 float64) time.Time { + part1I, part1F := math.Modf(part1) + part2I, part2F := math.Modf(part2) + julianDays := part1I + part2I + julianFraction := part1F + part2F + julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction) + day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays)) + hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction) + return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC) +} + +// doTheFliegelAndVanFlandernAlgorithm; By this point generations of +// programmers have repeated the algorithm sent to the editor of +// "Communications of the ACM" in 1968 (published in CACM, volume 11, number +// 10, October 1968, p.657). None of those programmers seems to have found it +// necessary to explain the constants or variable names set out by Henry F. +// Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that jounal and +// expand an explanation here - that day is not today. +func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) { + l := jd + 68569 + n := (4 * l) / 146097 + l = l - (146097*n+3)/4 + i := (4000 * (l + 1)) / 1461001 + l = l - (1461*i)/4 + 31 + j := (80 * l) / 2447 + d := l - (2447*j)/80 + l = j / 11 + m := j + 2 - (12 * l) + y := 100*(n-49) + i + l + return d, m, y +} + +// timeFromExcelTime provides a function to convert an excelTime +// representation (stored as a floating point number) to a time.Time. +func timeFromExcelTime(excelTime float64, date1904 bool) time.Time { + var date time.Time + wholeDaysPart := int(excelTime) + // Excel uses Julian dates prior to March 1st 1900, and Gregorian + // thereafter. + if wholeDaysPart <= 61 { + const OFFSET1900 = 15018.0 + const OFFSET1904 = 16480.0 + const MJD0 float64 = 2400000.5 + var date time.Time + if date1904 { + date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904) + } else { + date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900) + } + return date + } + floatPart := excelTime - float64(wholeDaysPart) + roundEpsilon + if date1904 { + date = excel1904Epoc + } else { + date = excel1900Epoc + } + durationPart := time.Duration(nanosInADay * floatPart) + return date.AddDate(0, 0, wholeDaysPart).Add(durationPart).Truncate(time.Second) +} + +// ExcelDateToTime converts a float-based excel date representation to a time.Time. +func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) { + if excelDate < 0 { + return time.Time{}, newInvalidExcelDateError(excelDate) + } + return timeFromExcelTime(excelDate, use1904Format), nil +} + +// isLeapYear determine if leap year for a given year. +func isLeapYear(y int) bool { + if y == y/400*400 { + return true + } + if y == y/100*100 { + return false + } + return y == y/4*4 +} + +// getDaysInMonth provides a function to get the days by a given year and +// month number. +func getDaysInMonth(y, m int) int { + if m == 2 && isLeapYear(y) { + return 29 + } + return daysInMonth[m-1] +} + +// validateDate provides a function to validate if a valid date by a given +// year, month, and day number. +func validateDate(y, m, d int) bool { + if m < 1 || m > 12 { + return false + } + if d < 1 { + return false + } + return d <= getDaysInMonth(y, m) +} + +// formatYear converts the given year number into a 4-digit format. +func formatYear(y int) int { + if y < 1900 { + if y < 30 { + y += 2000 + } else { + y += 1900 + } + } + return y +} diff --git a/vendor/github.com/xuri/excelize/v2/docProps.go b/vendor/github.com/xuri/excelize/v2/docProps.go new file mode 100644 index 00000000..41ea18e2 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/docProps.go @@ -0,0 +1,257 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "reflect" +) + +// SetAppProps provides a function to set document application properties. The +// properties that can be set are: +// +// Property | Description +// -------------------+-------------------------------------------------------------------------- +// Application | The name of the application that created this document. +// | +// ScaleCrop | Indicates the display mode of the document thumbnail. Set this element +// | to 'true' to enable scaling of the document thumbnail to the display. Set +// | this element to 'false' to enable cropping of the document thumbnail to +// | show only sections that will fit the display. +// | +// DocSecurity | Security level of a document as a numeric value. Document security is +// | defined as: +// | 1 - Document is password protected. +// | 2 - Document is recommended to be opened as read-only. +// | 3 - Document is enforced to be opened as read-only. +// | 4 - Document is locked for annotation. +// | +// Company | The name of a company associated with the document. +// | +// LinksUpToDate | Indicates whether hyperlinks in a document are up-to-date. Set this +// | element to 'true' to indicate that hyperlinks are updated. Set this +// | element to 'false' to indicate that hyperlinks are outdated. +// | +// HyperlinksChanged | Specifies that one or more hyperlinks in this part were updated +// | exclusively in this part by a producer. The next producer to open this +// | document shall update the hyperlink relationships with the new +// | hyperlinks specified in this part. +// | +// AppVersion | Specifies the version of the application which produced this document. +// | The content of this element shall be of the form XX.YYYY where X and Y +// | represent numerical values, or the document shall be considered +// | non-conformant. +// +// For example: +// +// err := f.SetAppProps(&excelize.AppProperties{ +// Application: "Microsoft Excel", +// ScaleCrop: true, +// DocSecurity: 3, +// Company: "Company Name", +// LinksUpToDate: true, +// HyperlinksChanged: true, +// AppVersion: "16.0000", +// }) +// +func (f *File) SetAppProps(appProperties *AppProperties) (err error) { + var ( + app *xlsxProperties + fields []string + output []byte + immutable, mutable reflect.Value + field string + ) + app = new(xlsxProperties) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))). + Decode(app); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + fields = []string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"} + immutable, mutable = reflect.ValueOf(*appProperties), reflect.ValueOf(app).Elem() + for _, field = range fields { + immutableField := immutable.FieldByName(field) + switch immutableField.Kind() { + case reflect.Bool: + mutable.FieldByName(field).SetBool(immutableField.Bool()) + case reflect.Int: + mutable.FieldByName(field).SetInt(immutableField.Int()) + default: + mutable.FieldByName(field).SetString(immutableField.String()) + } + } + app.Vt = NameSpaceDocumentPropertiesVariantTypes.Value + output, err = xml.Marshal(app) + f.saveFileList(defaultXMLPathDocPropsApp, output) + return +} + +// GetAppProps provides a function to get document application properties. +func (f *File) GetAppProps() (ret *AppProperties, err error) { + app := new(xlsxProperties) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))). + Decode(app); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + ret, err = &AppProperties{ + Application: app.Application, + ScaleCrop: app.ScaleCrop, + DocSecurity: app.DocSecurity, + Company: app.Company, + LinksUpToDate: app.LinksUpToDate, + HyperlinksChanged: app.HyperlinksChanged, + AppVersion: app.AppVersion, + }, nil + return +} + +// SetDocProps provides a function to set document core properties. The +// properties that can be set are: +// +// Property | Description +// ----------------+----------------------------------------------------------------------------- +// Title | The name given to the resource. +// | +// Subject | The topic of the content of the resource. +// | +// Creator | An entity primarily responsible for making the content of the resource. +// | +// Keywords | A delimited set of keywords to support searching and indexing. This is +// | typically a list of terms that are not available elsewhere in the properties. +// | +// Description | An explanation of the content of the resource. +// | +// LastModifiedBy | The user who performed the last modification. The identification is +// | environment-specific. +// | +// Language | The language of the intellectual content of the resource. +// | +// Identifier | An unambiguous reference to the resource within a given context. +// | +// Revision | The topic of the content of the resource. +// | +// ContentStatus | The status of the content. For example: Values might include "Draft", +// | "Reviewed" and "Final" +// | +// Category | A categorization of the content of this package. +// | +// Version | The version number. This value is set by the user or by the application. +// +// For example: +// +// err := f.SetDocProps(&excelize.DocProperties{ +// Category: "category", +// ContentStatus: "Draft", +// Created: "2019-06-04T22:00:10Z", +// Creator: "Go Excelize", +// Description: "This file created by Go Excelize", +// Identifier: "xlsx", +// Keywords: "Spreadsheet", +// LastModifiedBy: "Go Author", +// Modified: "2019-06-04T22:00:10Z", +// Revision: "0", +// Subject: "Test Subject", +// Title: "Test Title", +// Language: "en-US", +// Version: "1.0.0", +// }) +// +func (f *File) SetDocProps(docProperties *DocProperties) (err error) { + var ( + core *decodeCoreProperties + newProps *xlsxCoreProperties + fields []string + output []byte + immutable, mutable reflect.Value + field, val string + ) + + core = new(decodeCoreProperties) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))). + Decode(core); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + newProps, err = &xlsxCoreProperties{ + Dc: NameSpaceDublinCore, + Dcterms: NameSpaceDublinCoreTerms, + Dcmitype: NameSpaceDublinCoreMetadataInitiative, + XSI: NameSpaceXMLSchemaInstance, + Title: core.Title, + Subject: core.Subject, + Creator: core.Creator, + Keywords: core.Keywords, + Description: core.Description, + LastModifiedBy: core.LastModifiedBy, + Language: core.Language, + Identifier: core.Identifier, + Revision: core.Revision, + ContentStatus: core.ContentStatus, + Category: core.Category, + Version: core.Version, + }, nil + newProps.Created.Text, newProps.Created.Type, newProps.Modified.Text, newProps.Modified.Type = core.Created.Text, core.Created.Type, core.Modified.Text, core.Modified.Type + fields = []string{ + "Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords", + "LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version", + } + immutable, mutable = reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem() + for _, field = range fields { + if val = immutable.FieldByName(field).String(); val != "" { + mutable.FieldByName(field).SetString(val) + } + } + if docProperties.Created != "" { + newProps.Created.Text = docProperties.Created + } + if docProperties.Modified != "" { + newProps.Modified.Text = docProperties.Modified + } + output, err = xml.Marshal(newProps) + f.saveFileList(defaultXMLPathDocPropsCore, output) + + return +} + +// GetDocProps provides a function to get document core properties. +func (f *File) GetDocProps() (ret *DocProperties, err error) { + core := new(decodeCoreProperties) + + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))). + Decode(core); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + ret, err = &DocProperties{ + Category: core.Category, + ContentStatus: core.ContentStatus, + Created: core.Created.Text, + Creator: core.Creator, + Description: core.Description, + Identifier: core.Identifier, + Keywords: core.Keywords, + LastModifiedBy: core.LastModifiedBy, + Modified: core.Modified.Text, + Revision: core.Revision, + Subject: core.Subject, + Title: core.Title, + Language: core.Language, + Version: core.Version, + }, nil + + return +} diff --git a/vendor/github.com/xuri/excelize/v2/drawing.go b/vendor/github.com/xuri/excelize/v2/drawing.go new file mode 100644 index 00000000..d0e9135b --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/drawing.go @@ -0,0 +1,1335 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "log" + "reflect" + "strconv" + "strings" +) + +// prepareDrawing provides a function to prepare drawing ID and XML by given +// drawingID, worksheet name and default drawingXML. +func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) { + sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + if ws.Drawing != nil { + // The worksheet already has a picture or chart relationships, use the relationships drawing ../drawings/drawing%d.xml. + sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) + drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml")) + drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1) + } else { + // Add first picture for given sheet. + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + f.addSheetDrawing(sheet, rID) + } + return drawingID, drawingXML +} + +// prepareChartSheetDrawing provides a function to prepare drawing ID and XML +// by given drawingID, worksheet name and default drawingXML. +func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet string) { + sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + // Only allow one chart in a chartsheet. + sheetRels := "xl/chartsheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/chartsheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + f.addSheetNameSpace(sheet, SourceRelationship) + cs.Drawing = &xlsxDrawing{ + RID: "rId" + strconv.Itoa(rID), + } +} + +// addChart provides a function to create chart as xl/charts/chart%d.xml by +// given format sets. +func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) { + count := f.countCharts() + xlsxChartSpace := xlsxChartSpace{ + XMLNSa: NameSpaceDrawingML.Value, + Date1904: &attrValBool{Val: boolPtr(false)}, + Lang: &attrValString{Val: stringPtr("en-US")}, + RoundedCorners: &attrValBool{Val: boolPtr(false)}, + Chart: cChart{ + Title: &cTitle{ + Tx: cTx{ + Rich: &cRich{ + P: aP{ + PPr: &aPPr{ + DefRPr: aRPr{ + Kern: 1200, + Strike: "noStrike", + U: "none", + Sz: 1400, + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{ + Val: "tx1", + LumMod: &attrValInt{ + Val: intPtr(65000), + }, + LumOff: &attrValInt{ + Val: intPtr(35000), + }, + }, + }, + Ea: &aEa{ + Typeface: "+mn-ea", + }, + Cs: &aCs{ + Typeface: "+mn-cs", + }, + Latin: &aLatin{ + Typeface: "+mn-lt", + }, + }, + }, + R: &aR{ + RPr: aRPr{ + Lang: "en-US", + AltLang: "en-US", + }, + T: formatSet.Title.Name, + }, + }, + }, + }, + TxPr: cTxPr{ + P: aP{ + PPr: &aPPr{ + DefRPr: aRPr{ + Kern: 1200, + U: "none", + Sz: 14000, + Strike: "noStrike", + }, + }, + EndParaRPr: &aEndParaRPr{ + Lang: "en-US", + }, + }, + }, + Overlay: &attrValBool{Val: boolPtr(false)}, + }, + View3D: &cView3D{ + RotX: &attrValInt{Val: intPtr(chartView3DRotX[formatSet.Type])}, + RotY: &attrValInt{Val: intPtr(chartView3DRotY[formatSet.Type])}, + Perspective: &attrValInt{Val: intPtr(chartView3DPerspective[formatSet.Type])}, + RAngAx: &attrValInt{Val: intPtr(chartView3DRAngAx[formatSet.Type])}, + }, + Floor: &cThicknessSpPr{ + Thickness: &attrValInt{Val: intPtr(0)}, + }, + SideWall: &cThicknessSpPr{ + Thickness: &attrValInt{Val: intPtr(0)}, + }, + BackWall: &cThicknessSpPr{ + Thickness: &attrValInt{Val: intPtr(0)}, + }, + PlotArea: &cPlotArea{}, + Legend: &cLegend{ + LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[formatSet.Legend.Position])}, + Overlay: &attrValBool{Val: boolPtr(false)}, + }, + + PlotVisOnly: &attrValBool{Val: boolPtr(false)}, + DispBlanksAs: &attrValString{Val: stringPtr(formatSet.ShowBlanksAs)}, + ShowDLblsOverMax: &attrValBool{Val: boolPtr(false)}, + }, + SpPr: &cSpPr{ + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{Val: "bg1"}, + }, + Ln: &aLn{ + W: 9525, + Cap: "flat", + Cmpd: "sng", + Algn: "ctr", + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{ + Val: "tx1", + LumMod: &attrValInt{ + Val: intPtr(15000), + }, + LumOff: &attrValInt{ + Val: intPtr(85000), + }, + }, + }, + }, + }, + PrintSettings: &cPrintSettings{ + PageMargins: &cPageMargins{ + B: 0.75, + L: 0.7, + R: 0.7, + T: 0.7, + Header: 0.3, + Footer: 0.3, + }, + }, + } + plotAreaFunc := map[string]func(*formatChart) *cPlotArea{ + Area: f.drawBaseChart, + AreaStacked: f.drawBaseChart, + AreaPercentStacked: f.drawBaseChart, + Area3D: f.drawBaseChart, + Area3DStacked: f.drawBaseChart, + Area3DPercentStacked: f.drawBaseChart, + Bar: f.drawBaseChart, + BarStacked: f.drawBaseChart, + BarPercentStacked: f.drawBaseChart, + Bar3DClustered: f.drawBaseChart, + Bar3DStacked: f.drawBaseChart, + Bar3DPercentStacked: f.drawBaseChart, + Bar3DConeClustered: f.drawBaseChart, + Bar3DConeStacked: f.drawBaseChart, + Bar3DConePercentStacked: f.drawBaseChart, + Bar3DPyramidClustered: f.drawBaseChart, + Bar3DPyramidStacked: f.drawBaseChart, + Bar3DPyramidPercentStacked: f.drawBaseChart, + Bar3DCylinderClustered: f.drawBaseChart, + Bar3DCylinderStacked: f.drawBaseChart, + Bar3DCylinderPercentStacked: f.drawBaseChart, + Col: f.drawBaseChart, + ColStacked: f.drawBaseChart, + ColPercentStacked: f.drawBaseChart, + Col3D: f.drawBaseChart, + Col3DClustered: f.drawBaseChart, + Col3DStacked: f.drawBaseChart, + Col3DPercentStacked: f.drawBaseChart, + Col3DCone: f.drawBaseChart, + Col3DConeClustered: f.drawBaseChart, + Col3DConeStacked: f.drawBaseChart, + Col3DConePercentStacked: f.drawBaseChart, + Col3DPyramid: f.drawBaseChart, + Col3DPyramidClustered: f.drawBaseChart, + Col3DPyramidStacked: f.drawBaseChart, + Col3DPyramidPercentStacked: f.drawBaseChart, + Col3DCylinder: f.drawBaseChart, + Col3DCylinderClustered: f.drawBaseChart, + Col3DCylinderStacked: f.drawBaseChart, + Col3DCylinderPercentStacked: f.drawBaseChart, + Doughnut: f.drawDoughnutChart, + Line: f.drawLineChart, + Pie3D: f.drawPie3DChart, + Pie: f.drawPieChart, + PieOfPieChart: f.drawPieOfPieChart, + BarOfPieChart: f.drawBarOfPieChart, + Radar: f.drawRadarChart, + Scatter: f.drawScatterChart, + Surface3D: f.drawSurface3DChart, + WireframeSurface3D: f.drawSurface3DChart, + Contour: f.drawSurfaceChart, + WireframeContour: f.drawSurfaceChart, + Bubble: f.drawBaseChart, + Bubble3D: f.drawBaseChart, + } + if formatSet.Legend.None { + xlsxChartSpace.Chart.Legend = nil + } + addChart := func(c, p *cPlotArea) { + immutable, mutable := reflect.ValueOf(c).Elem(), reflect.ValueOf(p).Elem() + for i := 0; i < mutable.NumField(); i++ { + field := mutable.Field(i) + if field.IsNil() { + continue + } + immutable.FieldByName(mutable.Type().Field(i).Name).Set(field) + } + } + addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[formatSet.Type](formatSet)) + order := len(formatSet.Series) + for idx := range comboCharts { + comboCharts[idx].order = order + addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](comboCharts[idx])) + order += len(comboCharts[idx].Series) + } + chart, _ := xml.Marshal(xlsxChartSpace) + media := "xl/charts/chart" + strconv.Itoa(count+1) + ".xml" + f.saveFileList(media, chart) +} + +// drawBaseChart provides a function to draw the c:plotArea element for bar, +// and column series charts by given format sets. +func (f *File) drawBaseChart(formatSet *formatChart) *cPlotArea { + c := cCharts{ + BarDir: &attrValString{ + Val: stringPtr("col"), + }, + Grouping: &attrValString{ + Val: stringPtr("clustered"), + }, + VaryColors: &attrValBool{ + Val: boolPtr(formatSet.VaryColors), + }, + Ser: f.drawChartSeries(formatSet), + Shape: f.drawChartShape(formatSet), + DLbls: f.drawChartDLbls(formatSet), + AxID: []*attrValInt{ + {Val: intPtr(754001152)}, + {Val: intPtr(753999904)}, + }, + Overlap: &attrValInt{Val: intPtr(100)}, + } + var ok bool + if *c.BarDir.Val, ok = plotAreaChartBarDir[formatSet.Type]; !ok { + c.BarDir = nil + } + if *c.Grouping.Val, ok = plotAreaChartGrouping[formatSet.Type]; !ok { + c.Grouping = nil + } + if *c.Overlap.Val, ok = plotAreaChartOverlap[formatSet.Type]; !ok { + c.Overlap = nil + } + catAx := f.drawPlotAreaCatAx(formatSet) + valAx := f.drawPlotAreaValAx(formatSet) + charts := map[string]*cPlotArea{ + "area": { + AreaChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "areaStacked": { + AreaChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "areaPercentStacked": { + AreaChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "area3D": { + Area3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "area3DStacked": { + Area3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "area3DPercentStacked": { + Area3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar": { + BarChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "barStacked": { + BarChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "barPercentStacked": { + BarChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DClustered": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DPercentStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DConeClustered": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DConeStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DConePercentStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DPyramidClustered": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DPyramidStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DPyramidPercentStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DCylinderClustered": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DCylinderStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bar3DCylinderPercentStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col": { + BarChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "colStacked": { + BarChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "colPercentStacked": { + BarChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3D": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DClustered": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DPercentStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DCone": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DConeClustered": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DConeStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DConePercentStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DPyramid": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DPyramidClustered": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DPyramidStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DPyramidPercentStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DCylinder": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DCylinderClustered": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DCylinderStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "col3DCylinderPercentStacked": { + Bar3DChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bubble": { + BubbleChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + "bubble3D": { + BubbleChart: &c, + CatAx: catAx, + ValAx: valAx, + }, + } + return charts[formatSet.Type] +} + +// drawDoughnutChart provides a function to draw the c:plotArea element for +// doughnut chart by given format sets. +func (f *File) drawDoughnutChart(formatSet *formatChart) *cPlotArea { + holeSize := 75 + if formatSet.HoleSize > 0 && formatSet.HoleSize <= 90 { + holeSize = formatSet.HoleSize + } + + return &cPlotArea{ + DoughnutChart: &cCharts{ + VaryColors: &attrValBool{ + Val: boolPtr(formatSet.VaryColors), + }, + Ser: f.drawChartSeries(formatSet), + HoleSize: &attrValInt{Val: intPtr(holeSize)}, + }, + } +} + +// drawLineChart provides a function to draw the c:plotArea element for line +// chart by given format sets. +func (f *File) drawLineChart(formatSet *formatChart) *cPlotArea { + return &cPlotArea{ + LineChart: &cCharts{ + Grouping: &attrValString{ + Val: stringPtr(plotAreaChartGrouping[formatSet.Type]), + }, + VaryColors: &attrValBool{ + Val: boolPtr(false), + }, + Ser: f.drawChartSeries(formatSet), + DLbls: f.drawChartDLbls(formatSet), + Smooth: &attrValBool{ + Val: boolPtr(false), + }, + AxID: []*attrValInt{ + {Val: intPtr(754001152)}, + {Val: intPtr(753999904)}, + }, + }, + CatAx: f.drawPlotAreaCatAx(formatSet), + ValAx: f.drawPlotAreaValAx(formatSet), + } +} + +// drawPieChart provides a function to draw the c:plotArea element for pie +// chart by given format sets. +func (f *File) drawPieChart(formatSet *formatChart) *cPlotArea { + return &cPlotArea{ + PieChart: &cCharts{ + VaryColors: &attrValBool{ + Val: boolPtr(formatSet.VaryColors), + }, + Ser: f.drawChartSeries(formatSet), + }, + } +} + +// drawPie3DChart provides a function to draw the c:plotArea element for 3D +// pie chart by given format sets. +func (f *File) drawPie3DChart(formatSet *formatChart) *cPlotArea { + return &cPlotArea{ + Pie3DChart: &cCharts{ + VaryColors: &attrValBool{ + Val: boolPtr(formatSet.VaryColors), + }, + Ser: f.drawChartSeries(formatSet), + }, + } +} + +// drawPieOfPieChart provides a function to draw the c:plotArea element for +// pie chart by given format sets. +func (f *File) drawPieOfPieChart(formatSet *formatChart) *cPlotArea { + return &cPlotArea{ + OfPieChart: &cCharts{ + OfPieType: &attrValString{ + Val: stringPtr("pie"), + }, + VaryColors: &attrValBool{ + Val: boolPtr(formatSet.VaryColors), + }, + Ser: f.drawChartSeries(formatSet), + SerLines: &attrValString{}, + }, + } +} + +// drawBarOfPieChart provides a function to draw the c:plotArea element for +// pie chart by given format sets. +func (f *File) drawBarOfPieChart(formatSet *formatChart) *cPlotArea { + return &cPlotArea{ + OfPieChart: &cCharts{ + OfPieType: &attrValString{ + Val: stringPtr("bar"), + }, + VaryColors: &attrValBool{ + Val: boolPtr(formatSet.VaryColors), + }, + Ser: f.drawChartSeries(formatSet), + SerLines: &attrValString{}, + }, + } +} + +// drawRadarChart provides a function to draw the c:plotArea element for radar +// chart by given format sets. +func (f *File) drawRadarChart(formatSet *formatChart) *cPlotArea { + return &cPlotArea{ + RadarChart: &cCharts{ + RadarStyle: &attrValString{ + Val: stringPtr("marker"), + }, + VaryColors: &attrValBool{ + Val: boolPtr(false), + }, + Ser: f.drawChartSeries(formatSet), + DLbls: f.drawChartDLbls(formatSet), + AxID: []*attrValInt{ + {Val: intPtr(754001152)}, + {Val: intPtr(753999904)}, + }, + }, + CatAx: f.drawPlotAreaCatAx(formatSet), + ValAx: f.drawPlotAreaValAx(formatSet), + } +} + +// drawScatterChart provides a function to draw the c:plotArea element for +// scatter chart by given format sets. +func (f *File) drawScatterChart(formatSet *formatChart) *cPlotArea { + return &cPlotArea{ + ScatterChart: &cCharts{ + ScatterStyle: &attrValString{ + Val: stringPtr("smoothMarker"), // line,lineMarker,marker,none,smooth,smoothMarker + }, + VaryColors: &attrValBool{ + Val: boolPtr(false), + }, + Ser: f.drawChartSeries(formatSet), + DLbls: f.drawChartDLbls(formatSet), + AxID: []*attrValInt{ + {Val: intPtr(754001152)}, + {Val: intPtr(753999904)}, + }, + }, + CatAx: f.drawPlotAreaCatAx(formatSet), + ValAx: f.drawPlotAreaValAx(formatSet), + } +} + +// drawSurface3DChart provides a function to draw the c:surface3DChart element by +// given format sets. +func (f *File) drawSurface3DChart(formatSet *formatChart) *cPlotArea { + plotArea := &cPlotArea{ + Surface3DChart: &cCharts{ + Ser: f.drawChartSeries(formatSet), + AxID: []*attrValInt{ + {Val: intPtr(754001152)}, + {Val: intPtr(753999904)}, + {Val: intPtr(832256642)}, + }, + }, + CatAx: f.drawPlotAreaCatAx(formatSet), + ValAx: f.drawPlotAreaValAx(formatSet), + SerAx: f.drawPlotAreaSerAx(formatSet), + } + if formatSet.Type == WireframeSurface3D { + plotArea.Surface3DChart.Wireframe = &attrValBool{Val: boolPtr(true)} + } + return plotArea +} + +// drawSurfaceChart provides a function to draw the c:surfaceChart element by +// given format sets. +func (f *File) drawSurfaceChart(formatSet *formatChart) *cPlotArea { + plotArea := &cPlotArea{ + SurfaceChart: &cCharts{ + Ser: f.drawChartSeries(formatSet), + AxID: []*attrValInt{ + {Val: intPtr(754001152)}, + {Val: intPtr(753999904)}, + {Val: intPtr(832256642)}, + }, + }, + CatAx: f.drawPlotAreaCatAx(formatSet), + ValAx: f.drawPlotAreaValAx(formatSet), + SerAx: f.drawPlotAreaSerAx(formatSet), + } + if formatSet.Type == WireframeContour { + plotArea.SurfaceChart.Wireframe = &attrValBool{Val: boolPtr(true)} + } + return plotArea +} + +// drawChartShape provides a function to draw the c:shape element by given +// format sets. +func (f *File) drawChartShape(formatSet *formatChart) *attrValString { + shapes := map[string]string{ + Bar3DConeClustered: "cone", + Bar3DConeStacked: "cone", + Bar3DConePercentStacked: "cone", + Bar3DPyramidClustered: "pyramid", + Bar3DPyramidStacked: "pyramid", + Bar3DPyramidPercentStacked: "pyramid", + Bar3DCylinderClustered: "cylinder", + Bar3DCylinderStacked: "cylinder", + Bar3DCylinderPercentStacked: "cylinder", + Col3DCone: "cone", + Col3DConeClustered: "cone", + Col3DConeStacked: "cone", + Col3DConePercentStacked: "cone", + Col3DPyramid: "pyramid", + Col3DPyramidClustered: "pyramid", + Col3DPyramidStacked: "pyramid", + Col3DPyramidPercentStacked: "pyramid", + Col3DCylinder: "cylinder", + Col3DCylinderClustered: "cylinder", + Col3DCylinderStacked: "cylinder", + Col3DCylinderPercentStacked: "cylinder", + } + if shape, ok := shapes[formatSet.Type]; ok { + return &attrValString{Val: stringPtr(shape)} + } + return nil +} + +// drawChartSeries provides a function to draw the c:ser element by given +// format sets. +func (f *File) drawChartSeries(formatSet *formatChart) *[]cSer { + var ser []cSer + for k := range formatSet.Series { + ser = append(ser, cSer{ + IDx: &attrValInt{Val: intPtr(k + formatSet.order)}, + Order: &attrValInt{Val: intPtr(k + formatSet.order)}, + Tx: &cTx{ + StrRef: &cStrRef{ + F: formatSet.Series[k].Name, + }, + }, + SpPr: f.drawChartSeriesSpPr(k, formatSet), + Marker: f.drawChartSeriesMarker(k, formatSet), + DPt: f.drawChartSeriesDPt(k, formatSet), + DLbls: f.drawChartSeriesDLbls(formatSet), + InvertIfNegative: &attrValBool{Val: boolPtr(false)}, + Cat: f.drawChartSeriesCat(formatSet.Series[k], formatSet), + Val: f.drawChartSeriesVal(formatSet.Series[k], formatSet), + XVal: f.drawChartSeriesXVal(formatSet.Series[k], formatSet), + YVal: f.drawChartSeriesYVal(formatSet.Series[k], formatSet), + BubbleSize: f.drawCharSeriesBubbleSize(formatSet.Series[k], formatSet), + Bubble3D: f.drawCharSeriesBubble3D(formatSet), + }) + } + return &ser +} + +// drawChartSeriesSpPr provides a function to draw the c:spPr element by given +// format sets. +func (f *File) drawChartSeriesSpPr(i int, formatSet *formatChart) *cSpPr { + spPrScatter := &cSpPr{ + Ln: &aLn{ + W: 25400, + NoFill: " ", + }, + } + spPrLine := &cSpPr{ + Ln: &aLn{ + W: f.ptToEMUs(formatSet.Series[i].Line.Width), + Cap: "rnd", // rnd, sq, flat + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa((formatSet.order+i)%6+1)}, + }, + }, + } + chartSeriesSpPr := map[string]*cSpPr{Line: spPrLine, Scatter: spPrScatter} + return chartSeriesSpPr[formatSet.Type] +} + +// drawChartSeriesDPt provides a function to draw the c:dPt element by given +// data index and format sets. +func (f *File) drawChartSeriesDPt(i int, formatSet *formatChart) []*cDPt { + dpt := []*cDPt{{ + IDx: &attrValInt{Val: intPtr(i)}, + Bubble3D: &attrValBool{Val: boolPtr(false)}, + SpPr: &cSpPr{ + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+1)}, + }, + Ln: &aLn{ + W: 25400, + Cap: "rnd", + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{Val: "lt" + strconv.Itoa(i+1)}, + }, + }, + Sp3D: &aSp3D{ + ContourW: 25400, + ContourClr: &aContourClr{ + SchemeClr: &aSchemeClr{Val: "lt" + strconv.Itoa(i+1)}, + }, + }, + }, + }} + chartSeriesDPt := map[string][]*cDPt{Pie: dpt, Pie3D: dpt} + return chartSeriesDPt[formatSet.Type] +} + +// drawChartSeriesCat provides a function to draw the c:cat element by given +// chart series and format sets. +func (f *File) drawChartSeriesCat(v formatChartSeries, formatSet *formatChart) *cCat { + cat := &cCat{ + StrRef: &cStrRef{ + F: v.Categories, + }, + } + chartSeriesCat := map[string]*cCat{Scatter: nil, Bubble: nil, Bubble3D: nil} + if _, ok := chartSeriesCat[formatSet.Type]; ok || v.Categories == "" { + return nil + } + return cat +} + +// drawChartSeriesVal provides a function to draw the c:val element by given +// chart series and format sets. +func (f *File) drawChartSeriesVal(v formatChartSeries, formatSet *formatChart) *cVal { + val := &cVal{ + NumRef: &cNumRef{ + F: v.Values, + }, + } + chartSeriesVal := map[string]*cVal{Scatter: nil, Bubble: nil, Bubble3D: nil} + if _, ok := chartSeriesVal[formatSet.Type]; ok { + return nil + } + return val +} + +// drawChartSeriesMarker provides a function to draw the c:marker element by +// given data index and format sets. +func (f *File) drawChartSeriesMarker(i int, formatSet *formatChart) *cMarker { + defaultSymbol := map[string]*attrValString{Scatter: {Val: stringPtr("circle")}} + marker := &cMarker{ + Symbol: defaultSymbol[formatSet.Type], + Size: &attrValInt{Val: intPtr(5)}, + } + if symbol := stringPtr(formatSet.Series[i].Marker.Symbol); *symbol != "" { + marker.Symbol = &attrValString{Val: symbol} + } + if size := intPtr(formatSet.Series[i].Marker.Size); *size != 0 { + marker.Size = &attrValInt{Val: size} + } + if i < 6 { + marker.SpPr = &cSpPr{ + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{ + Val: "accent" + strconv.Itoa(i+1), + }, + }, + Ln: &aLn{ + W: 9252, + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{ + Val: "accent" + strconv.Itoa(i+1), + }, + }, + }, + } + } + chartSeriesMarker := map[string]*cMarker{Scatter: marker, Line: marker} + return chartSeriesMarker[formatSet.Type] +} + +// drawChartSeriesXVal provides a function to draw the c:xVal element by given +// chart series and format sets. +func (f *File) drawChartSeriesXVal(v formatChartSeries, formatSet *formatChart) *cCat { + cat := &cCat{ + StrRef: &cStrRef{ + F: v.Categories, + }, + } + chartSeriesXVal := map[string]*cCat{Scatter: cat} + return chartSeriesXVal[formatSet.Type] +} + +// drawChartSeriesYVal provides a function to draw the c:yVal element by given +// chart series and format sets. +func (f *File) drawChartSeriesYVal(v formatChartSeries, formatSet *formatChart) *cVal { + val := &cVal{ + NumRef: &cNumRef{ + F: v.Values, + }, + } + chartSeriesYVal := map[string]*cVal{Scatter: val, Bubble: val, Bubble3D: val} + return chartSeriesYVal[formatSet.Type] +} + +// drawCharSeriesBubbleSize provides a function to draw the c:bubbleSize +// element by given chart series and format sets. +func (f *File) drawCharSeriesBubbleSize(v formatChartSeries, formatSet *formatChart) *cVal { + if _, ok := map[string]bool{Bubble: true, Bubble3D: true}[formatSet.Type]; !ok { + return nil + } + return &cVal{ + NumRef: &cNumRef{ + F: v.Values, + }, + } +} + +// drawCharSeriesBubble3D provides a function to draw the c:bubble3D element +// by given format sets. +func (f *File) drawCharSeriesBubble3D(formatSet *formatChart) *attrValBool { + if _, ok := map[string]bool{Bubble3D: true}[formatSet.Type]; !ok { + return nil + } + return &attrValBool{Val: boolPtr(true)} +} + +// drawChartDLbls provides a function to draw the c:dLbls element by given +// format sets. +func (f *File) drawChartDLbls(formatSet *formatChart) *cDLbls { + return &cDLbls{ + ShowLegendKey: &attrValBool{Val: boolPtr(formatSet.Legend.ShowLegendKey)}, + ShowVal: &attrValBool{Val: boolPtr(formatSet.Plotarea.ShowVal)}, + ShowCatName: &attrValBool{Val: boolPtr(formatSet.Plotarea.ShowCatName)}, + ShowSerName: &attrValBool{Val: boolPtr(formatSet.Plotarea.ShowSerName)}, + ShowBubbleSize: &attrValBool{Val: boolPtr(formatSet.Plotarea.ShowBubbleSize)}, + ShowPercent: &attrValBool{Val: boolPtr(formatSet.Plotarea.ShowPercent)}, + ShowLeaderLines: &attrValBool{Val: boolPtr(formatSet.Plotarea.ShowLeaderLines)}, + } +} + +// drawChartSeriesDLbls provides a function to draw the c:dLbls element by +// given format sets. +func (f *File) drawChartSeriesDLbls(formatSet *formatChart) *cDLbls { + dLbls := f.drawChartDLbls(formatSet) + chartSeriesDLbls := map[string]*cDLbls{ + Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil, + } + if _, ok := chartSeriesDLbls[formatSet.Type]; ok { + return nil + } + return dLbls +} + +// drawPlotAreaCatAx provides a function to draw the c:catAx element. +func (f *File) drawPlotAreaCatAx(formatSet *formatChart) []*cAxs { + max := &attrValFloat{Val: formatSet.XAxis.Maximum} + min := &attrValFloat{Val: formatSet.XAxis.Minimum} + if formatSet.XAxis.Maximum == nil { + max = nil + } + if formatSet.XAxis.Minimum == nil { + min = nil + } + axs := []*cAxs{ + { + AxID: &attrValInt{Val: intPtr(754001152)}, + Scaling: &cScaling{ + Orientation: &attrValString{Val: stringPtr(orientation[formatSet.XAxis.ReverseOrder])}, + Max: max, + Min: min, + }, + Delete: &attrValBool{Val: boolPtr(formatSet.XAxis.None)}, + AxPos: &attrValString{Val: stringPtr(catAxPos[formatSet.XAxis.ReverseOrder])}, + NumFmt: &cNumFmt{ + FormatCode: "General", + SourceLinked: true, + }, + MajorTickMark: &attrValString{Val: stringPtr("none")}, + MinorTickMark: &attrValString{Val: stringPtr("none")}, + TickLblPos: &attrValString{Val: stringPtr("nextTo")}, + SpPr: f.drawPlotAreaSpPr(), + TxPr: f.drawPlotAreaTxPr(), + CrossAx: &attrValInt{Val: intPtr(753999904)}, + Crosses: &attrValString{Val: stringPtr("autoZero")}, + Auto: &attrValBool{Val: boolPtr(true)}, + LblAlgn: &attrValString{Val: stringPtr("ctr")}, + LblOffset: &attrValInt{Val: intPtr(100)}, + NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)}, + }, + } + if formatSet.XAxis.MajorGridlines { + axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} + } + if formatSet.XAxis.MinorGridlines { + axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} + } + if formatSet.XAxis.TickLabelSkip != 0 { + axs[0].TickLblSkip = &attrValInt{Val: intPtr(formatSet.XAxis.TickLabelSkip)} + } + return axs +} + +// drawPlotAreaValAx provides a function to draw the c:valAx element. +func (f *File) drawPlotAreaValAx(formatSet *formatChart) []*cAxs { + max := &attrValFloat{Val: formatSet.YAxis.Maximum} + min := &attrValFloat{Val: formatSet.YAxis.Minimum} + if formatSet.YAxis.Maximum == nil { + max = nil + } + if formatSet.YAxis.Minimum == nil { + min = nil + } + var logBase *attrValFloat + if formatSet.YAxis.LogBase >= 2 && formatSet.YAxis.LogBase <= 1000 { + logBase = &attrValFloat{Val: float64Ptr(formatSet.YAxis.LogBase)} + } + axs := []*cAxs{ + { + AxID: &attrValInt{Val: intPtr(753999904)}, + Scaling: &cScaling{ + LogBase: logBase, + Orientation: &attrValString{Val: stringPtr(orientation[formatSet.YAxis.ReverseOrder])}, + Max: max, + Min: min, + }, + Delete: &attrValBool{Val: boolPtr(formatSet.YAxis.None)}, + AxPos: &attrValString{Val: stringPtr(valAxPos[formatSet.YAxis.ReverseOrder])}, + NumFmt: &cNumFmt{ + FormatCode: chartValAxNumFmtFormatCode[formatSet.Type], + SourceLinked: true, + }, + MajorTickMark: &attrValString{Val: stringPtr("none")}, + MinorTickMark: &attrValString{Val: stringPtr("none")}, + TickLblPos: &attrValString{Val: stringPtr("nextTo")}, + SpPr: f.drawPlotAreaSpPr(), + TxPr: f.drawPlotAreaTxPr(), + CrossAx: &attrValInt{Val: intPtr(754001152)}, + Crosses: &attrValString{Val: stringPtr("autoZero")}, + CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[formatSet.Type])}, + }, + } + if formatSet.YAxis.MajorGridlines { + axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} + } + if formatSet.YAxis.MinorGridlines { + axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} + } + if pos, ok := valTickLblPos[formatSet.Type]; ok { + axs[0].TickLblPos.Val = stringPtr(pos) + } + if formatSet.YAxis.MajorUnit != 0 { + axs[0].MajorUnit = &attrValFloat{Val: float64Ptr(formatSet.YAxis.MajorUnit)} + } + return axs +} + +// drawPlotAreaSerAx provides a function to draw the c:serAx element. +func (f *File) drawPlotAreaSerAx(formatSet *formatChart) []*cAxs { + max := &attrValFloat{Val: formatSet.YAxis.Maximum} + min := &attrValFloat{Val: formatSet.YAxis.Minimum} + if formatSet.YAxis.Maximum == nil { + max = nil + } + if formatSet.YAxis.Minimum == nil { + min = nil + } + return []*cAxs{ + { + AxID: &attrValInt{Val: intPtr(832256642)}, + Scaling: &cScaling{ + Orientation: &attrValString{Val: stringPtr(orientation[formatSet.YAxis.ReverseOrder])}, + Max: max, + Min: min, + }, + Delete: &attrValBool{Val: boolPtr(formatSet.YAxis.None)}, + AxPos: &attrValString{Val: stringPtr(catAxPos[formatSet.XAxis.ReverseOrder])}, + TickLblPos: &attrValString{Val: stringPtr("nextTo")}, + SpPr: f.drawPlotAreaSpPr(), + TxPr: f.drawPlotAreaTxPr(), + CrossAx: &attrValInt{Val: intPtr(753999904)}, + }, + } +} + +// drawPlotAreaSpPr provides a function to draw the c:spPr element. +func (f *File) drawPlotAreaSpPr() *cSpPr { + return &cSpPr{ + Ln: &aLn{ + W: 9525, + Cap: "flat", + Cmpd: "sng", + Algn: "ctr", + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{ + Val: "tx1", + LumMod: &attrValInt{Val: intPtr(15000)}, + LumOff: &attrValInt{Val: intPtr(85000)}, + }, + }, + }, + } +} + +// drawPlotAreaTxPr provides a function to draw the c:txPr element. +func (f *File) drawPlotAreaTxPr() *cTxPr { + return &cTxPr{ + BodyPr: aBodyPr{ + Rot: -60000000, + SpcFirstLastPara: true, + VertOverflow: "ellipsis", + Vert: "horz", + Wrap: "square", + Anchor: "ctr", + AnchorCtr: true, + }, + P: aP{ + PPr: &aPPr{ + DefRPr: aRPr{ + Sz: 900, + B: false, + I: false, + U: "none", + Strike: "noStrike", + Kern: 1200, + Baseline: 0, + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{ + Val: "tx1", + LumMod: &attrValInt{Val: intPtr(15000)}, + LumOff: &attrValInt{Val: intPtr(85000)}, + }, + }, + Latin: &aLatin{Typeface: "+mn-lt"}, + Ea: &aEa{Typeface: "+mn-ea"}, + Cs: &aCs{Typeface: "+mn-cs"}, + }, + }, + EndParaRPr: &aEndParaRPr{Lang: "en-US"}, + }, + } +} + +// drawingParser provides a function to parse drawingXML. In order to solve +// the problem that the label structure is changed after serialization and +// deserialization, two different structures: decodeWsDr and encodeWsDr are +// defined. +func (f *File) drawingParser(path string) (*xlsxWsDr, int) { + var ( + err error + ok bool + ) + _, ok = f.Drawings.Load(path) + if !ok { + content := xlsxWsDr{} + content.A = NameSpaceDrawingML.Value + content.Xdr = NameSpaceDrawingMLSpreadSheet.Value + if _, ok = f.Pkg.Load(path); ok { // Append Model + decodeWsDr := decodeWsDr{} + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). + Decode(&decodeWsDr); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + content.R = decodeWsDr.R + for _, v := range decodeWsDr.AlternateContent { + content.AlternateContent = append(content.AlternateContent, &xlsxAlternateContent{ + Content: v.Content, + XMLNSMC: SourceRelationshipCompatibility.Value, + }) + } + for _, v := range decodeWsDr.OneCellAnchor { + content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ + EditAs: v.EditAs, + GraphicFrame: v.Content, + }) + } + for _, v := range decodeWsDr.TwoCellAnchor { + content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{ + EditAs: v.EditAs, + GraphicFrame: v.Content, + }) + } + } + f.Drawings.Store(path, &content) + } + var wsDr *xlsxWsDr + if drawing, ok := f.Drawings.Load(path); ok && drawing != nil { + wsDr = drawing.(*xlsxWsDr) + } + wsDr.Lock() + defer wsDr.Unlock() + return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2 +} + +// addDrawingChart provides a function to add chart graphic frame by given +// sheet, drawingXML, cell, width, height, relationship index and format sets. +func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, formatSet *formatPicture) error { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return err + } + colIdx := col - 1 + rowIdx := row - 1 + + width = int(float64(width) * formatSet.XScale) + height = int(float64(height) * formatSet.YScale) + colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height) + content, cNvPrID := f.drawingParser(drawingXML) + twoCellAnchor := xdrCellAnchor{} + twoCellAnchor.EditAs = formatSet.Positioning + from := xlsxFrom{} + from.Col = colStart + from.ColOff = formatSet.OffsetX * EMU + from.Row = rowStart + from.RowOff = formatSet.OffsetY * EMU + to := xlsxTo{} + to.Col = colEnd + to.ColOff = x2 * EMU + to.Row = rowEnd + to.RowOff = y2 * EMU + twoCellAnchor.From = &from + twoCellAnchor.To = &to + + graphicFrame := xlsxGraphicFrame{ + NvGraphicFramePr: xlsxNvGraphicFramePr{ + CNvPr: &xlsxCNvPr{ + ID: cNvPrID, + Name: "Chart " + strconv.Itoa(cNvPrID), + }, + }, + Graphic: &xlsxGraphic{ + GraphicData: &xlsxGraphicData{ + URI: NameSpaceDrawingMLChart.Value, + Chart: &xlsxChart{ + C: NameSpaceDrawingMLChart.Value, + R: SourceRelationship.Value, + RID: "rId" + strconv.Itoa(rID), + }, + }, + }, + } + graphic, _ := xml.Marshal(graphicFrame) + twoCellAnchor.GraphicFrame = string(graphic) + twoCellAnchor.ClientData = &xdrClientData{ + FLocksWithSheet: formatSet.FLocksWithSheet, + FPrintsWithSheet: formatSet.FPrintsWithSheet, + } + content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) + f.Drawings.Store(drawingXML, content) + return err +} + +// addSheetDrawingChart provides a function to add chart graphic frame for +// chartsheet by given sheet, drawingXML, width, height, relationship index +// and format sets. +func (f *File) addSheetDrawingChart(drawingXML string, rID int, formatSet *formatPicture) { + content, cNvPrID := f.drawingParser(drawingXML) + absoluteAnchor := xdrCellAnchor{ + EditAs: formatSet.Positioning, + Pos: &xlsxPoint2D{}, + Ext: &xlsxExt{}, + } + + graphicFrame := xlsxGraphicFrame{ + NvGraphicFramePr: xlsxNvGraphicFramePr{ + CNvPr: &xlsxCNvPr{ + ID: cNvPrID, + Name: "Chart " + strconv.Itoa(cNvPrID), + }, + }, + Graphic: &xlsxGraphic{ + GraphicData: &xlsxGraphicData{ + URI: NameSpaceDrawingMLChart.Value, + Chart: &xlsxChart{ + C: NameSpaceDrawingMLChart.Value, + R: SourceRelationship.Value, + RID: "rId" + strconv.Itoa(rID), + }, + }, + }, + } + graphic, _ := xml.Marshal(graphicFrame) + absoluteAnchor.GraphicFrame = string(graphic) + absoluteAnchor.ClientData = &xdrClientData{ + FLocksWithSheet: formatSet.FLocksWithSheet, + FPrintsWithSheet: formatSet.FPrintsWithSheet, + } + content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor) + f.Drawings.Store(drawingXML, content) +} + +// deleteDrawing provides a function to delete chart graphic frame by given by +// given coordinates and graphic type. +func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) (err error) { + var ( + wsDr *xlsxWsDr + deTwoCellAnchor *decodeTwoCellAnchor + ) + xdrCellAnchorFuncs := map[string]func(anchor *xdrCellAnchor) bool{ + "Chart": func(anchor *xdrCellAnchor) bool { return anchor.Pic == nil }, + "Pic": func(anchor *xdrCellAnchor) bool { return anchor.Pic != nil }, + } + decodeTwoCellAnchorFuncs := map[string]func(anchor *decodeTwoCellAnchor) bool{ + "Chart": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic == nil }, + "Pic": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic != nil }, + } + wsDr, _ = f.drawingParser(drawingXML) + for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ { + if err = nil; wsDr.TwoCellAnchor[idx].From != nil && xdrCellAnchorFuncs[drawingType](wsDr.TwoCellAnchor[idx]) { + if wsDr.TwoCellAnchor[idx].From.Col == col && wsDr.TwoCellAnchor[idx].From.Row == row { + wsDr.TwoCellAnchor = append(wsDr.TwoCellAnchor[:idx], wsDr.TwoCellAnchor[idx+1:]...) + idx-- + } + } + } + for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ { + deTwoCellAnchor = new(decodeTwoCellAnchor) + if err = f.xmlNewDecoder(strings.NewReader("" + wsDr.TwoCellAnchor[idx].GraphicFrame + "")). + Decode(deTwoCellAnchor); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + if err = nil; deTwoCellAnchor.From != nil && decodeTwoCellAnchorFuncs[drawingType](deTwoCellAnchor) { + if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row { + wsDr.TwoCellAnchor = append(wsDr.TwoCellAnchor[:idx], wsDr.TwoCellAnchor[idx+1:]...) + idx-- + } + } + } + f.Drawings.Store(drawingXML, wsDr) + return err +} diff --git a/vendor/github.com/xuri/excelize/v2/errors.go b/vendor/github.com/xuri/excelize/v2/errors.go new file mode 100644 index 00000000..c9d18cb3 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/errors.go @@ -0,0 +1,194 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "errors" + "fmt" +) + +// newInvalidColumnNameError defined the error message on receiving the +// invalid column name. +func newInvalidColumnNameError(col string) error { + return fmt.Errorf("invalid column name %q", col) +} + +// newInvalidRowNumberError defined the error message on receiving the invalid +// row number. +func newInvalidRowNumberError(row int) error { + return fmt.Errorf("invalid row number %d", row) +} + +// newInvalidCellNameError defined the error message on receiving the invalid +// cell name. +func newInvalidCellNameError(cell string) error { + return fmt.Errorf("invalid cell name %q", cell) +} + +// newInvalidExcelDateError defined the error message on receiving the data +// with negative values. +func newInvalidExcelDateError(dateValue float64) error { + return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue) +} + +// newUnsupportedChartType defined the error message on receiving the chart +// type are unsupported. +func newUnsupportedChartType(chartType string) error { + return fmt.Errorf("unsupported chart type %s", chartType) +} + +// newUnzipSizeLimitError defined the error message on unzip size exceeds the +// limit. +func newUnzipSizeLimitError(unzipSizeLimit int64) error { + return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit) +} + +// newInvalidStyleID defined the error message on receiving the invalid style +// ID. +func newInvalidStyleID(styleID int) error { + return fmt.Errorf("invalid style ID %d, negative values are not supported", styleID) +} + +// newFieldLengthError defined the error message on receiving the field length +// overflow. +func newFieldLengthError(name string) error { + return fmt.Errorf("field %s must be less or equal than 255 characters", name) +} + +// newCellNameToCoordinatesError defined the error message on converts +// alphanumeric cell name to coordinates. +func newCellNameToCoordinatesError(cell string, err error) error { + return fmt.Errorf("cannot convert cell %q to coordinates: %v", cell, err) +} + +var ( + // ErrStreamSetColWidth defined the error message on set column width in + // stream writing mode. + ErrStreamSetColWidth = errors.New("must call the SetColWidth function before the SetRow function") + // ErrColumnNumber defined the error message on receive an invalid column + // number. + ErrColumnNumber = errors.New("column number exceeds maximum limit") + // ErrColumnWidth defined the error message on receive an invalid column + // width. + ErrColumnWidth = fmt.Errorf("the width of the column must be smaller than or equal to %d characters", MaxColumnWidth) + // ErrOutlineLevel defined the error message on receive an invalid outline + // level number. + ErrOutlineLevel = errors.New("invalid outline level") + // ErrCoordinates defined the error message on invalid coordinates tuples + // length. + ErrCoordinates = errors.New("coordinates length must be 4") + // ErrExistsWorksheet defined the error message on given worksheet already + // exists. + ErrExistsWorksheet = errors.New("the same name worksheet already exists") + // ErrTotalSheetHyperlinks defined the error message on hyperlinks count + // overflow. + ErrTotalSheetHyperlinks = errors.New("over maximum limit hyperlinks in a worksheet") + // ErrInvalidFormula defined the error message on receive an invalid + // formula. + ErrInvalidFormula = errors.New("formula not valid") + // ErrAddVBAProject defined the error message on add the VBA project in + // the workbook. + ErrAddVBAProject = errors.New("unsupported VBA project extension") + // ErrMaxRows defined the error message on receive a row number exceeds maximum limit. + ErrMaxRows = errors.New("row number exceeds maximum limit") + // ErrMaxRowHeight defined the error message on receive an invalid row + // height. + ErrMaxRowHeight = errors.New("the height of the row must be smaller than or equal to 409 points") + // ErrImgExt defined the error message on receive an unsupported image + // extension. + ErrImgExt = errors.New("unsupported image extension") + // ErrWorkbookExt defined the error message on receive an unsupported + // workbook extension. + ErrWorkbookExt = errors.New("unsupported workbook extension") + // ErrMaxFileNameLength defined the error message on receive the file name + // length overflow. + ErrMaxFileNameLength = errors.New("file name length exceeds maximum limit") + // ErrEncrypt defined the error message on encryption spreadsheet. + ErrEncrypt = errors.New("not support encryption currently") + // ErrUnknownEncryptMechanism defined the error message on unsupported + // encryption mechanism. + ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism") + // ErrUnsupportedEncryptMechanism defined the error message on unsupported + // encryption mechanism. + ErrUnsupportedEncryptMechanism = errors.New("unsupported encryption mechanism") + // ErrUnsupportedHashAlgorithm defined the error message on unsupported + // hash algorithm. + ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm") + // ErrUnsupportedNumberFormat defined the error message on unsupported number format + // expression. + ErrUnsupportedNumberFormat = errors.New("unsupported number format token") + // ErrPasswordLengthInvalid defined the error message on invalid password + // length. + ErrPasswordLengthInvalid = errors.New("password length invalid") + // ErrParameterRequired defined the error message on receive the empty + // parameter. + ErrParameterRequired = errors.New("parameter is required") + // ErrParameterInvalid defined the error message on receive the invalid + // parameter. + ErrParameterInvalid = errors.New("parameter is invalid") + // ErrDefinedNameScope defined the error message on not found defined name + // in the given scope. + ErrDefinedNameScope = errors.New("no defined name on the scope") + // ErrDefinedNameDuplicate defined the error message on the same name + // already exists on the scope. + ErrDefinedNameDuplicate = errors.New("the same name already exists on the scope") + // ErrCustomNumFmt defined the error message on receive the empty custom number format. + ErrCustomNumFmt = errors.New("custom number format can not be empty") + // ErrFontLength defined the error message on the length of the font + // family name overflow. + ErrFontLength = errors.New("the length of the font family name must be smaller than or equal to 31") + // ErrFontSize defined the error message on the size of the font is invalid. + ErrFontSize = errors.New("font size must be between 1 and 409 points") + // ErrSheetIdx defined the error message on receive the invalid worksheet + // index. + ErrSheetIdx = errors.New("invalid worksheet index") + // ErrUnprotectSheet defined the error message on worksheet has set no + // protection. + ErrUnprotectSheet = errors.New("worksheet has set no protect") + // ErrUnprotectSheetPassword defined the error message on remove sheet + // protection with password verification failed. + ErrUnprotectSheetPassword = errors.New("worksheet protect password not match") + // ErrGroupSheets defined the error message on group sheets. + ErrGroupSheets = errors.New("group worksheet must contain an active worksheet") + // ErrDataValidationFormulaLength defined the error message for receiving a + // data validation formula length that exceeds the limit. + ErrDataValidationFormulaLength = errors.New("data validation must be 0-255 characters") + // ErrDataValidationRange defined the error message on set decimal range + // exceeds limit. + ErrDataValidationRange = errors.New("data validation range exceeds limit") + // ErrCellCharsLength defined the error message for receiving a cell + // characters length that exceeds the limit. + ErrCellCharsLength = fmt.Errorf("cell value must be 0-%d characters", TotalCellChars) + // ErrOptionsUnzipSizeLimit defined the error message for receiving + // invalid UnzipSizeLimit and UnzipXMLSizeLimit. + ErrOptionsUnzipSizeLimit = errors.New("the value of UnzipSizeLimit should be greater than or equal to UnzipXMLSizeLimit") + // ErrSave defined the error message for saving file. + ErrSave = errors.New("no path defined for file, consider File.WriteTo or File.Write") + // ErrAttrValBool defined the error message on marshal and unmarshal + // boolean type XML attribute. + ErrAttrValBool = errors.New("unexpected child of attrValBool") + // ErrSparklineType defined the error message on receive the invalid + // sparkline Type parameters. + ErrSparklineType = errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'") + // ErrSparklineLocation defined the error message on missing Location + // parameters + ErrSparklineLocation = errors.New("parameter 'Location' is required") + // ErrSparklineRange defined the error message on missing sparkline Range + // parameters + ErrSparklineRange = errors.New("parameter 'Range' is required") + // ErrSparkline defined the error message on receive the invalid sparkline + // parameters. + ErrSparkline = errors.New("must have the same number of 'Location' and 'Range' parameters") + // ErrSparklineStyle defined the error message on receive the invalid + // sparkline Style parameters. + ErrSparklineStyle = errors.New("parameter 'Style' must between 0-35") +) diff --git a/vendor/github.com/xuri/excelize/v2/excelize.go b/vendor/github.com/xuri/excelize/v2/excelize.go new file mode 100644 index 00000000..9fe3d881 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/excelize.go @@ -0,0 +1,491 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. +// +// See https://xuri.me/excelize for more information about this package. +package excelize + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "sync" + + "golang.org/x/net/html/charset" +) + +// File define a populated spreadsheet file struct. +type File struct { + sync.Mutex + options *Options + xmlAttr map[string][]xml.Attr + checked map[string]bool + sheetMap map[string]string + streams map[string]*StreamWriter + tempFiles sync.Map + CalcChain *xlsxCalcChain + Comments map[string]*xlsxComments + ContentTypes *xlsxTypes + Drawings sync.Map + Path string + SharedStrings *xlsxSST + sharedStringsMap map[string]int + sharedStringItem [][]uint + sharedStringTemp *os.File + Sheet sync.Map + SheetCount int + Styles *xlsxStyleSheet + Theme *xlsxTheme + DecodeVMLDrawing map[string]*decodeVmlDrawing + VMLDrawing map[string]*vmlDrawing + WorkBook *xlsxWorkbook + Relationships sync.Map + Pkg sync.Map + CharsetReader charsetTranscoderFn +} + +type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error) + +// Options define the options for open and reading spreadsheet. +// +// Password specifies the password of the spreadsheet in plain text. +// +// RawCellValue specifies if apply the number format for the cell value or get +// the raw value. +// +// UnzipSizeLimit specifies the unzip size limit in bytes on open the +// spreadsheet, this value should be greater than or equal to +// UnzipXMLSizeLimit, the default size limit is 16GB. +// +// UnzipXMLSizeLimit specifies the memory limit on unzipping worksheet and +// shared string table in bytes, worksheet XML will be extracted to system +// temporary directory when the file size is over this value, this value +// should be less than or equal to UnzipSizeLimit, the default value is +// 16MB. +type Options struct { + Password string + RawCellValue bool + UnzipSizeLimit int64 + UnzipXMLSizeLimit int64 +} + +// OpenFile take the name of an spreadsheet file and returns a populated +// spreadsheet file struct for it. For example, open spreadsheet with +// password protection: +// +// f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"}) +// if err != nil { +// return +// } +// +// Note that the excelize just support decrypt and not support encrypt +// currently, the spreadsheet saved by Save and SaveAs will be without +// password unprotected. Close the file by Close after opening the +// spreadsheet. +func OpenFile(filename string, opt ...Options) (*File, error) { + file, err := os.Open(filepath.Clean(filename)) + if err != nil { + return nil, err + } + f, err := OpenReader(file, opt...) + if err != nil { + closeErr := file.Close() + if closeErr == nil { + return f, err + } + return f, closeErr + } + f.Path = filename + return f, file.Close() +} + +// newFile is object builder +func newFile() *File { + return &File{ + options: &Options{UnzipSizeLimit: UnzipSizeLimit, UnzipXMLSizeLimit: StreamChunkSize}, + xmlAttr: make(map[string][]xml.Attr), + checked: make(map[string]bool), + sheetMap: make(map[string]string), + tempFiles: sync.Map{}, + Comments: make(map[string]*xlsxComments), + Drawings: sync.Map{}, + sharedStringsMap: make(map[string]int), + Sheet: sync.Map{}, + DecodeVMLDrawing: make(map[string]*decodeVmlDrawing), + VMLDrawing: make(map[string]*vmlDrawing), + Relationships: sync.Map{}, + CharsetReader: charset.NewReaderLabel, + } +} + +// OpenReader read data stream from io.Reader and return a populated +// spreadsheet file. +func OpenReader(r io.Reader, opt ...Options) (*File, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + f := newFile() + f.options = parseOptions(opt...) + if f.options.UnzipSizeLimit == 0 { + f.options.UnzipSizeLimit = UnzipSizeLimit + if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit { + f.options.UnzipSizeLimit = f.options.UnzipXMLSizeLimit + } + } + if f.options.UnzipXMLSizeLimit == 0 { + f.options.UnzipXMLSizeLimit = StreamChunkSize + if f.options.UnzipSizeLimit < f.options.UnzipXMLSizeLimit { + f.options.UnzipXMLSizeLimit = f.options.UnzipSizeLimit + } + } + if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit { + return nil, ErrOptionsUnzipSizeLimit + } + if bytes.Contains(b, oleIdentifier) { + b, err = Decrypt(b, f.options) + if err != nil { + return nil, fmt.Errorf("decrypted file failed") + } + } + zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) + if err != nil { + return nil, err + } + file, sheetCount, err := f.ReadZipReader(zr) + if err != nil { + return nil, err + } + f.SheetCount = sheetCount + for k, v := range file { + f.Pkg.Store(k, v) + } + f.CalcChain = f.calcChainReader() + f.sheetMap = f.getSheetMap() + f.Styles = f.stylesReader() + f.Theme = f.themeReader() + return f, nil +} + +// parseOptions provides a function to parse the optional settings for open +// and reading spreadsheet. +func parseOptions(opts ...Options) *Options { + opt := &Options{} + for _, o := range opts { + opt = &o + } + return opt +} + +// CharsetTranscoder Set user defined codepage transcoder function for open +// XLSX from non UTF-8 encoding. +func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f } + +// Creates new XML decoder with charset reader. +func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) { + ret = xml.NewDecoder(rdr) + ret.CharsetReader = f.CharsetReader + return +} + +// setDefaultTimeStyle provides a function to set default numbers format for +// time.Time type cell value by given worksheet name, cell coordinates and +// number format code. +func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error { + s, err := f.GetCellStyle(sheet, axis) + if err != nil { + return err + } + if s == 0 { + style, _ := f.NewStyle(&Style{NumFmt: format}) + err = f.SetCellStyle(sheet, axis, axis, style) + } + return err +} + +// workSheetReader provides a function to get the pointer to the structure +// after deserialization by given worksheet name. +func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) { + f.Lock() + defer f.Unlock() + var ( + name string + ok bool + ) + if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok { + err = fmt.Errorf("sheet %s is not exist", sheet) + return + } + if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil { + ws = worksheet.(*xlsxWorksheet) + return + } + if strings.HasPrefix(name, "xl/chartsheets") || strings.HasPrefix(name, "xl/macrosheet") { + err = fmt.Errorf("sheet %s is not a worksheet", sheet) + return + } + ws = new(xlsxWorksheet) + if _, ok := f.xmlAttr[name]; !ok { + d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))) + f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...) + } + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))). + Decode(ws); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + err = nil + if f.checked == nil { + f.checked = make(map[string]bool) + } + if ok = f.checked[name]; !ok { + checkSheet(ws) + if err = checkRow(ws); err != nil { + return + } + f.checked[name] = true + } + f.Sheet.Store(name, ws) + return +} + +// checkSheet provides a function to fill each row element and make that is +// continuous in a worksheet of XML. +func checkSheet(ws *xlsxWorksheet) { + var row int + var r0 xlsxRow + for i, r := range ws.SheetData.Row { + if i == 0 && r.R == 0 { + r0 = r + ws.SheetData.Row = ws.SheetData.Row[1:] + continue + } + if r.R != 0 && r.R > row { + row = r.R + continue + } + if r.R != row { + row++ + } + } + sheetData := xlsxSheetData{Row: make([]xlsxRow, row)} + row = 0 + for _, r := range ws.SheetData.Row { + if r.R == row && row > 0 { + sheetData.Row[r.R-1].C = append(sheetData.Row[r.R-1].C, r.C...) + continue + } + if r.R != 0 { + sheetData.Row[r.R-1] = r + row = r.R + continue + } + row++ + r.R = row + sheetData.Row[row-1] = r + } + for i := 1; i <= row; i++ { + sheetData.Row[i-1].R = i + } + checkSheetR0(ws, &sheetData, &r0) +} + +// checkSheetR0 handle the row element with r="0" attribute, cells in this row +// could be disorderly, the cell in this row can be used as the value of +// which cell is empty in the normal rows. +func checkSheetR0(ws *xlsxWorksheet, sheetData *xlsxSheetData, r0 *xlsxRow) { + for _, cell := range r0.C { + if col, row, err := CellNameToCoordinates(cell.R); err == nil { + rows, rowIdx := len(sheetData.Row), row-1 + for r := rows; r < row; r++ { + sheetData.Row = append(sheetData.Row, xlsxRow{R: r + 1}) + } + columns, colIdx := len(sheetData.Row[rowIdx].C), col-1 + for c := columns; c < col; c++ { + sheetData.Row[rowIdx].C = append(sheetData.Row[rowIdx].C, xlsxC{}) + } + if !sheetData.Row[rowIdx].C[colIdx].hasValue() { + sheetData.Row[rowIdx].C[colIdx] = cell + } + } + } + ws.SheetData = *sheetData +} + +// addRels provides a function to add relationships by given XML path, +// relationship type, target and target mode. +func (f *File) addRels(relPath, relType, target, targetMode string) int { + uniqPart := map[string]string{ + SourceRelationshipSharedStrings: "/xl/sharedStrings.xml", + } + rels := f.relsReader(relPath) + if rels == nil { + rels = &xlsxRelationships{} + } + rels.Lock() + defer rels.Unlock() + var rID int + for idx, rel := range rels.Relationships { + ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId")) + if ID > rID { + rID = ID + } + if relType == rel.Type { + if partName, ok := uniqPart[rel.Type]; ok { + rels.Relationships[idx].Target = partName + return rID + } + } + } + rID++ + var ID bytes.Buffer + ID.WriteString("rId") + ID.WriteString(strconv.Itoa(rID)) + rels.Relationships = append(rels.Relationships, xlsxRelationship{ + ID: ID.String(), + Type: relType, + Target: target, + TargetMode: targetMode, + }) + f.Relationships.Store(relPath, rels) + return rID +} + +// UpdateLinkedValue fix linked values within a spreadsheet are not updating in +// Office Excel 2007 and 2010. This function will be remove value tag when met a +// cell have a linked value. Reference +// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating +// +// Notice: after open XLSX file Excel will be update linked value and generate +// new value and will prompt save file or not. +// +// For example: +// +// +// +// SUM(Sheet2!D2,Sheet2!D11) +// 100 +// +// +// +// to +// +// +// +// SUM(Sheet2!D2,Sheet2!D11) +// +// +// +func (f *File) UpdateLinkedValue() error { + wb := f.workbookReader() + // recalculate formulas + wb.CalcPr = nil + for _, name := range f.GetSheetList() { + ws, err := f.workSheetReader(name) + if err != nil { + if err.Error() == fmt.Sprintf("sheet %s is not a worksheet", trimSheetName(name)) { + continue + } + return err + } + for indexR := range ws.SheetData.Row { + for indexC, col := range ws.SheetData.Row[indexR].C { + if col.F != nil && col.V != "" { + ws.SheetData.Row[indexR].C[indexC].V = "" + ws.SheetData.Row[indexR].C[indexC].T = "" + } + } + } + } + return nil +} + +// AddVBAProject provides the method to add vbaProject.bin file which contains +// functions and/or macros. The file extension should be .xlsm. For example: +// +// if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil { +// fmt.Println(err) +// } +// if err := f.AddVBAProject("vbaProject.bin"); err != nil { +// fmt.Println(err) +// } +// if err := f.SaveAs("macros.xlsm"); err != nil { +// fmt.Println(err) +// } +// +func (f *File) AddVBAProject(bin string) error { + var err error + // Check vbaProject.bin exists first. + if _, err = os.Stat(bin); os.IsNotExist(err) { + return fmt.Errorf("stat %s: no such file or directory", bin) + } + if path.Ext(bin) != ".bin" { + return ErrAddVBAProject + } + wb := f.relsReader(f.getWorkbookRelsPath()) + wb.Lock() + defer wb.Unlock() + var rID int + var ok bool + for _, rel := range wb.Relationships { + if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject { + ok = true + continue + } + t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId")) + if t > rID { + rID = t + } + } + rID++ + if !ok { + wb.Relationships = append(wb.Relationships, xlsxRelationship{ + ID: "rId" + strconv.Itoa(rID), + Target: "vbaProject.bin", + Type: SourceRelationshipVBAProject, + }) + } + file, _ := ioutil.ReadFile(filepath.Clean(bin)) + f.Pkg.Store("xl/vbaProject.bin", file) + return err +} + +// setContentTypePartProjectExtensions provides a function to set the content +// type for relationship parts and the main document part. +func (f *File) setContentTypePartProjectExtensions(contentType string) { + var ok bool + content := f.contentTypesReader() + content.Lock() + defer content.Unlock() + for _, v := range content.Defaults { + if v.Extension == "bin" { + ok = true + } + } + for idx, o := range content.Overrides { + if o.PartName == "/xl/workbook.xml" { + content.Overrides[idx].ContentType = contentType + } + } + if !ok { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: "bin", + ContentType: ContentTypeVBA, + }) + } +} diff --git a/vendor/github.com/xuri/excelize/v2/excelize.svg b/vendor/github.com/xuri/excelize/v2/excelize.svg new file mode 100644 index 00000000..afa88281 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/excelize.svg @@ -0,0 +1 @@ +Excelize logo \ No newline at end of file diff --git a/vendor/github.com/xuri/excelize/v2/file.go b/vendor/github.com/xuri/excelize/v2/file.go new file mode 100644 index 00000000..9707a793 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/file.go @@ -0,0 +1,229 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "io" + "os" + "path/filepath" + "sync" +) + +// NewFile provides a function to create new file by default template. For +// example: +// +// f := NewFile() +// +func NewFile() *File { + f := newFile() + f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels)) + f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp)) + f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore)) + f.Pkg.Store("xl/_rels/workbook.xml.rels", []byte(xml.Header+templateWorkbookRels)) + f.Pkg.Store("xl/theme/theme1.xml", []byte(xml.Header+templateTheme)) + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+templateSheet)) + f.Pkg.Store(defaultXMLPathStyles, []byte(xml.Header+templateStyles)) + f.Pkg.Store(defaultXMLPathWorkbook, []byte(xml.Header+templateWorkbook)) + f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes)) + f.SheetCount = 1 + f.CalcChain = f.calcChainReader() + f.Comments = make(map[string]*xlsxComments) + f.ContentTypes = f.contentTypesReader() + f.Drawings = sync.Map{} + f.Styles = f.stylesReader() + f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing) + f.VMLDrawing = make(map[string]*vmlDrawing) + f.WorkBook = f.workbookReader() + f.Relationships = sync.Map{} + f.Relationships.Store("xl/_rels/workbook.xml.rels", f.relsReader("xl/_rels/workbook.xml.rels")) + f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml" + ws, _ := f.workSheetReader("Sheet1") + f.Sheet.Store("xl/worksheets/sheet1.xml", ws) + f.Theme = f.themeReader() + return f +} + +// Save provides a function to override the spreadsheet with origin path. +func (f *File) Save() error { + if f.Path == "" { + return ErrSave + } + return f.SaveAs(f.Path) +} + +// SaveAs provides a function to create or update to a spreadsheet at the +// provided path. +func (f *File) SaveAs(name string, opt ...Options) error { + if len(name) > MaxFileNameLength { + return ErrMaxFileNameLength + } + f.Path = name + contentType, ok := map[string]string{ + ".xlam": ContentTypeAddinMacro, + ".xlsm": ContentTypeMacro, + ".xlsx": ContentTypeSheetML, + ".xltm": ContentTypeTemplateMacro, + ".xltx": ContentTypeTemplate, + }[filepath.Ext(f.Path)] + if !ok { + return ErrWorkbookExt + } + f.setContentTypePartProjectExtensions(contentType) + file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm) + if err != nil { + return err + } + defer file.Close() + f.options = nil + for i := range opt { + f.options = &opt[i] + } + return f.Write(file) +} + +// Close closes and cleanup the open temporary file for the spreadsheet. +func (f *File) Close() error { + var err error + if f.sharedStringTemp != nil { + if err := f.sharedStringTemp.Close(); err != nil { + return err + } + } + f.tempFiles.Range(func(k, v interface{}) bool { + if err = os.Remove(v.(string)); err != nil { + return false + } + return true + }) + return err +} + +// Write provides a function to write to an io.Writer. +func (f *File) Write(w io.Writer) error { + _, err := f.WriteTo(w) + return err +} + +// WriteTo implements io.WriterTo to write the file. +func (f *File) WriteTo(w io.Writer) (int64, error) { + if f.options != nil && f.options.Password != "" { + buf, err := f.WriteToBuffer() + if err != nil { + return 0, err + } + return buf.WriteTo(w) + } + if err := f.writeDirectToWriter(w); err != nil { + return 0, err + } + return 0, nil +} + +// WriteToBuffer provides a function to get bytes.Buffer from the saved file, +// and it allocates space in memory. Be careful when the file size is large. +func (f *File) WriteToBuffer() (*bytes.Buffer, error) { + buf := new(bytes.Buffer) + zw := zip.NewWriter(buf) + + if err := f.writeToZip(zw); err != nil { + return buf, zw.Close() + } + + if f.options != nil && f.options.Password != "" { + if err := zw.Close(); err != nil { + return buf, err + } + b, err := Encrypt(buf.Bytes(), f.options) + if err != nil { + return buf, err + } + buf.Reset() + buf.Write(b) + return buf, nil + } + return buf, zw.Close() +} + +// writeDirectToWriter provides a function to write to io.Writer. +func (f *File) writeDirectToWriter(w io.Writer) error { + zw := zip.NewWriter(w) + if err := f.writeToZip(zw); err != nil { + _ = zw.Close() + return err + } + return zw.Close() +} + +// writeToZip provides a function to write to zip.Writer +func (f *File) writeToZip(zw *zip.Writer) error { + f.calcChainWriter() + f.commentsWriter() + f.contentTypesWriter() + f.drawingsWriter() + f.vmlDrawingWriter() + f.workBookWriter() + f.workSheetWriter() + f.relsWriter() + f.sharedStringsLoader() + f.sharedStringsWriter() + f.styleSheetWriter() + + for path, stream := range f.streams { + fi, err := zw.Create(path) + if err != nil { + return err + } + var from io.Reader + from, err = stream.rawData.Reader() + if err != nil { + _ = stream.rawData.Close() + return err + } + _, err = io.Copy(fi, from) + if err != nil { + return err + } + _ = stream.rawData.Close() + } + var err error + f.Pkg.Range(func(path, content interface{}) bool { + if err != nil { + return false + } + if _, ok := f.streams[path.(string)]; ok { + return true + } + var fi io.Writer + fi, err = zw.Create(path.(string)) + if err != nil { + return false + } + _, err = fi.Write(content.([]byte)) + return true + }) + f.tempFiles.Range(func(path, content interface{}) bool { + if _, ok := f.Pkg.Load(path); ok { + return true + } + var fi io.Writer + fi, err = zw.Create(path.(string)) + if err != nil { + return false + } + _, err = fi.Write(f.readBytes(path.(string))) + return true + }) + return err +} diff --git a/vendor/github.com/xuri/excelize/v2/go.mod b/vendor/github.com/xuri/excelize/v2/go.mod new file mode 100644 index 00000000..116b7a17 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/go.mod @@ -0,0 +1,15 @@ +module github.com/xuri/excelize/v2 + +go 1.15 + +require ( + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 + github.com/richardlehane/mscfb v1.0.4 + github.com/stretchr/testify v1.7.0 + github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8 + github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 + golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 + golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 + golang.org/x/text v0.3.7 +) diff --git a/vendor/github.com/xuri/excelize/v2/go.sum b/vendor/github.com/xuri/excelize/v2/go.sum new file mode 100644 index 00000000..8ca6233f --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/go.sum @@ -0,0 +1,38 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8 h1:3X7aE0iLKJ5j+tz58BpvIZkXNV7Yq4jC93Z/rbN2Fxk= +github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM= +golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c= +golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/xuri/excelize/v2/hsl.go b/vendor/github.com/xuri/excelize/v2/hsl.go new file mode 100644 index 00000000..c30c165a --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/hsl.go @@ -0,0 +1,139 @@ +// Copyright (c) 2012 Rodrigo Moraes. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package excelize + +import ( + "image/color" + "math" +) + +// HSLModel converts any color.Color to a HSL color. +var HSLModel = color.ModelFunc(hslModel) + +// HSL represents a cylindrical coordinate of points in an RGB color model. +// +// Values are in the range 0 to 1. +type HSL struct { + H, S, L float64 +} + +// RGBA returns the alpha-premultiplied red, green, blue and alpha values +// for the HSL. +func (c HSL) RGBA() (uint32, uint32, uint32, uint32) { + r, g, b := HSLToRGB(c.H, c.S, c.L) + return uint32(r) * 0x101, uint32(g) * 0x101, uint32(b) * 0x101, 0xffff +} + +// hslModel converts a color.Color to HSL. +func hslModel(c color.Color) color.Color { + if _, ok := c.(HSL); ok { + return c + } + r, g, b, _ := c.RGBA() + h, s, l := RGBToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) + return HSL{h, s, l} +} + +// RGBToHSL converts an RGB triple to a HSL triple. +func RGBToHSL(r, g, b uint8) (h, s, l float64) { + fR := float64(r) / 255 + fG := float64(g) / 255 + fB := float64(b) / 255 + max := math.Max(math.Max(fR, fG), fB) + min := math.Min(math.Min(fR, fG), fB) + l = (max + min) / 2 + if max == min { + // Achromatic. + h, s = 0, 0 + } else { + // Chromatic. + d := max - min + if l > 0.5 { + s = d / (2.0 - max - min) + } else { + s = d / (max + min) + } + switch max { + case fR: + h = (fG - fB) / d + if fG < fB { + h += 6 + } + case fG: + h = (fB-fR)/d + 2 + case fB: + h = (fR-fG)/d + 4 + } + h /= 6 + } + return +} + +// HSLToRGB converts an HSL triple to a RGB triple. +func HSLToRGB(h, s, l float64) (r, g, b uint8) { + var fR, fG, fB float64 + if s == 0 { + fR, fG, fB = l, l, l + } else { + var q float64 + if l < 0.5 { + q = l * (1 + s) + } else { + q = l + s - s*l + } + p := 2*l - q + fR = hueToRGB(p, q, h+1.0/3) + fG = hueToRGB(p, q, h) + fB = hueToRGB(p, q, h-1.0/3) + } + r = uint8((fR * 255) + 0.5) + g = uint8((fG * 255) + 0.5) + b = uint8((fB * 255) + 0.5) + return +} + +// hueToRGB is a helper function for HSLToRGB. +func hueToRGB(p, q, t float64) float64 { + if t < 0 { + t++ + } + if t > 1 { + t-- + } + if t < 1.0/6 { + return p + (q-p)*6*t + } + if t < 0.5 { + return q + } + if t < 2.0/3 { + return p + (q-p)*(2.0/3-t)*6 + } + return p +} diff --git a/vendor/github.com/xuri/excelize/v2/lib.go b/vendor/github.com/xuri/excelize/v2/lib.go new file mode 100644 index 00000000..723b976f --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/lib.go @@ -0,0 +1,857 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "archive/zip" + "bytes" + "container/list" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "os" + "regexp" + "strconv" + "strings" +) + +// ReadZipReader extract spreadsheet with given options. +func (f *File) ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) { + var ( + err error + docPart = map[string]string{ + "[content_types].xml": defaultXMLPathContentTypes, + "xl/sharedstrings.xml": defaultXMLPathSharedStrings, + } + fileList = make(map[string][]byte, len(r.File)) + worksheets int + unzipSize int64 + ) + for _, v := range r.File { + fileSize := v.FileInfo().Size() + unzipSize += fileSize + if unzipSize > f.options.UnzipSizeLimit { + return fileList, worksheets, newUnzipSizeLimitError(f.options.UnzipSizeLimit) + } + fileName := strings.Replace(v.Name, "\\", "/", -1) + if partName, ok := docPart[strings.ToLower(fileName)]; ok { + fileName = partName + } + if strings.EqualFold(fileName, defaultXMLPathSharedStrings) && fileSize > f.options.UnzipXMLSizeLimit { + if tempFile, err := f.unzipToTemp(v); err == nil { + f.tempFiles.Store(fileName, tempFile) + continue + } + } + if strings.HasPrefix(fileName, "xl/worksheets/sheet") { + worksheets++ + if fileSize > f.options.UnzipXMLSizeLimit && !v.FileInfo().IsDir() { + if tempFile, err := f.unzipToTemp(v); err == nil { + f.tempFiles.Store(fileName, tempFile) + continue + } + } + } + if fileList[fileName], err = readFile(v); err != nil { + return nil, 0, err + } + } + return fileList, worksheets, nil +} + +// unzipToTemp unzip the zip entity to the system temporary directory and +// returned the unzipped file path. +func (f *File) unzipToTemp(zipFile *zip.File) (string, error) { + tmp, err := ioutil.TempFile(os.TempDir(), "excelize-") + if err != nil { + return "", err + } + rc, err := zipFile.Open() + if err != nil { + return tmp.Name(), err + } + if _, err = io.Copy(tmp, rc); err != nil { + return tmp.Name(), err + } + if err = rc.Close(); err != nil { + return tmp.Name(), err + } + return tmp.Name(), tmp.Close() +} + +// readXML provides a function to read XML content as bytes. +func (f *File) readXML(name string) []byte { + if content, _ := f.Pkg.Load(name); content != nil { + return content.([]byte) + } + if content, ok := f.streams[name]; ok { + return content.rawData.buf.Bytes() + } + return []byte{} +} + +// readBytes read file as bytes by given path. +func (f *File) readBytes(name string) []byte { + content := f.readXML(name) + if len(content) != 0 { + return content + } + file, err := f.readTemp(name) + if err != nil { + return content + } + content, _ = ioutil.ReadAll(file) + f.Pkg.Store(name, content) + _ = file.Close() + return content +} + +// readTemp read file from system temporary directory by given path. +func (f *File) readTemp(name string) (file *os.File, err error) { + path, ok := f.tempFiles.Load(name) + if !ok { + return + } + file, err = os.Open(path.(string)) + return +} + +// saveFileList provides a function to update given file content in file list +// of spreadsheet. +func (f *File) saveFileList(name string, content []byte) { + f.Pkg.Store(name, append([]byte(xml.Header), content...)) +} + +// Read file content as string in an archive file. +func readFile(file *zip.File) ([]byte, error) { + rc, err := file.Open() + if err != nil { + return nil, err + } + dat := make([]byte, 0, file.FileInfo().Size()) + buff := bytes.NewBuffer(dat) + _, _ = io.Copy(buff, rc) + return buff.Bytes(), rc.Close() +} + +// SplitCellName splits cell name to column name and row number. +// +// Example: +// +// excelize.SplitCellName("AK74") // return "AK", 74, nil +// +func SplitCellName(cell string) (string, int, error) { + alpha := func(r rune) bool { + return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') || (r == 36) + } + if strings.IndexFunc(cell, alpha) == 0 { + i := strings.LastIndexFunc(cell, alpha) + if i >= 0 && i < len(cell)-1 { + col, rowStr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:] + if row, err := strconv.Atoi(rowStr); err == nil && row > 0 { + return col, row, nil + } + } + } + return "", -1, newInvalidCellNameError(cell) +} + +// JoinCellName joins cell name from column name and row number. +func JoinCellName(col string, row int) (string, error) { + normCol := strings.Map(func(rune rune) rune { + switch { + case 'A' <= rune && rune <= 'Z': + return rune + case 'a' <= rune && rune <= 'z': + return rune - 32 + } + return -1 + }, col) + if len(col) == 0 || len(col) != len(normCol) { + return "", newInvalidColumnNameError(col) + } + if row < 1 { + return "", newInvalidRowNumberError(row) + } + return normCol + strconv.Itoa(row), nil +} + +// ColumnNameToNumber provides a function to convert Excel sheet column name +// to int. Column name case-insensitive. The function returns an error if +// column name incorrect. +// +// Example: +// +// excelize.ColumnNameToNumber("AK") // returns 37, nil +// +func ColumnNameToNumber(name string) (int, error) { + if len(name) == 0 { + return -1, newInvalidColumnNameError(name) + } + col := 0 + multi := 1 + for i := len(name) - 1; i >= 0; i-- { + r := name[i] + if r >= 'A' && r <= 'Z' { + col += int(r-'A'+1) * multi + } else if r >= 'a' && r <= 'z' { + col += int(r-'a'+1) * multi + } else { + return -1, newInvalidColumnNameError(name) + } + multi *= 26 + } + if col > TotalColumns { + return -1, ErrColumnNumber + } + return col, nil +} + +// ColumnNumberToName provides a function to convert the integer to Excel +// sheet column title. +// +// Example: +// +// excelize.ColumnNumberToName(37) // returns "AK", nil +// +func ColumnNumberToName(num int) (string, error) { + if num < 1 { + return "", fmt.Errorf("incorrect column number %d", num) + } + if num > TotalColumns { + return "", ErrColumnNumber + } + var col string + for num > 0 { + col = string(rune((num-1)%26+65)) + col + num = (num - 1) / 26 + } + return col, nil +} + +// CellNameToCoordinates converts alphanumeric cell name to [X, Y] coordinates +// or returns an error. +// +// Example: +// +// excelize.CellNameToCoordinates("A1") // returns 1, 1, nil +// excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil +// +func CellNameToCoordinates(cell string) (int, int, error) { + colName, row, err := SplitCellName(cell) + if err != nil { + return -1, -1, newCellNameToCoordinatesError(cell, err) + } + if row > TotalRows { + return -1, -1, ErrMaxRows + } + col, err := ColumnNameToNumber(colName) + return col, row, err +} + +// CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell +// name or returns an error. +// +// Example: +// +// excelize.CoordinatesToCellName(1, 1) // returns "A1", nil +// excelize.CoordinatesToCellName(1, 1, true) // returns "$A$1", nil +// +func CoordinatesToCellName(col, row int, abs ...bool) (string, error) { + if col < 1 || row < 1 { + return "", fmt.Errorf("invalid cell coordinates [%d, %d]", col, row) + } + sign := "" + for _, a := range abs { + if a { + sign = "$" + } + } + colName, err := ColumnNumberToName(col) + return sign + colName + sign + strconv.Itoa(row), err +} + +// areaRefToCoordinates provides a function to convert area reference to a +// pair of coordinates. +func areaRefToCoordinates(ref string) ([]int, error) { + rng := strings.Split(strings.Replace(ref, "$", "", -1), ":") + if len(rng) < 2 { + return nil, ErrParameterInvalid + } + return areaRangeToCoordinates(rng[0], rng[1]) +} + +// areaRangeToCoordinates provides a function to convert cell range to a +// pair of coordinates. +func areaRangeToCoordinates(firstCell, lastCell string) ([]int, error) { + coordinates := make([]int, 4) + var err error + coordinates[0], coordinates[1], err = CellNameToCoordinates(firstCell) + if err != nil { + return coordinates, err + } + coordinates[2], coordinates[3], err = CellNameToCoordinates(lastCell) + return coordinates, err +} + +// sortCoordinates provides a function to correct the coordinate area, such +// correct C1:B3 to B1:C3. +func sortCoordinates(coordinates []int) error { + if len(coordinates) != 4 { + return ErrCoordinates + } + if coordinates[2] < coordinates[0] { + coordinates[2], coordinates[0] = coordinates[0], coordinates[2] + } + if coordinates[3] < coordinates[1] { + coordinates[3], coordinates[1] = coordinates[1], coordinates[3] + } + return nil +} + +// coordinatesToAreaRef provides a function to convert a pair of coordinates +// to area reference. +func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) { + if len(coordinates) != 4 { + return "", ErrCoordinates + } + firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1]) + if err != nil { + return "", err + } + lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3]) + if err != nil { + return "", err + } + return firstCell + ":" + lastCell, err +} + +// getDefinedNameRefTo convert defined name to reference range. +func (f *File) getDefinedNameRefTo(definedNameName string, currentSheet string) (refTo string) { + var workbookRefTo, worksheetRefTo string + for _, definedName := range f.GetDefinedName() { + if definedName.Name == definedNameName { + // worksheet scope takes precedence over scope workbook when both definedNames exist + if definedName.Scope == "Workbook" { + workbookRefTo = definedName.RefersTo + } + if definedName.Scope == currentSheet { + worksheetRefTo = definedName.RefersTo + } + } + } + refTo = workbookRefTo + if worksheetRefTo != "" { + refTo = worksheetRefTo + } + return +} + +// flatSqref convert reference sequence to cell coordinates list. +func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) { + var coordinates []int + cells = make(map[int][][]int) + for _, ref := range strings.Fields(sqref) { + rng := strings.Split(ref, ":") + switch len(rng) { + case 1: + var col, row int + col, row, err = CellNameToCoordinates(rng[0]) + if err != nil { + return + } + cells[col] = append(cells[col], []int{col, row}) + case 2: + if coordinates, err = areaRefToCoordinates(ref); err != nil { + return + } + _ = sortCoordinates(coordinates) + for c := coordinates[0]; c <= coordinates[2]; c++ { + for r := coordinates[1]; r <= coordinates[3]; r++ { + cells[c] = append(cells[c], []int{c, r}) + } + } + } + } + return +} + +// inCoordinates provides a method to check if a coordinate is present in +// coordinates array, and return the index of its location, otherwise +// return -1. +func inCoordinates(a [][]int, x []int) int { + for idx, n := range a { + if x[0] == n[0] && x[1] == n[1] { + return idx + } + } + return -1 +} + +// inStrSlice provides a method to check if an element is present in an array, +// and return the index of its location, otherwise return -1. +func inStrSlice(a []string, x string, caseSensitive bool) int { + for idx, n := range a { + if !caseSensitive && strings.EqualFold(x, n) { + return idx + } + if x == n { + return idx + } + } + return -1 +} + +// inFloat64Slice provides a method to check if an element is present in a +// float64 array, and return the index of its location, otherwise return -1. +func inFloat64Slice(a []float64, x float64) int { + for idx, n := range a { + if x == n { + return idx + } + } + return -1 +} + +// boolPtr returns a pointer to a bool with the given value. +func boolPtr(b bool) *bool { return &b } + +// intPtr returns a pointer to an int with the given value. +func intPtr(i int) *int { return &i } + +// float64Ptr returns a pointer to a float64 with the given value. +func float64Ptr(f float64) *float64 { return &f } + +// stringPtr returns a pointer to a string with the given value. +func stringPtr(s string) *string { return &s } + +// defaultTrue returns true if b is nil, or the pointed value. +func defaultTrue(b *bool) bool { + if b == nil { + return true + } + return *b +} + +// MarshalXML convert the boolean data type to literal values 0 or 1 on +// serialization. +func (avb attrValBool) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + attr := xml.Attr{ + Name: xml.Name{ + Space: start.Name.Space, + Local: "val", + }, + Value: "0", + } + if avb.Val != nil { + if *avb.Val { + attr.Value = "1" + } else { + attr.Value = "0" + } + } + start.Attr = []xml.Attr{attr} + if err := e.EncodeToken(start); err != nil { + return err + } + return e.EncodeToken(start.End()) +} + +// UnmarshalXML convert the literal values true, false, 1, 0 of the XML +// attribute to boolean data type on deserialization. +func (avb *attrValBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for { + t, err := d.Token() + if err != nil { + return err + } + found := false + switch t.(type) { + case xml.StartElement: + return ErrAttrValBool + case xml.EndElement: + found = true + } + if found { + break + } + } + for _, attr := range start.Attr { + if attr.Name.Local == "val" { + if attr.Value == "" { + val := true + avb.Val = &val + } else { + val, err := strconv.ParseBool(attr.Value) + if err != nil { + return err + } + avb.Val = &val + } + return nil + } + } + defaultVal := true + avb.Val = &defaultVal + return nil +} + +// parseFormatSet provides a method to convert format string to []byte and +// handle empty string. +func parseFormatSet(formatSet string) []byte { + if formatSet != "" { + return []byte(formatSet) + } + return []byte("{}") +} + +// namespaceStrictToTransitional provides a method to convert Strict and +// Transitional namespaces. +func namespaceStrictToTransitional(content []byte) []byte { + namespaceTranslationDic := map[string]string{ + StrictSourceRelationship: SourceRelationship.Value, + StrictSourceRelationshipOfficeDocument: SourceRelationshipOfficeDocument, + StrictSourceRelationshipChart: SourceRelationshipChart, + StrictSourceRelationshipComments: SourceRelationshipComments, + StrictSourceRelationshipImage: SourceRelationshipImage, + StrictNameSpaceSpreadSheet: NameSpaceSpreadSheet.Value, + } + for s, n := range namespaceTranslationDic { + content = bytesReplace(content, []byte(s), []byte(n), -1) + } + return content +} + +// bytesReplace replace old bytes with given new. +func bytesReplace(s, old, new []byte, n int) []byte { + if n == 0 { + return s + } + + if len(old) < len(new) { + return bytes.Replace(s, old, new, n) + } + + if n < 0 { + n = len(s) + } + + var wid, i, j, w int + for i, j = 0, 0; i < len(s) && j < n; j++ { + wid = bytes.Index(s[i:], old) + if wid < 0 { + break + } + + w += copy(s[w:], s[i:i+wid]) + w += copy(s[w:], new) + i += wid + len(old) + } + + w += copy(s[w:], s[i:]) + return s[:w] +} + +// genSheetPasswd provides a method to generate password for worksheet +// protection by given plaintext. When an Excel sheet is being protected with +// a password, a 16-bit (two byte) long hash is generated. To verify a +// password, it is compared to the hash. Obviously, if the input data volume +// is great, numerous passwords will match the same hash. Here is the +// algorithm to create the hash value: +// +// take the ASCII values of all characters shift left the first character 1 bit, +// the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits, +// the highest bit of the 16-bit value is always 0 [signed short]) +// XOR all these values +// XOR the count of characters +// XOR the constant 0xCE4B +func genSheetPasswd(plaintext string) string { + var password int64 = 0x0000 + var charPos uint = 1 + for _, v := range plaintext { + value := int64(v) << charPos + charPos++ + rotatedBits := value >> 15 // rotated bits beyond bit 15 + value &= 0x7fff // first 15 bits + password ^= value | rotatedBits + } + password ^= int64(len(plaintext)) + password ^= 0xCE4B + return strings.ToUpper(strconv.FormatInt(password, 16)) +} + +// getRootElement extract root element attributes by given XML decoder. +func getRootElement(d *xml.Decoder) []xml.Attr { + tokenIdx := 0 + for { + token, _ := d.Token() + if token == nil { + break + } + switch startElement := token.(type) { + case xml.StartElement: + tokenIdx++ + if tokenIdx == 1 { + return startElement.Attr + } + } + } + return nil +} + +// genXMLNamespace generate serialized XML attributes with a multi namespace +// by given element attributes. +func genXMLNamespace(attr []xml.Attr) string { + var rootElement string + for _, v := range attr { + if lastSpace := getXMLNamespace(v.Name.Space, attr); lastSpace != "" { + if lastSpace == NameSpaceXML { + lastSpace = "xml" + } + rootElement += fmt.Sprintf("%s:%s=\"%s\" ", lastSpace, v.Name.Local, v.Value) + continue + } + rootElement += fmt.Sprintf("%s=\"%s\" ", v.Name.Local, v.Value) + } + return strings.TrimSpace(rootElement) + ">" +} + +// getXMLNamespace extract XML namespace from specified element name and attributes. +func getXMLNamespace(space string, attr []xml.Attr) string { + for _, attribute := range attr { + if attribute.Value == space { + return attribute.Name.Local + } + } + return space +} + +// replaceNameSpaceBytes provides a function to replace the XML root element +// attribute by the given component part path and XML content. +func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte { + oldXmlns := []byte(`xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`) + newXmlns := []byte(templateNamespaceIDMap) + if attr, ok := f.xmlAttr[path]; ok { + newXmlns = []byte(genXMLNamespace(attr)) + } + return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1) +} + +// addNameSpaces provides a function to add an XML attribute by the given +// component part path. +func (f *File) addNameSpaces(path string, ns xml.Attr) { + exist := false + mc := false + ignore := -1 + if attr, ok := f.xmlAttr[path]; ok { + for i, attribute := range attr { + if attribute.Name.Local == ns.Name.Local && attribute.Name.Space == ns.Name.Space { + exist = true + } + if attribute.Name.Local == "Ignorable" && getXMLNamespace(attribute.Name.Space, attr) == "mc" { + ignore = i + } + if attribute.Name.Local == "mc" && attribute.Name.Space == "xmlns" { + mc = true + } + } + } + if !exist { + f.xmlAttr[path] = append(f.xmlAttr[path], ns) + if !mc { + f.xmlAttr[path] = append(f.xmlAttr[path], SourceRelationshipCompatibility) + } + if ignore == -1 { + f.xmlAttr[path] = append(f.xmlAttr[path], xml.Attr{ + Name: xml.Name{Local: "Ignorable", Space: "mc"}, + Value: ns.Name.Local, + }) + return + } + f.setIgnorableNameSpace(path, ignore, ns) + } +} + +// setIgnorableNameSpace provides a function to set XML namespace as ignorable +// by the given attribute. +func (f *File) setIgnorableNameSpace(path string, index int, ns xml.Attr) { + ignorableNS := []string{"c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14", "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9", "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x15", "x16", "x16r2", "mo", "mx", "mv", "o", "v"} + if inStrSlice(strings.Fields(f.xmlAttr[path][index].Value), ns.Name.Local, true) == -1 && inStrSlice(ignorableNS, ns.Name.Local, true) != -1 { + f.xmlAttr[path][index].Value = strings.TrimSpace(fmt.Sprintf("%s %s", f.xmlAttr[path][index].Value, ns.Name.Local)) + } +} + +// addSheetNameSpace add XML attribute for worksheet. +func (f *File) addSheetNameSpace(sheet string, ns xml.Attr) { + name := f.sheetMap[trimSheetName(sheet)] + f.addNameSpaces(name, ns) +} + +// isNumeric determines whether an expression is a valid numeric type and get +// the precision for the numeric. +func isNumeric(s string) (bool, int) { + dot, e, n, p := false, false, false, 0 + for i, v := range s { + if v == '.' { + if dot { + return false, 0 + } + dot = true + } else if v == 'E' || v == 'e' { + e = true + } else if v < '0' || v > '9' { + if i == 0 && v == '-' { + continue + } + if e && v == '-' { + return true, 0 + } + if e && v == '+' { + p = 15 + continue + } + return false, 0 + } else { + p++ + } + n = true + } + return n, p +} + +var ( + bstrExp = regexp.MustCompile(`_x[a-zA-Z\d]{4}_`) + bstrEscapeExp = regexp.MustCompile(`x[a-zA-Z\d]{4}_`) +) + +// bstrUnmarshal parses the binary basic string, this will trim escaped string +// literal which not permitted in an XML 1.0 document. The basic string +// variant type can store any valid Unicode character. Unicode's characters +// that cannot be directly represented in XML as defined by the XML 1.0 +// specification, shall be escaped using the Unicode numerical character +// representation escape character format _xHHHH_, where H represents a +// hexadecimal character in the character's value. For example: The Unicode +// character 8 is not permitted in an XML 1.0 document, so it shall be +// escaped as _x0008_. To store the literal form of an escape sequence, the +// initial underscore shall itself be escaped (i.e. stored as _x005F_). For +// example: The string literal _x0008_ would be stored as _x005F_x0008_. +func bstrUnmarshal(s string) (result string) { + matches, l, cursor := bstrExp.FindAllStringSubmatchIndex(s, -1), len(s), 0 + for _, match := range matches { + result += s[cursor:match[0]] + subStr := s[match[0]:match[1]] + if subStr == "_x005F_" { + cursor = match[1] + result += "_" + continue + } + if bstrExp.MatchString(subStr) { + cursor = match[1] + v, err := strconv.Unquote(`"\u` + s[match[0]+2:match[1]-1] + `"`) + if err != nil { + if l > match[1]+6 && bstrEscapeExp.MatchString(s[match[1]:match[1]+6]) { + result += subStr[:6] + cursor = match[1] + 6 + continue + } + result += subStr + continue + } + result += v + } + } + if cursor < l { + result += s[cursor:] + } + return result +} + +// bstrMarshal encode the escaped string literal which not permitted in an XML +// 1.0 document. +func bstrMarshal(s string) (result string) { + matches, l, cursor := bstrExp.FindAllStringSubmatchIndex(s, -1), len(s), 0 + for _, match := range matches { + result += s[cursor:match[0]] + subStr := s[match[0]:match[1]] + if subStr == "_x005F_" { + cursor = match[1] + if match[1]+6 <= l && bstrEscapeExp.MatchString(s[match[1]:match[1]+6]) { + _, err := strconv.Unquote(`"\u` + s[match[1]+1:match[1]+5] + `"`) + if err == nil { + result += subStr + "x005F" + subStr + continue + } + } + result += subStr + "x005F_" + continue + } + if bstrExp.MatchString(subStr) { + cursor = match[1] + _, err := strconv.Unquote(`"\u` + s[match[0]+2:match[1]-1] + `"`) + if err == nil { + result += "_x005F" + subStr + continue + } + result += subStr + } + } + if cursor < l { + result += s[cursor:] + } + return result +} + +// Stack defined an abstract data type that serves as a collection of elements. +type Stack struct { + list *list.List +} + +// NewStack create a new stack. +func NewStack() *Stack { + l := list.New() + return &Stack{l} +} + +// Push a value onto the top of the stack. +func (stack *Stack) Push(value interface{}) { + stack.list.PushBack(value) +} + +// Pop the top item of the stack and return it. +func (stack *Stack) Pop() interface{} { + e := stack.list.Back() + if e != nil { + stack.list.Remove(e) + return e.Value + } + return nil +} + +// Peek view the top item on the stack. +func (stack *Stack) Peek() interface{} { + e := stack.list.Back() + if e != nil { + return e.Value + } + return nil +} + +// Len return the number of items in the stack. +func (stack *Stack) Len() int { + return stack.list.Len() +} + +// Empty the stack. +func (stack *Stack) Empty() bool { + return stack.list.Len() == 0 +} diff --git a/vendor/github.com/xuri/excelize/v2/logo.png b/vendor/github.com/xuri/excelize/v2/logo.png new file mode 100644 index 00000000..c37ac156 Binary files /dev/null and b/vendor/github.com/xuri/excelize/v2/logo.png differ diff --git a/vendor/github.com/xuri/excelize/v2/merge.go b/vendor/github.com/xuri/excelize/v2/merge.go new file mode 100644 index 00000000..376b68b2 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/merge.go @@ -0,0 +1,287 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "strings" +) + +// Rect gets merged cell rectangle coordinates sequence. +func (mc *xlsxMergeCell) Rect() ([]int, error) { + var err error + if mc.rect == nil { + mc.rect, err = areaRefToCoordinates(mc.Ref) + } + return mc.rect, err +} + +// MergeCell provides a function to merge cells by given coordinate area and +// sheet name. Merging cells only keeps the upper-left cell value, and +// discards the other values. For example create a merged cell of D3:E9 on +// Sheet1: +// +// err := f.MergeCell("Sheet1", "D3", "E9") +// +// If you create a merged cell that overlaps with another existing merged cell, +// those merged cells that already exist will be removed. The cell coordinates +// tuple after merging in the following range will be: A1(x3,y1) D1(x2,y1) +// A8(x3,y4) D8(x2,y4) +// +// B1(x1,y1) D1(x2,y1) +// +------------------------+ +// | | +// A4(x3,y3) | C4(x4,y3) | +// +------------------------+ | +// | | | | +// | |B5(x1,y2) | D5(x2,y2)| +// | +------------------------+ +// | | +// |A8(x3,y4) C8(x4,y4)| +// +------------------------+ +// +func (f *File) MergeCell(sheet, hCell, vCell string) error { + rect, err := areaRefToCoordinates(hCell + ":" + vCell) + if err != nil { + return err + } + // Correct the coordinate area, such correct C1:B3 to B1:C3. + _ = sortCoordinates(rect) + + hCell, _ = CoordinatesToCellName(rect[0], rect[1]) + vCell, _ = CoordinatesToCellName(rect[2], rect[3]) + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + ref := hCell + ":" + vCell + if ws.MergeCells != nil { + ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: rect}) + } else { + ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref, rect: rect}}} + } + ws.MergeCells.Count = len(ws.MergeCells.Cells) + styleID, _ := f.GetCellStyle(sheet, hCell) + return f.SetCellStyle(sheet, hCell, vCell, styleID) +} + +// UnmergeCell provides a function to unmerge a given coordinate area. +// For example unmerge area D3:E9 on Sheet1: +// +// err := f.UnmergeCell("Sheet1", "D3", "E9") +// +// Attention: overlapped areas will also be unmerged. +func (f *File) UnmergeCell(sheet string, hCell, vCell string) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + rect1, err := areaRefToCoordinates(hCell + ":" + vCell) + if err != nil { + return err + } + + // Correct the coordinate area, such correct C1:B3 to B1:C3. + _ = sortCoordinates(rect1) + + // return nil since no MergeCells in the sheet + if ws.MergeCells == nil { + return nil + } + if err = f.mergeOverlapCells(ws); err != nil { + return err + } + i := 0 + for _, mergeCell := range ws.MergeCells.Cells { + if mergeCell == nil { + continue + } + rect2, _ := areaRefToCoordinates(mergeCell.Ref) + if isOverlap(rect1, rect2) { + continue + } + ws.MergeCells.Cells[i] = mergeCell + i++ + } + ws.MergeCells.Cells = ws.MergeCells.Cells[:i] + ws.MergeCells.Count = len(ws.MergeCells.Cells) + if ws.MergeCells.Count == 0 { + ws.MergeCells = nil + } + return nil +} + +// GetMergeCells provides a function to get all merged cells from a worksheet +// currently. +func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) { + var mergeCells []MergeCell + ws, err := f.workSheetReader(sheet) + if err != nil { + return mergeCells, err + } + if ws.MergeCells != nil { + if err = f.mergeOverlapCells(ws); err != nil { + return mergeCells, err + } + mergeCells = make([]MergeCell, 0, len(ws.MergeCells.Cells)) + for i := range ws.MergeCells.Cells { + ref := ws.MergeCells.Cells[i].Ref + axis := strings.Split(ref, ":")[0] + val, _ := f.GetCellValue(sheet, axis) + mergeCells = append(mergeCells, []string{ref, val}) + } + } + return mergeCells, err +} + +// overlapRange calculate overlap range of merged cells, and returns max +// column and rows of the range. +func overlapRange(ws *xlsxWorksheet) (row, col int, err error) { + var rect []int + for _, mergeCell := range ws.MergeCells.Cells { + if mergeCell == nil { + continue + } + if rect, err = mergeCell.Rect(); err != nil { + return + } + x1, y1, x2, y2 := rect[0], rect[1], rect[2], rect[3] + if x1 > col { + col = x1 + } + if x2 > col { + col = x2 + } + if y1 > row { + row = y1 + } + if y2 > row { + row = y2 + } + } + return +} + +// flatMergedCells convert merged cells range reference to cell-matrix. +func flatMergedCells(ws *xlsxWorksheet, matrix [][]*xlsxMergeCell) error { + for i, cell := range ws.MergeCells.Cells { + rect, err := cell.Rect() + if err != nil { + return err + } + x1, y1, x2, y2 := rect[0]-1, rect[1]-1, rect[2]-1, rect[3]-1 + var overlapCells []*xlsxMergeCell + for x := x1; x <= x2; x++ { + for y := y1; y <= y2; y++ { + if matrix[x][y] != nil { + overlapCells = append(overlapCells, matrix[x][y]) + } + matrix[x][y] = cell + } + } + if len(overlapCells) != 0 { + newCell := cell + for _, overlapCell := range overlapCells { + newCell = mergeCell(cell, overlapCell) + } + newRect, _ := newCell.Rect() + x1, y1, x2, y2 := newRect[0]-1, newRect[1]-1, newRect[2]-1, newRect[3]-1 + for x := x1; x <= x2; x++ { + for y := y1; y <= y2; y++ { + matrix[x][y] = newCell + } + } + ws.MergeCells.Cells[i] = newCell + } + } + return nil +} + +// mergeOverlapCells merge overlap cells. +func (f *File) mergeOverlapCells(ws *xlsxWorksheet) error { + rows, cols, err := overlapRange(ws) + if err != nil { + return err + } + if rows == 0 || cols == 0 { + return nil + } + matrix := make([][]*xlsxMergeCell, cols) + for i := range matrix { + matrix[i] = make([]*xlsxMergeCell, rows) + } + _ = flatMergedCells(ws, matrix) + mergeCells := ws.MergeCells.Cells[:0] + for _, cell := range ws.MergeCells.Cells { + rect, _ := cell.Rect() + x1, y1, x2, y2 := rect[0]-1, rect[1]-1, rect[2]-1, rect[3]-1 + if matrix[x1][y1] == cell { + mergeCells = append(mergeCells, cell) + for x := x1; x <= x2; x++ { + for y := y1; y <= y2; y++ { + matrix[x][y] = nil + } + } + } + } + ws.MergeCells.Count, ws.MergeCells.Cells = len(mergeCells), mergeCells + return nil +} + +// mergeCell merge two cells. +func mergeCell(cell1, cell2 *xlsxMergeCell) *xlsxMergeCell { + rect1, _ := cell1.Rect() + rect2, _ := cell2.Rect() + + if rect1[0] > rect2[0] { + rect1[0], rect2[0] = rect2[0], rect1[0] + } + + if rect1[2] < rect2[2] { + rect1[2], rect2[2] = rect2[2], rect1[2] + } + + if rect1[1] > rect2[1] { + rect1[1], rect2[1] = rect2[1], rect1[1] + } + + if rect1[3] < rect2[3] { + rect1[3], rect2[3] = rect2[3], rect1[3] + } + hCell, _ := CoordinatesToCellName(rect1[0], rect1[1]) + vCell, _ := CoordinatesToCellName(rect1[2], rect1[3]) + return &xlsxMergeCell{rect: rect1, Ref: hCell + ":" + vCell} +} + +// MergeCell define a merged cell data. +// It consists of the following structure. +// example: []string{"D4:E10", "cell value"} +type MergeCell []string + +// GetCellValue returns merged cell value. +func (m *MergeCell) GetCellValue() string { + return (*m)[1] +} + +// GetStartAxis returns the top left cell coordinates of merged range, for +// example: "C2". +func (m *MergeCell) GetStartAxis() string { + axis := strings.Split((*m)[0], ":") + return axis[0] +} + +// GetEndAxis returns the bottom right cell coordinates of merged range, for +// example: "D4". +func (m *MergeCell) GetEndAxis() string { + axis := strings.Split((*m)[0], ":") + return axis[1] +} diff --git a/vendor/github.com/xuri/excelize/v2/numfmt.go b/vendor/github.com/xuri/excelize/v2/numfmt.go new file mode 100644 index 00000000..6cb7fc74 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/numfmt.go @@ -0,0 +1,953 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "fmt" + "math" + "strconv" + "strings" + "time" + + "github.com/xuri/nfp" +) + +// languageInfo defined the required fields of localization support for number format. +type languageInfo struct { + apFmt string + tags []string + localMonth func(t time.Time, abbr int) string +} + +// numberFormat directly maps the number format parser runtime required +// fields. +type numberFormat struct { + section []nfp.Section + t time.Time + sectionIdx int + isNumeric, hours, seconds bool + number float64 + ap, afterPoint, beforePoint, localCode, result, value, valueSectionType string +} + +var ( + // supportedTokenTypes list the supported number format token types currently. + supportedTokenTypes = []string{ + nfp.TokenSubTypeLanguageInfo, + nfp.TokenTypeColor, + nfp.TokenTypeCurrencyLanguage, + nfp.TokenTypeDateTimes, + nfp.TokenTypeElapsedDateTimes, + nfp.TokenTypeGeneral, + nfp.TokenTypeLiteral, + nfp.TokenTypeTextPlaceHolder, + nfp.TokenTypeZeroPlaceHolder, + } + // supportedLanguageInfo directly maps the supported language ID and tags. + supportedLanguageInfo = map[string]languageInfo{ + "36": {tags: []string{"af"}, localMonth: localMonthsNameAfrikaans, apFmt: apFmtAfrikaans}, + "445": {tags: []string{"bn-IN"}, localMonth: localMonthsNameBangla, apFmt: nfp.AmPm[0]}, + "4": {tags: []string{"zh-Hans"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2]}, + "7804": {tags: []string{"zh"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2]}, + "804": {tags: []string{"zh-CN"}, localMonth: localMonthsNameChinese1, apFmt: nfp.AmPm[2]}, + "1004": {tags: []string{"zh-SG"}, localMonth: localMonthsNameChinese2, apFmt: nfp.AmPm[2]}, + "7C04": {tags: []string{"zh-Hant"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2]}, + "C04": {tags: []string{"zh-HK"}, localMonth: localMonthsNameChinese2, apFmt: nfp.AmPm[2]}, + "1404": {tags: []string{"zh-MO"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2]}, + "404": {tags: []string{"zh-TW"}, localMonth: localMonthsNameChinese3, apFmt: nfp.AmPm[2]}, + "9": {tags: []string{"en"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "1000": {tags: []string{ + "aa", "aa-DJ", "aa-ER", "aa-ER", "aa-NA", "agq", "agq-CM", "ak", "ak-GH", "sq-ML", + "gsw-LI", "gsw-CH", "ar-TD", "ar-KM", "ar-DJ", "ar-ER", "ar-IL", "ar-MR", "ar-PS", + "ar-SO", "ar-SS", "ar-SD", "ar-001", "ast", "ast-ES", "asa", "asa-TZ", "ksf", "ksf-CM", + "bm", "bm-Latn-ML", "bas", "bas-CM", "bem", "bem-ZM", "bez", "bez-TZ", "byn", "byn-ER", + "brx", "brx-IN", "ca-AD", "ca-FR", "ca-IT", "ceb", "ceb-Latn", "ceb-Latn-PH", "tzm-Latn-MA", + "ccp", "ccp-Cakm", "ccp-Cakm-BD", "ccp-Cakm-IN", "ce-RU", "cgg", "cgg-UG", "cu-RU", "swc", + "swc-CD", "kw", "ke-GB", "da-GL", "dua", "dua-CM", "nl-AW", "nl-BQ", "nl-CW", "nl-SX", + "nl-SR", "dz", "ebu", "ebu-KE", "en-AS", "en-AI", "en-AG", "en-AT", "en-BS", "en-BB", + "en-BE", "en-BM", "en-BW", "en-IO", "en-VG", "en-BI", "en-CM", "en-KY", "en-CX", "en-CC", + "en-CK", "en-CY", "en-DK", "en-DM", "en-ER", "en-150", "en-FK", "en-FI", "en-FJ", "en-GM", + "en-DE", "en-GH", "en-GI", "en-GD", "en-GU", "en-GG", "en-GY", "en-IM", "en-IL", "en-JE", + "en-KE", "en-KI", "en-LS", "en-LR", "en-MO", "en-MG", "en-MW", "en-MT", "en-MH", "en-MU", + "en-FM", "en-MS", "en-NA", "en-NR", "en-NL", "en-NG", "en-NU", "en-NF", "en-MP", "en-PK", + "en-PW", "en-PG", "en-PN", "en-PR", "en-RW", "en-KN", "en-LC", "en-VC", "en-WS", "en-SC", + "en-SL", "en-SX", "en-SI", "en-SB", "en-SS", "en-SH", "en-SD", "en-SZ", "en-SE", "en-CH", + "en-TZ", "en-TK", "en-TO", "en-TC", "en-TV", "en-UG", "en-UM", "en-VI", "en-VU", "en-001", + "en-ZM", "eo", "eo-001", "ee", "ee-GH", "ee-TG", "ewo", "ewo-CM", "fo-DK", "fr-DZ", + "fr-BJ", "fr-BF", "fr-BI", "fr-CF", "fr-TD", "fr-KM", "fr-CG", "fr-DJ", "fr-GQ", "fr-GF", + "fr-PF", "fr-GA", "fr-GP", "fr-GN", "fr-MG", "fr-MQ", "fr-MR", "fr-MU", "fr-YT", "fr-NC", + "fr-NE", "fr-RW", "fr-BL", "fr-MF", "fr-PM", "fr-SC", "fr-SY", "fr-TG", "fr-TN", "fr-VU", + "fr-WF", "fur", "fur-IT", "ff-Latn-BF", "ff-CM", "ff-Latn-CM", "ff-Latn-GM", "ff-Latn-GH", + "ff-GN", "ff-Latn-GN", "ff-Latn-GW", "ff-Latn-LR", "ff-MR", "ff-Latn-MR", "ff-Latn-NE", + "ff-Latn-SL", "lg", "lg-UG", "de-BE", "de-IT", "el-CY", "guz", "guz-KE", "ha-Latn-GH", + "ha-Latn-NG", "ia-FR", "ia-001", "it-SM", "it-VA", "jv", "jv-Latn", "jv-Latn-ID", "dyo", + "dyo-SN", "kea", "kea-CV", "kab", "kab-DZ", "kkj", "kkj-CM", "kln", "kln-KE", "kam", + "kam-KE", "ks-Arab-IN", "ki", "ki-KE", "sw-TZ", "sw-UG", "ko-KP", "khq", "khq-ML", "ses", + "ses-ML", "nmg", "nmq-CM", "ku-Arab-IR", "lkt", "lkt-US", "lag", "lag-TZ", "ln", "ln-AO", + "ln-CF", "ln-CD", "nds", "nds-DE", "nds-NL", "lu", "lu-CD", "luo", "luo", "luo-KE", "luy", + "luy-KE", "jmc", "jmc-TZ", "mgh", "mgh-MZ", "kde", "kde-TZ", "mg", "mg-MG", "gv", "gv-IM", + "mas", "mas-KE", "mas-TZ", "mas-IR", "mer", "mer-KE", "mgo", "mgo-CM", "mfe", "mfe-MU", + "mua", "mua-CM", "nqo", "nqo-GN", "nqa", "naq-NA", "nnh", "nnh-CM", "jgo", "jgo-CM", + "lrc-IQ", "lrc-IR", "nd", "nd-ZW", "nb-SJ", "nus", "nus-SD", "nus-SS", "nyn", "nyn-UG", + "om-KE", "os", "os-GE", "os-RU", "ps-PK", "fa-AF", "pt-AO", "pt-CV", "pt-GQ", "pt-GW", + "pt-LU", "pt-MO", "pt-MZ", "pt-ST", "pt-CH", "pt-TL", "prg-001", "ksh", "ksh-DE", "rof", + "rof-TZ", "rn", "rn-BI", "ru-BY", "ru-KZ", "ru-KG", "ru-UA", "rwk", "rwk-TZ", "ssy", + "ssy-ER", "saq", "saq-KE", "sg", "sq-CF", "sbp", "sbp-TZ", "seh", "seh-MZ", "ksb", "ksb-TZ", + "sn", "sn-Latn", "sn-Latn-ZW", "xog", "xog-UG", "so-DJ", "so-ET", "so-KE", "nr", "nr-ZA", + "st-LS", "es-BZ", "es-BR", "es-PH", "zgh", "zgh-Tfng-MA", "zgh-Tfng", "ss", "ss-ZA", + "ss-SZ", "sv-AX", "shi", "shi-Tfng", "shi-Tfng-MA", "shi-Latn", "shi-Latn-MA", "dav", + "dav-KE", "ta-MY", "ta-SG", "twq", "twq-NE", "teo", "teo-KE", "teo-UG", "bo-IN", "tig", + "tig-ER", "to", "to-TO", "tr-CY", "uz-Arab", "us-Arab-AF", "vai", "vai-Vaii", + "vai-Vaii-LR", "vai-Latn-LR", "vai-Latn", "vo", "vo-001", "vun", "vun-TZ", "wae", + "wae-CH", "wal", "wae-ET", "yav", "yav-CM", "yo-BJ", "dje", "dje-NE", + }, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "C09": {tags: []string{"en-AU"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0])}, + "2829": {tags: []string{"en-BZ"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "1009": {tags: []string{"en-CA"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "2409": {tags: []string{"en-029"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "3C09": {tags: []string{"en-HK"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "4009": {tags: []string{"en-IN"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "1809": {tags: []string{"en-IE"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0])}, + "2009": {tags: []string{"en-JM"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "4409": {tags: []string{"en-MY"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "1409": {tags: []string{"en-NZ"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "3409": {tags: []string{"en-PH"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "4809": {tags: []string{"en-SG"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "1C09": {tags: []string{"en-ZA"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "2C09": {tags: []string{"en-TT"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "4C09": {tags: []string{"en-AE"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "809": {tags: []string{"en-GB"}, localMonth: localMonthsNameEnglish, apFmt: strings.ToLower(nfp.AmPm[0])}, + "409": {tags: []string{"en-US"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "3009": {tags: []string{"en-ZW"}, localMonth: localMonthsNameEnglish, apFmt: nfp.AmPm[0]}, + "C": {tags: []string{"fr"}, localMonth: localMonthsNameFrench, apFmt: nfp.AmPm[0]}, + "7": {tags: []string{"de"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0]}, + "C07": {tags: []string{"de-AT"}, localMonth: localMonthsNameAustria, apFmt: nfp.AmPm[0]}, + "407": {tags: []string{"de-DE"}, localMonth: localMonthsNameGerman, apFmt: nfp.AmPm[0]}, + "3C": {tags: []string{"ga"}, localMonth: localMonthsNameIrish, apFmt: apFmtIrish}, + "83C": {tags: []string{"ga-IE"}, localMonth: localMonthsNameIrish, apFmt: apFmtIrish}, + "10": {tags: []string{"it"}, localMonth: localMonthsNameItalian, apFmt: nfp.AmPm[0]}, + "11": {tags: []string{"ja"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese}, + "411": {tags: []string{"ja-JP"}, localMonth: localMonthsNameChinese3, apFmt: apFmtJapanese}, + "12": {tags: []string{"ko"}, localMonth: localMonthsNameKorean, apFmt: apFmtKorean}, + "412": {tags: []string{"ko-KR"}, localMonth: localMonthsNameKorean, apFmt: apFmtKorean}, + "7C50": {tags: []string{"mn-Mong"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0]}, + "850": {tags: []string{"mn-Mong-CN"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0]}, + "C50": {tags: []string{"mn-Mong-MN"}, localMonth: localMonthsNameTraditionalMongolian, apFmt: nfp.AmPm[0]}, + "19": {tags: []string{"ru"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0]}, + "819": {tags: []string{"ru-MD"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0]}, + "419": {tags: []string{"ru-RU"}, localMonth: localMonthsNameRussian, apFmt: nfp.AmPm[0]}, + "A": {tags: []string{"es"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "2C0A": {tags: []string{"es-AR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "200A": {tags: []string{"es-VE"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "400A": {tags: []string{"es-BO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "340A": {tags: []string{"es-CL"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "240A": {tags: []string{"es-CO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "140A": {tags: []string{"es-CR"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "5C0A": {tags: []string{"es-CU"}, localMonth: localMonthsNameSpanish, apFmt: apFmtCuba}, + "1C0A": {tags: []string{"es-DO"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "300A": {tags: []string{"es-EC"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "440A": {tags: []string{"es-SV"}, localMonth: localMonthsNameSpanish, apFmt: apFmtSpanish}, + "1E": {tags: []string{"th"}, localMonth: localMonthsNameThai, apFmt: nfp.AmPm[0]}, + "41E": {tags: []string{"th-TH"}, localMonth: localMonthsNameThai, apFmt: nfp.AmPm[0]}, + "51": {tags: []string{"bo"}, localMonth: localMonthsNameTibetan, apFmt: apFmtTibetan}, + "451": {tags: []string{"bo-CN"}, localMonth: localMonthsNameTibetan, apFmt: apFmtTibetan}, + "1F": {tags: []string{"tr"}, localMonth: localMonthsNameTurkish, apFmt: apFmtTurkish}, + "41F": {tags: []string{"tr-TR"}, localMonth: localMonthsNameTurkish, apFmt: apFmtTurkish}, + "52": {tags: []string{"cy"}, localMonth: localMonthsNameWelsh, apFmt: apFmtWelsh}, + "452": {tags: []string{"cy-GB"}, localMonth: localMonthsNameWelsh, apFmt: apFmtWelsh}, + "2A": {tags: []string{"vi"}, localMonth: localMonthsNameVietnamese, apFmt: apFmtVietnamese}, + "42A": {tags: []string{"vi-VN"}, localMonth: localMonthsNameVietnamese, apFmt: apFmtVietnamese}, + "88": {tags: []string{"wo"}, localMonth: localMonthsNameWolof, apFmt: apFmtWolof}, + "488": {tags: []string{"wo-SN"}, localMonth: localMonthsNameWolof, apFmt: apFmtWolof}, + "34": {tags: []string{"xh"}, localMonth: localMonthsNameXhosa, apFmt: nfp.AmPm[0]}, + "434": {tags: []string{"xh-ZA"}, localMonth: localMonthsNameXhosa, apFmt: nfp.AmPm[0]}, + "78": {tags: []string{"ii"}, localMonth: localMonthsNameYi, apFmt: apFmtYi}, + "478": {tags: []string{"ii-CN"}, localMonth: localMonthsNameYi, apFmt: apFmtYi}, + "35": {tags: []string{"zu"}, localMonth: localMonthsNameZulu, apFmt: nfp.AmPm[0]}, + "435": {tags: []string{"zu-ZA"}, localMonth: localMonthsNameZulu, apFmt: nfp.AmPm[0]}, + } + // monthNamesBangla list the month names in the Bangla. + monthNamesBangla = []string{ + "\u099C\u09BE\u09A8\u09C1\u09AF\u09BC\u09BE\u09B0\u09C0", + "\u09AB\u09C7\u09AC\u09CD\u09B0\u09C1\u09AF\u09BC\u09BE\u09B0\u09C0", + "\u09AE\u09BE\u09B0\u09CD\u099A", + "\u098F\u09AA\u09CD\u09B0\u09BF\u09B2", + "\u09AE\u09C7", + "\u099C\u09C1\u09A8", + "\u099C\u09C1\u09B2\u09BE\u0987", + "\u0986\u0997\u09B8\u09CD\u099F", + "\u09B8\u09C7\u09AA\u09CD\u099F\u09C7\u09AE\u09CD\u09AC\u09B0", + "\u0985\u0995\u09CD\u099F\u09CB\u09AC\u09B0", + "\u09A8\u09AD\u09C7\u09AE\u09CD\u09AC\u09B0", + "\u09A1\u09BF\u09B8\u09C7\u09AE\u09CD\u09AC\u09B0", + } + // monthNamesAfrikaans list the month names in the Afrikaans. + monthNamesAfrikaans = []string{"Januarie", "Februarie", "Maart", "April", "Mei", "Junie", "Julie", "Augustus", "September", "Oktober", "November", "Desember"} + // monthNamesChinese list the month names in the Chinese. + monthNamesChinese = []string{"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"} + // monthNamesFrench list the month names in the French. + monthNamesFrench = []string{"janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"} + // monthNamesGerman list the month names in the German. + monthNamesGerman = []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"} + // monthNamesAustria list the month names in the Austria. + monthNamesAustria = []string{"Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"} + // monthNamesIrish list the month names in the Irish. + monthNamesIrish = []string{"Eanáir", "Feabhra", "Márta", "Aibreán", "Bealtaine", "Meitheamh", "Iúil", "Lúnasa", "Meán Fómhair", "Deireadh Fómhair", "Samhain", "Nollaig"} + // monthNamesItalian list the month names in the Italian. + monthNamesItalian = []string{"gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre"} + // monthNamesRussian list the month names in the Russian. + monthNamesRussian = []string{"январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"} + // monthNamesSpanish list the month names in the Spanish. + monthNamesSpanish = []string{"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"} + // monthNamesThai list the month names in the Thai. + monthNamesThai = []string{ + "\u0e21\u0e01\u0e23\u0e32\u0e04\u0e21", + "\u0e01\u0e38\u0e21\u0e20\u0e32\u0e1e\u0e31\u0e19\u0e18\u0e4c", + "\u0e21\u0e35\u0e19\u0e32\u0e04\u0e21", + "\u0e40\u0e21\u0e29\u0e32\u0e22\u0e19", + "\u0e1e\u0e24\u0e29\u0e20\u0e32\u0e04\u0e21", + "\u0e21\u0e34\u0e16\u0e38\u0e19\u0e32\u0e22\u0e19", + "\u0e01\u0e23\u0e01\u0e0e\u0e32\u0e04\u0e21", + "\u0e2a\u0e34\u0e07\u0e2b\u0e32\u0e04\u0e21", + "\u0e01\u0e31\u0e19\u0e22\u0e32\u0e22\u0e19", + "\u0e15\u0e38\u0e25\u0e32\u0e04\u0e21", + "\u0e1e\u0e24\u0e28\u0e08\u0e34\u0e01\u0e32\u0e22\u0e19", + "\u0e18\u0e31\u0e19\u0e27\u0e32\u0e04\u0e21", + } + // monthNamesTibetan list the month names in the Tibetan. + monthNamesTibetan = []string{ + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f44\u0f0b\u0f54\u0f7c\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f42\u0f66\u0f74\u0f58\u0f0b\u0f54\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f5e\u0f72\u0f0b\u0f54\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f63\u0f94\u0f0b\u0f54\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0fb2\u0f74\u0f42\u0f0b\u0f54\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f51\u0f74\u0f53\u0f0b\u0f54\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f62\u0f92\u0fb1\u0f51\u0f0b\u0f54\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f51\u0f42\u0f74\u0f0b\u0f54\u0f0b", + "\u0f66\u0fa4\u0fb1\u0f72\u0f0b\u0f5f\u0fb3\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f54\u0f0d", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f45\u0f72\u0f42\u0f0b\u0f54\u0f0b", + "\u0f5f\u0fb3\u0f0b\u0f56\u0f0b\u0f56\u0f45\u0f74\u0f0b\u0f42\u0f49\u0f72\u0f66\u0f0b\u0f54\u0f0b", + } + // monthNamesTurkish list the month names in the Turkish. + monthNamesTurkish = []string{"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"} + // monthNamesWelsh list the month names in the Welsh. + monthNamesWelsh = []string{"Ionawr", "Chwefror", "Mawrth", "Ebrill", "Mai", "Mehefin", "Gorffennaf", "Awst", "Medi", "Hydref", "Tachwedd", "Rhagfyr"} + // monthNamesWolof list the month names in the Wolof. + monthNamesWolof = []string{"Samwiye", "Fewriye", "Maars", "Awril", "Me", "Suwe", "Sullet", "Ut", "Septàmbar", "Oktoobar", "Noowàmbar", "Desàmbar"} + // monthNamesXhosa list the month names in the Xhosa. + monthNamesXhosa = []string{"Januwari", "Febuwari", "Matshi", "Aprili", "Meyi", "Juni", "Julayi", "Agasti", "Septemba", "Oktobha", "Novemba", "Disemba"} + // monthNamesYi list the month names in the Yi. + monthNamesYi = []string{"\ua2cd", "\ua44d", "\ua315", "\ua1d6", "\ua26c", "\ua0d8", "\ua3c3", "\ua246", "\ua22c", "\ua2b0", "\ua2b0\ua2aa", "\ua2b0\ua44b"} + // monthNamesZulu list the month names in the Zulu. + monthNamesZulu = []string{"Januwari", "Febhuwari", "Mashi", "Ephreli", "Meyi", "Juni", "Julayi", "Agasti", "Septemba", "Okthoba", "Novemba", "Disemba"} + // apFmtAfrikaans defined the AM/PM name in the Afrikaans. + apFmtAfrikaans = "vm./nm." + // apFmtCuba defined the AM/PM name in the Cuba. + apFmtCuba = "a.m./p.m." + // apFmtIrish defined the AM/PM name in the Irish. + apFmtIrish = "r.n./i.n." + // apFmtJapanese defined the AM/PM name in the Japanese. + apFmtJapanese = "午前/午後" + // apFmtKorean defined the AM/PM name in the Korean. + apFmtKorean = "오전/오후" + // apFmtSpanish defined the AM/PM name in the Spanish. + apFmtSpanish = "a. m./p. m." + // apFmtTibetan defined the AM/PM name in the Tibetan. + apFmtTibetan = "\u0f66\u0f94\u0f0b\u0f51\u0fb2\u0f7c\u0f0b/\u0f55\u0fb1\u0f72\u0f0b\u0f51\u0fb2\u0f7c\u0f0b" + // apFmtTurkish defined the AM/PM name in the Turkish. + apFmtTurkish = "\u00F6\u00F6/\u00F6\u0053" + // apFmtVietnamese defined the AM/PM name in the Vietnamese. + apFmtVietnamese = "SA/CH" + // apFmtWolof defined the AM/PM name in the Wolof. + apFmtWolof = "Sub/Ngo" + // apFmtYi defined the AM/PM name in the Yi. + apFmtYi = "\ua3b8\ua111/\ua06f\ua2d2" + // apFmtWelsh defined the AM/PM name in the Welsh. + apFmtWelsh = "yb/yh" +) + +// prepareNumberic split the number into two before and after parts by a +// decimal point. +func (nf *numberFormat) prepareNumberic(value string) { + if nf.isNumeric, _ = isNumeric(value); !nf.isNumeric { + return + } +} + +// format provides a function to return a string parse by number format +// expression. If the given number format is not supported, this will return +// the original cell value. +func format(value, numFmt string) string { + p := nfp.NumberFormatParser() + nf := numberFormat{section: p.Parse(numFmt), value: value} + nf.number, nf.valueSectionType = nf.getValueSectionType(value) + nf.prepareNumberic(value) + for i, section := range nf.section { + nf.sectionIdx = i + if section.Type != nf.valueSectionType { + continue + } + if nf.isNumeric { + switch section.Type { + case nfp.TokenSectionPositive: + return nf.positiveHandler() + case nfp.TokenSectionNegative: + return nf.negativeHandler() + default: + return nf.zeroHandler() + } + } + return nf.textHandler() + } + return value +} + +// positiveHandler will be handling positive selection for a number format +// expression. +func (nf *numberFormat) positiveHandler() (result string) { + nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, false), false, false + for i, token := range nf.section[nf.sectionIdx].Items { + if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral { + result = nf.value + return + } + if token.TType == nfp.TokenTypeCurrencyLanguage { + if err := nf.currencyLanguageHandler(i, token); err != nil { + result = nf.value + return + } + } + if token.TType == nfp.TokenTypeDateTimes { + nf.dateTimesHandler(i, token) + } + if token.TType == nfp.TokenTypeElapsedDateTimes { + nf.elapsedDateTimesHandler(token) + } + if token.TType == nfp.TokenTypeLiteral { + nf.result += token.TValue + continue + } + if token.TType == nfp.TokenTypeZeroPlaceHolder && token.TValue == "0" { + if isNum, precision := isNumeric(nf.value); isNum { + if nf.number < 1 { + nf.result += "0" + continue + } + if precision > 15 { + nf.result += roundPrecision(nf.value, 15) + } else { + nf.result += fmt.Sprintf("%.f", nf.number) + } + continue + } + } + } + result = nf.result + return +} + +// currencyLanguageHandler will be handling currency and language types tokens for a number +// format expression. +func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (err error) { + for _, part := range token.Parts { + if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 { + err = ErrUnsupportedNumberFormat + return + } + if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok { + err = ErrUnsupportedNumberFormat + return + } + nf.localCode = strings.ToUpper(part.Token.TValue) + } + return +} + +// localAmPm return AM/PM name by supported language ID. +func (nf *numberFormat) localAmPm(ap string) string { + if languageInfo, ok := supportedLanguageInfo[nf.localCode]; ok { + return languageInfo.apFmt + } + return ap +} + +// localMonthsNameEnglish returns the English name of the month. +func localMonthsNameEnglish(t time.Time, abbr int) string { + if abbr == 3 { + return t.Month().String()[:3] + } + if abbr == 4 { + return t.Month().String() + } + return t.Month().String()[:1] +} + +// localMonthsNameAfrikaans returns the Afrikaans name of the month. +func localMonthsNameAfrikaans(t time.Time, abbr int) string { + if abbr == 3 { + month := monthNamesAfrikaans[int(t.Month())-1] + if len([]rune(month)) <= 3 { + return month + } + return string([]rune(month)[:3]) + "." + } + if abbr == 4 { + return monthNamesAfrikaans[int(t.Month())-1] + } + return monthNamesAfrikaans[int(t.Month())-1][:1] +} + +// localMonthsNameAustria returns the Austria name of the month. +func localMonthsNameAustria(t time.Time, abbr int) string { + if abbr == 3 { + return string([]rune(monthNamesAustria[int(t.Month())-1])[:3]) + } + if abbr == 4 { + return monthNamesAustria[int(t.Month())-1] + } + return monthNamesAustria[int(t.Month())-1][:1] +} + +// localMonthsNameBangla returns the German name of the month. +func localMonthsNameBangla(t time.Time, abbr int) string { + if abbr == 3 || abbr == 4 { + return monthNamesBangla[int(t.Month())-1] + } + return string([]rune(monthNamesBangla[int(t.Month())-1])[:1]) +} + +// localMonthsNameFrench returns the French name of the month. +func localMonthsNameFrench(t time.Time, abbr int) string { + if abbr == 3 { + month := monthNamesFrench[int(t.Month())-1] + if len([]rune(month)) <= 4 { + return month + } + return string([]rune(month)[:4]) + "." + } + if abbr == 4 { + return monthNamesFrench[int(t.Month())-1] + } + return monthNamesFrench[int(t.Month())-1][:1] +} + +// localMonthsNameIrish returns the Irish name of the month. +func localMonthsNameIrish(t time.Time, abbr int) string { + if abbr == 3 { + switch int(t.Month()) { + case 1, 4, 8: + return string([]rune(monthNamesIrish[int(t.Month())-1])[:3]) + case 2, 3, 6: + return string([]rune(monthNamesIrish[int(t.Month())-1])[:5]) + case 9, 10: + return string([]rune(monthNamesIrish[int(t.Month())-1])[:1]) + "Fómh" + default: + return string([]rune(monthNamesIrish[int(t.Month())-1])[:4]) + } + } + if abbr == 4 { + return monthNamesIrish[int(t.Month())-1] + } + return string([]rune(monthNamesIrish[int(t.Month())-1])[:1]) +} + +// localMonthsNameItalian returns the Italian name of the month. +func localMonthsNameItalian(t time.Time, abbr int) string { + if abbr == 3 { + return monthNamesItalian[int(t.Month())-1][:3] + } + if abbr == 4 { + return monthNamesItalian[int(t.Month())-1] + } + return monthNamesItalian[int(t.Month())-1][:1] +} + +// localMonthsNameGerman returns the German name of the month. +func localMonthsNameGerman(t time.Time, abbr int) string { + if abbr == 3 { + return string([]rune(monthNamesGerman[int(t.Month())-1])[:3]) + } + if abbr == 4 { + return monthNamesGerman[int(t.Month())-1] + } + return string([]rune(monthNamesGerman[int(t.Month())-1])[:1]) +} + +// localMonthsNameChinese1 returns the Chinese name of the month. +func localMonthsNameChinese1(t time.Time, abbr int) string { + if abbr == 3 { + return strconv.Itoa(int(t.Month())) + "月" + } + if abbr == 4 { + return monthNamesChinese[int(t.Month())-1] + "月" + } + return monthNamesChinese[int(t.Month())-1] +} + +// localMonthsNameChinese2 returns the Chinese name of the month. +func localMonthsNameChinese2(t time.Time, abbr int) string { + if abbr == 3 || abbr == 4 { + return monthNamesChinese[int(t.Month())-1] + "月" + } + return monthNamesChinese[int(t.Month())-1] +} + +// localMonthsNameChinese3 returns the Chinese name of the month. +func localMonthsNameChinese3(t time.Time, abbr int) string { + if abbr == 3 || abbr == 4 { + return strconv.Itoa(int(t.Month())) + "月" + } + return strconv.Itoa(int(t.Month())) +} + +// localMonthsNameKorean returns the Korean name of the month. +func localMonthsNameKorean(t time.Time, abbr int) string { + if abbr == 3 || abbr == 4 { + return strconv.Itoa(int(t.Month())) + "월" + } + return strconv.Itoa(int(t.Month())) +} + +// localMonthsNameTraditionalMongolian returns the Traditional Mongolian name of the month. +func localMonthsNameTraditionalMongolian(t time.Time, abbr int) string { + if abbr == 5 { + return "M" + } + return fmt.Sprintf("M%02d", int(t.Month())) +} + +// localMonthsNameRussian returns the Russian name of the month. +func localMonthsNameRussian(t time.Time, abbr int) string { + if abbr == 3 { + month := monthNamesRussian[int(t.Month())-1] + if len([]rune(month)) <= 4 { + return month + } + return string([]rune(month)[:3]) + "." + } + if abbr == 4 { + return monthNamesRussian[int(t.Month())-1] + } + return string([]rune(monthNamesRussian[int(t.Month())-1])[:1]) +} + +// localMonthsNameSpanish returns the Spanish name of the month. +func localMonthsNameSpanish(t time.Time, abbr int) string { + if abbr == 3 { + return monthNamesSpanish[int(t.Month())-1][:3] + } + if abbr == 4 { + return monthNamesSpanish[int(t.Month())-1] + } + return monthNamesSpanish[int(t.Month())-1][:1] +} + +// localMonthsNameThai returns the Thai name of the month. +func localMonthsNameThai(t time.Time, abbr int) string { + if abbr == 3 { + r := []rune(monthNamesThai[int(t.Month())-1]) + return string(r[:1]) + "." + string(r[len(r)-2:len(r)-1]) + "." + } + if abbr == 4 { + return monthNamesThai[int(t.Month())-1] + } + return string([]rune(monthNamesThai[int(t.Month())-1])[:1]) +} + +// localMonthsNameTibetan returns the Tibetan name of the month. +func localMonthsNameTibetan(t time.Time, abbr int) string { + if abbr == 3 { + return "\u0f5f\u0fb3\u0f0b" + []string{"\u0f21", "\u0f22", "\u0f23", "\u0f24", "\u0f25", "\u0f26", "\u0f27", "\u0f28", "\u0f29", "\u0f21\u0f20", "\u0f21\u0f21", "\u0f21\u0f22"}[int(t.Month())-1] + } + if abbr == 5 { + if t.Month() == 10 { + return "\u0f66" + } + return "\u0f5f" + } + return monthNamesTibetan[int(t.Month())-1] +} + +// localMonthsNameTurkish returns the Turkish name of the month. +func localMonthsNameTurkish(t time.Time, abbr int) string { + if abbr == 3 { + return string([]rune(monthNamesTurkish[int(t.Month())-1])[:3]) + } + if abbr == 4 { + return monthNamesTurkish[int(t.Month())-1] + } + return string([]rune(monthNamesTurkish[int(t.Month())-1])[:1]) +} + +// localMonthsNameWelsh returns the Welsh name of the month. +func localMonthsNameWelsh(t time.Time, abbr int) string { + if abbr == 3 { + switch int(t.Month()) { + case 2, 7: + return string([]rune(monthNamesWelsh[int(t.Month())-1])[:5]) + case 8, 9, 11, 12: + return string([]rune(monthNamesWelsh[int(t.Month())-1])[:4]) + default: + return string([]rune(monthNamesWelsh[int(t.Month())-1])[:3]) + } + } + if abbr == 4 { + return monthNamesWelsh[int(t.Month())-1] + } + return string([]rune(monthNamesWelsh[int(t.Month())-1])[:1]) +} + +// localMonthsNameVietnamese returns the Vietnamese name of the month. +func localMonthsNameVietnamese(t time.Time, abbr int) string { + if abbr == 3 { + return "Thg " + strconv.Itoa(int(t.Month())) + } + if abbr == 5 { + return "T " + strconv.Itoa(int(t.Month())) + } + return "Tháng " + strconv.Itoa(int(t.Month())) +} + +// localMonthsNameWolof returns the Wolof name of the month. +func localMonthsNameWolof(t time.Time, abbr int) string { + if abbr == 3 { + switch int(t.Month()) { + case 3, 6: + return string([]rune(monthNamesWolof[int(t.Month())-1])[:3]) + case 5, 8: + return string([]rune(monthNamesWolof[int(t.Month())-1])[:2]) + case 9: + return string([]rune(monthNamesWolof[int(t.Month())-1])[:4]) + "." + case 11: + return "Now." + default: + return string([]rune(monthNamesWolof[int(t.Month())-1])[:3]) + "." + } + } + if abbr == 4 { + return monthNamesWolof[int(t.Month())-1] + } + return string([]rune(monthNamesWolof[int(t.Month())-1])[:1]) +} + +// localMonthsNameXhosa returns the Xhosa name of the month. +func localMonthsNameXhosa(t time.Time, abbr int) string { + if abbr == 3 { + switch int(t.Month()) { + case 4: + return "uEpr." + case 8: + return "u" + string([]rune(monthNamesXhosa[int(t.Month())-1])[:2]) + "." + default: + return "u" + string([]rune(monthNamesXhosa[int(t.Month())-1])[:3]) + "." + } + } + if abbr == 4 { + return "u" + monthNamesXhosa[int(t.Month())-1] + } + return "u" +} + +// localMonthsNameYi returns the Yi name of the month. +func localMonthsNameYi(t time.Time, abbr int) string { + if abbr == 3 || abbr == 4 { + return string(monthNamesYi[int(t.Month())-1]) + "\ua1aa" + } + return string([]rune(monthNamesYi[int(t.Month())-1])[:1]) +} + +// localMonthsNameZulu returns the Zulu name of the month. +func localMonthsNameZulu(t time.Time, abbr int) string { + if abbr == 3 { + if int(t.Month()) == 8 { + return string([]rune(monthNamesZulu[int(t.Month())-1])[:4]) + } + return string([]rune(monthNamesZulu[int(t.Month())-1])[:3]) + } + if abbr == 4 { + return monthNamesZulu[int(t.Month())-1] + } + return string([]rune(monthNamesZulu[int(t.Month())-1])[:1]) +} + +// localMonthName return months name by supported language ID. +func (nf *numberFormat) localMonthsName(abbr int) string { + if languageInfo, ok := supportedLanguageInfo[nf.localCode]; ok { + return languageInfo.localMonth(nf.t, abbr) + } + return localMonthsNameEnglish(nf.t, abbr) +} + +// dateTimesHandler will be handling date and times types tokens for a number +// format expression. +func (nf *numberFormat) dateTimesHandler(i int, token nfp.Token) { + if idx := inStrSlice(nfp.AmPm, strings.ToUpper(token.TValue), false); idx != -1 { + if nf.ap == "" { + nextHours := nf.hoursNext(i) + aps := strings.Split(nf.localAmPm(token.TValue), "/") + nf.ap = aps[0] + if nextHours > 12 { + nf.ap = aps[1] + } + } + nf.result += nf.ap + return + } + if strings.Contains(strings.ToUpper(token.TValue), "M") { + l := len(token.TValue) + if l == 1 && !nf.hours && !nf.secondsNext(i) { + nf.result += strconv.Itoa(int(nf.t.Month())) + return + } + if l == 2 && !nf.hours && !nf.secondsNext(i) { + nf.result += fmt.Sprintf("%02d", int(nf.t.Month())) + return + } + if l == 3 { + nf.result += nf.localMonthsName(3) + return + } + if l == 4 || l > 5 { + nf.result += nf.localMonthsName(4) + return + } + if l == 5 { + nf.result += nf.localMonthsName(5) + return + } + } + nf.yearsHandler(i, token) + nf.daysHandler(i, token) + nf.hoursHandler(i, token) + nf.minutesHandler(token) + nf.secondsHandler(token) +} + +// yearsHandler will be handling years in the date and times types tokens for a +// number format expression. +func (nf *numberFormat) yearsHandler(i int, token nfp.Token) { + years := strings.Contains(strings.ToUpper(token.TValue), "Y") + if years && len(token.TValue) <= 2 { + nf.result += strconv.Itoa(nf.t.Year())[2:] + return + } + if years && len(token.TValue) > 2 { + nf.result += strconv.Itoa(nf.t.Year()) + return + } +} + +// daysHandler will be handling days in the date and times types tokens for a +// number format expression. +func (nf *numberFormat) daysHandler(i int, token nfp.Token) { + if strings.Contains(strings.ToUpper(token.TValue), "D") { + switch len(token.TValue) { + case 1: + nf.result += strconv.Itoa(nf.t.Day()) + return + case 2: + nf.result += fmt.Sprintf("%02d", nf.t.Day()) + return + case 3: + nf.result += nf.t.Weekday().String()[:3] + return + default: + nf.result += nf.t.Weekday().String() + return + } + } +} + +// hoursHandler will be handling hours in the date and times types tokens for a +// number format expression. +func (nf *numberFormat) hoursHandler(i int, token nfp.Token) { + nf.hours = strings.Contains(strings.ToUpper(token.TValue), "H") + if nf.hours { + h := nf.t.Hour() + ap, ok := nf.apNext(i) + if ok { + nf.ap = ap[0] + if h > 12 { + h -= 12 + nf.ap = ap[1] + } + } + if nf.ap != "" && nf.hoursNext(i) == -1 && h > 12 { + h -= 12 + } + switch len(token.TValue) { + case 1: + nf.result += strconv.Itoa(h) + return + default: + nf.result += fmt.Sprintf("%02d", h) + return + } + } +} + +// minutesHandler will be handling minutes in the date and times types tokens +// for a number format expression. +func (nf *numberFormat) minutesHandler(token nfp.Token) { + if strings.Contains(strings.ToUpper(token.TValue), "M") { + nf.hours = false + switch len(token.TValue) { + case 1: + nf.result += strconv.Itoa(nf.t.Minute()) + return + default: + nf.result += fmt.Sprintf("%02d", nf.t.Minute()) + return + } + } +} + +// secondsHandler will be handling seconds in the date and times types tokens +// for a number format expression. +func (nf *numberFormat) secondsHandler(token nfp.Token) { + nf.seconds = strings.Contains(strings.ToUpper(token.TValue), "S") + if nf.seconds { + switch len(token.TValue) { + case 1: + nf.result += strconv.Itoa(nf.t.Second()) + return + default: + nf.result += fmt.Sprintf("%02d", nf.t.Second()) + return + } + } +} + +// elapsedDateTimesHandler will be handling elapsed date and times types tokens +// for a number format expression. +func (nf *numberFormat) elapsedDateTimesHandler(token nfp.Token) { + if strings.Contains(strings.ToUpper(token.TValue), "H") { + nf.result += fmt.Sprintf("%.f", nf.t.Sub(excel1900Epoc).Hours()) + return + } + if strings.Contains(strings.ToUpper(token.TValue), "M") { + nf.result += fmt.Sprintf("%.f", nf.t.Sub(excel1900Epoc).Minutes()) + return + } + if strings.Contains(strings.ToUpper(token.TValue), "S") { + nf.result += fmt.Sprintf("%.f", nf.t.Sub(excel1900Epoc).Seconds()) + return + } +} + +// hoursNext detects if a token of type hours exists after a given tokens list. +func (nf *numberFormat) hoursNext(i int) int { + tokens := nf.section[nf.sectionIdx].Items + for idx := i + 1; idx < len(tokens); idx++ { + if tokens[idx].TType == nfp.TokenTypeDateTimes { + if strings.Contains(strings.ToUpper(tokens[idx].TValue), "H") { + t := timeFromExcelTime(nf.number, false) + return t.Hour() + } + } + } + return -1 +} + +// apNext detects if a token of type AM/PM exists after a given tokens list. +func (nf *numberFormat) apNext(i int) ([]string, bool) { + tokens := nf.section[nf.sectionIdx].Items + for idx := i + 1; idx < len(tokens); idx++ { + if tokens[idx].TType == nfp.TokenTypeDateTimes { + if strings.Contains(strings.ToUpper(tokens[idx].TValue), "H") { + return nil, false + } + if i := inStrSlice(nfp.AmPm, tokens[idx].TValue, false); i != -1 { + return strings.Split(nf.localAmPm(tokens[idx].TValue), "/"), true + } + } + } + return nil, false +} + +// secondsNext detects if a token of type seconds exists after a given tokens +// list. +func (nf *numberFormat) secondsNext(i int) bool { + tokens := nf.section[nf.sectionIdx].Items + for idx := i + 1; idx < len(tokens); idx++ { + if tokens[idx].TType == nfp.TokenTypeDateTimes { + return strings.Contains(strings.ToUpper(tokens[idx].TValue), "S") + } + } + return false +} + +// negativeHandler will be handling negative selection for a number format +// expression. +func (nf *numberFormat) negativeHandler() (result string) { + for _, token := range nf.section[nf.sectionIdx].Items { + if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral { + result = nf.value + return + } + if token.TType == nfp.TokenTypeLiteral { + nf.result += token.TValue + continue + } + if token.TType == nfp.TokenTypeZeroPlaceHolder && token.TValue == "0" { + if isNum, precision := isNumeric(nf.value); isNum { + if math.Abs(nf.number) < 1 { + nf.result += "0" + continue + } + if precision > 15 { + nf.result += strings.TrimLeft(roundPrecision(nf.value, 15), "-") + } else { + nf.result += fmt.Sprintf("%.f", math.Abs(nf.number)) + } + continue + } + } + } + result = nf.result + return +} + +// zeroHandler will be handling zero selection for a number format expression. +func (nf *numberFormat) zeroHandler() string { + return nf.value +} + +// textHandler will be handling text selection for a number format expression. +func (nf *numberFormat) textHandler() (result string) { + for _, token := range nf.section[nf.sectionIdx].Items { + if token.TType == nfp.TokenTypeLiteral { + result += token.TValue + } + if token.TType == nfp.TokenTypeTextPlaceHolder { + result += nf.value + } + } + return result +} + +// getValueSectionType returns its applicable number format expression section +// based on the given value. +func (nf *numberFormat) getValueSectionType(value string) (float64, string) { + number, err := strconv.ParseFloat(value, 64) + if err != nil { + return number, nfp.TokenSectionText + } + if number > 0 { + return number, nfp.TokenSectionPositive + } + if number < 0 { + return number, nfp.TokenSectionNegative + } + return number, nfp.TokenSectionZero +} diff --git a/vendor/github.com/xuri/excelize/v2/picture.go b/vendor/github.com/xuri/excelize/v2/picture.go new file mode 100644 index 00000000..515f15f9 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/picture.go @@ -0,0 +1,701 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" + "image" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "strconv" + "strings" +) + +// parseFormatPictureSet provides a function to parse the format settings of +// the picture with default value. +func parseFormatPictureSet(formatSet string) (*formatPicture, error) { + format := formatPicture{ + FPrintsWithSheet: true, + FLocksWithSheet: false, + NoChangeAspect: false, + Autofit: false, + OffsetX: 0, + OffsetY: 0, + XScale: 1.0, + YScale: 1.0, + } + err := json.Unmarshal(parseFormatSet(formatSet), &format) + return &format, err +} + +// AddPicture provides the method to add picture in a sheet by given picture +// format set (such as offset, scale, aspect ratio setting and print settings) +// and file path. For example: +// +// package main +// +// import ( +// _ "image/gif" +// _ "image/jpeg" +// _ "image/png" +// +// "github.com/xuri/excelize/v2" +// ) +// +// func main() { +// f := excelize.NewFile() +// // Insert a picture. +// if err := f.AddPicture("Sheet1", "A2", "image.jpg", ""); err != nil { +// fmt.Println(err) +// } +// // Insert a picture scaling in the cell with location hyperlink. +// if err := f.AddPicture("Sheet1", "D2", "image.png", `{"x_scale": 0.5, "y_scale": 0.5, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`); err != nil { +// fmt.Println(err) +// } +// // Insert a picture offset in the cell with external hyperlink, printing and positioning support. +// if err := f.AddPicture("Sheet1", "H2", "image.gif", `{"x_offset": 15, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "print_obj": true, "lock_aspect_ratio": false, "locked": false, "positioning": "oneCell"}`); err != nil { +// fmt.Println(err) +// } +// if err := f.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// } +// +// The optional parameter "autofit" specifies if you make image size auto-fits the +// cell, the default value of that is 'false'. +// +// The optional parameter "hyperlink" specifies the hyperlink of the image. +// +// The optional parameter "hyperlink_type" defines two types of +// hyperlink "External" for website or "Location" for moving to one of the +// cells in this workbook. When the "hyperlink_type" is "Location", +// coordinates need to start with "#". +// +// The optional parameter "positioning" defines two types of the position of an +// image in an Excel spreadsheet, "oneCell" (Move but don't size with +// cells) or "absolute" (Don't move or size with cells). If you don't set this +// parameter, the default positioning is move and size with cells. +// +// The optional parameter "print_obj" indicates whether the image is printed +// when the worksheet is printed, the default value of that is 'true'. +// +// The optional parameter "lock_aspect_ratio" indicates whether lock aspect +// ratio for the image, the default value of that is 'false'. +// +// The optional parameter "locked" indicates whether lock the image. Locking +// an object has no effect unless the sheet is protected. +// +// The optional parameter "x_offset" specifies the horizontal offset of the +// image with the cell, the default value of that is 0. +// +// The optional parameter "x_scale" specifies the horizontal scale of images, +// the default value of that is 1.0 which presents 100%. +// +// The optional parameter "y_offset" specifies the vertical offset of the +// image with the cell, the default value of that is 0. +// +// The optional parameter "y_scale" specifies the vertical scale of images, +// the default value of that is 1.0 which presents 100%. +// +func (f *File) AddPicture(sheet, cell, picture, format string) error { + var err error + // Check picture exists first. + if _, err = os.Stat(picture); os.IsNotExist(err) { + return err + } + ext, ok := supportImageTypes[path.Ext(picture)] + if !ok { + return ErrImgExt + } + file, _ := ioutil.ReadFile(filepath.Clean(picture)) + _, name := filepath.Split(picture) + return f.AddPictureFromBytes(sheet, cell, format, name, ext, file) +} + +// AddPictureFromBytes provides the method to add picture in a sheet by given +// picture format set (such as offset, scale, aspect ratio setting and print +// settings), file base name, extension name and file bytes. For example: +// +// package main +// +// import ( +// "fmt" +// _ "image/jpeg" +// "io/ioutil" +// +// "github.com/xuri/excelize/v2" +// ) +// +// func main() { +// f := excelize.NewFile() +// +// file, err := ioutil.ReadFile("image.jpg") +// if err != nil { +// fmt.Println(err) +// } +// if err := f.AddPictureFromBytes("Sheet1", "A2", "", "Excel Logo", ".jpg", file); err != nil { +// fmt.Println(err) +// } +// if err := f.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// } +// +func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string, file []byte) error { + var drawingHyperlinkRID int + var hyperlinkType string + ext, ok := supportImageTypes[extension] + if !ok { + return ErrImgExt + } + formatSet, err := parseFormatPictureSet(format) + if err != nil { + return err + } + img, _, err := image.DecodeConfig(bytes.NewReader(file)) + if err != nil { + return err + } + // Read sheet data. + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + ws.Lock() + // Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. + drawingID := f.countDrawings() + 1 + drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML) + drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" + mediaStr := ".." + strings.TrimPrefix(f.addMedia(file, ext), "xl") + drawingRID := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType) + // Add picture with hyperlink. + if formatSet.Hyperlink != "" && formatSet.HyperlinkType != "" { + if formatSet.HyperlinkType == "External" { + hyperlinkType = formatSet.HyperlinkType + } + drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType) + } + ws.Unlock() + err = f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet) + if err != nil { + return err + } + f.addContentTypePart(drawingID, "drawings") + f.addSheetNameSpace(sheet, SourceRelationship) + return err +} + +// deleteSheetRelationships provides a function to delete relationships in +// xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and +// relationship index. +func (f *File) deleteSheetRelationships(sheet, rID string) { + name, ok := f.sheetMap[trimSheetName(sheet)] + if !ok { + name = strings.ToLower(sheet) + ".xml" + } + rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" + sheetRels := f.relsReader(rels) + if sheetRels == nil { + sheetRels = &xlsxRelationships{} + } + sheetRels.Lock() + defer sheetRels.Unlock() + for k, v := range sheetRels.Relationships { + if v.ID == rID { + sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...) + } + } + f.Relationships.Store(rels, sheetRels) +} + +// addSheetLegacyDrawing provides a function to add legacy drawing element to +// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. +func (f *File) addSheetLegacyDrawing(sheet string, rID int) { + ws, _ := f.workSheetReader(sheet) + ws.LegacyDrawing = &xlsxLegacyDrawing{ + RID: "rId" + strconv.Itoa(rID), + } +} + +// addSheetDrawing provides a function to add drawing element to +// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. +func (f *File) addSheetDrawing(sheet string, rID int) { + ws, _ := f.workSheetReader(sheet) + ws.Drawing = &xlsxDrawing{ + RID: "rId" + strconv.Itoa(rID), + } +} + +// addSheetPicture provides a function to add picture element to +// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. +func (f *File) addSheetPicture(sheet string, rID int) { + ws, _ := f.workSheetReader(sheet) + ws.Picture = &xlsxPicture{ + RID: "rId" + strconv.Itoa(rID), + } +} + +// countDrawings provides a function to get drawing files count storage in the +// folder xl/drawings. +func (f *File) countDrawings() (count int) { + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/drawings/drawing") { + count++ + } + return true + }) + f.Drawings.Range(func(rel, value interface{}) bool { + if strings.Contains(rel.(string), "xl/drawings/drawing") { + count++ + } + return true + }) + return +} + +// addDrawingPicture provides a function to add picture by given sheet, +// drawingXML, cell, file name, width, height relationship index and format +// sets. +func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, height, rID, hyperlinkRID int, formatSet *formatPicture) error { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return err + } + if formatSet.Autofit { + width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), formatSet) + if err != nil { + return err + } + } else { + width = int(float64(width) * formatSet.XScale) + height = int(float64(height) * formatSet.YScale) + } + col-- + row-- + colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) + content, cNvPrID := f.drawingParser(drawingXML) + twoCellAnchor := xdrCellAnchor{} + twoCellAnchor.EditAs = formatSet.Positioning + from := xlsxFrom{} + from.Col = colStart + from.ColOff = formatSet.OffsetX * EMU + from.Row = rowStart + from.RowOff = formatSet.OffsetY * EMU + to := xlsxTo{} + to.Col = colEnd + to.ColOff = x2 * EMU + to.Row = rowEnd + to.RowOff = y2 * EMU + twoCellAnchor.From = &from + twoCellAnchor.To = &to + pic := xlsxPic{} + pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = formatSet.NoChangeAspect + pic.NvPicPr.CNvPr.ID = cNvPrID + pic.NvPicPr.CNvPr.Descr = file + pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID) + if hyperlinkRID != 0 { + pic.NvPicPr.CNvPr.HlinkClick = &xlsxHlinkClick{ + R: SourceRelationship.Value, + RID: "rId" + strconv.Itoa(hyperlinkRID), + } + } + pic.BlipFill.Blip.R = SourceRelationship.Value + pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID) + pic.SpPr.PrstGeom.Prst = "rect" + + twoCellAnchor.Pic = &pic + twoCellAnchor.ClientData = &xdrClientData{ + FLocksWithSheet: formatSet.FLocksWithSheet, + FPrintsWithSheet: formatSet.FPrintsWithSheet, + } + content.Lock() + defer content.Unlock() + content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) + f.Drawings.Store(drawingXML, content) + return err +} + +// countMedia provides a function to get media files count storage in the +// folder xl/media/image. +func (f *File) countMedia() int { + count := 0 + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/media/image") { + count++ + } + return true + }) + return count +} + +// addMedia provides a function to add a picture into folder xl/media/image by +// given file and extension name. Duplicate images are only actually stored once +// and drawings that use it will reference the same image. +func (f *File) addMedia(file []byte, ext string) string { + count := f.countMedia() + var name string + f.Pkg.Range(func(k, existing interface{}) bool { + if !strings.HasPrefix(k.(string), "xl/media/image") { + return true + } + if bytes.Equal(file, existing.([]byte)) { + name = k.(string) + return false + } + return true + }) + if name != "" { + return name + } + media := "xl/media/image" + strconv.Itoa(count+1) + ext + f.Pkg.Store(media, file) + return media +} + +// setContentTypePartImageExtensions provides a function to set the content +// type for relationship parts and the Main Document part. +func (f *File) setContentTypePartImageExtensions() { + imageTypes := map[string]bool{"jpeg": false, "png": false, "gif": false, "tiff": false} + content := f.contentTypesReader() + content.Lock() + defer content.Unlock() + for _, v := range content.Defaults { + _, ok := imageTypes[v.Extension] + if ok { + imageTypes[v.Extension] = true + } + } + for k, v := range imageTypes { + if !v { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: k, + ContentType: "image/" + k, + }) + } + } +} + +// setContentTypePartVMLExtensions provides a function to set the content type +// for relationship parts and the Main Document part. +func (f *File) setContentTypePartVMLExtensions() { + vml := false + content := f.contentTypesReader() + content.Lock() + defer content.Unlock() + for _, v := range content.Defaults { + if v.Extension == "vml" { + vml = true + } + } + if !vml { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: "vml", + ContentType: ContentTypeVML, + }) + } +} + +// addContentTypePart provides a function to add content type part +// relationships in the file [Content_Types].xml by given index. +func (f *File) addContentTypePart(index int, contentType string) { + setContentType := map[string]func(){ + "comments": f.setContentTypePartVMLExtensions, + "drawings": f.setContentTypePartImageExtensions, + } + partNames := map[string]string{ + "chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml", + "chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml", + "comments": "/xl/comments" + strconv.Itoa(index) + ".xml", + "drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml", + "table": "/xl/tables/table" + strconv.Itoa(index) + ".xml", + "pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml", + "pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml", + "sharedStrings": "/xl/sharedStrings.xml", + } + contentTypes := map[string]string{ + "chart": ContentTypeDrawingML, + "chartsheet": ContentTypeSpreadSheetMLChartsheet, + "comments": ContentTypeSpreadSheetMLComments, + "drawings": ContentTypeDrawing, + "table": ContentTypeSpreadSheetMLTable, + "pivotTable": ContentTypeSpreadSheetMLPivotTable, + "pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition, + "sharedStrings": ContentTypeSpreadSheetMLSharedStrings, + } + s, ok := setContentType[contentType] + if ok { + s() + } + content := f.contentTypesReader() + content.Lock() + defer content.Unlock() + for _, v := range content.Overrides { + if v.PartName == partNames[contentType] { + return + } + } + content.Overrides = append(content.Overrides, xlsxOverride{ + PartName: partNames[contentType], + ContentType: contentTypes[contentType], + }) +} + +// getSheetRelationshipsTargetByID provides a function to get Target attribute +// value in xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and +// relationship index. +func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { + name, ok := f.sheetMap[trimSheetName(sheet)] + if !ok { + name = strings.ToLower(sheet) + ".xml" + } + rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" + sheetRels := f.relsReader(rels) + if sheetRels == nil { + sheetRels = &xlsxRelationships{} + } + sheetRels.Lock() + defer sheetRels.Unlock() + for _, v := range sheetRels.Relationships { + if v.ID == rID { + return v.Target + } + } + return "" +} + +// GetPicture provides a function to get picture base name and raw content +// embed in spreadsheet by given worksheet and cell name. This function +// returns the file name in spreadsheet and file contents as []byte data +// types. For example: +// +// f, err := excelize.OpenFile("Book1.xlsx") +// if err != nil { +// fmt.Println(err) +// return +// } +// defer func() { +// if err := f.Close(); err != nil { +// fmt.Println(err) +// } +// }() +// file, raw, err := f.GetPicture("Sheet1", "A2") +// if err != nil { +// fmt.Println(err) +// return +// } +// if err := ioutil.WriteFile(file, raw, 0644); err != nil { +// fmt.Println(err) +// } +// +func (f *File) GetPicture(sheet, cell string) (string, []byte, error) { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return "", nil, err + } + col-- + row-- + ws, err := f.workSheetReader(sheet) + if err != nil { + return "", nil, err + } + if ws.Drawing == nil { + return "", nil, err + } + target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) + drawingXML := strings.Replace(target, "..", "xl", -1) + if _, ok := f.Pkg.Load(drawingXML); !ok { + return "", nil, err + } + drawingRelationships := strings.Replace( + strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1) + + return f.getPicture(row, col, drawingXML, drawingRelationships) +} + +// DeletePicture provides a function to delete charts in spreadsheet by given +// worksheet and cell name. Note that the image file won't be deleted from the +// document currently. +func (f *File) DeletePicture(sheet, cell string) (err error) { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return + } + col-- + row-- + ws, err := f.workSheetReader(sheet) + if err != nil { + return + } + if ws.Drawing == nil { + return + } + drawingXML := strings.Replace(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl", -1) + return f.deleteDrawing(col, row, drawingXML, "Pic") +} + +// getPicture provides a function to get picture base name and raw content +// embed in spreadsheet by given coordinates and drawing relationships. +func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (ret string, buf []byte, err error) { + var ( + wsDr *xlsxWsDr + ok bool + deWsDr *decodeWsDr + drawRel *xlsxRelationship + deTwoCellAnchor *decodeTwoCellAnchor + ) + + wsDr, _ = f.drawingParser(drawingXML) + if ret, buf = f.getPictureFromWsDr(row, col, drawingRelationships, wsDr); len(buf) > 0 { + return + } + deWsDr = new(decodeWsDr) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))). + Decode(deWsDr); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + err = nil + for _, anchor := range deWsDr.TwoCellAnchor { + deTwoCellAnchor = new(decodeTwoCellAnchor) + if err = f.xmlNewDecoder(strings.NewReader("" + anchor.Content + "")). + Decode(deTwoCellAnchor); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + if err = nil; deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil { + if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row { + drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed) + if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok { + ret = filepath.Base(drawRel.Target) + if buffer, _ := f.Pkg.Load(strings.Replace(drawRel.Target, "..", "xl", -1)); buffer != nil { + buf = buffer.([]byte) + } + return + } + } + } + } + return +} + +// getPictureFromWsDr provides a function to get picture base name and raw +// content in worksheet drawing by given coordinates and drawing +// relationships. +func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsDr *xlsxWsDr) (ret string, buf []byte) { + var ( + ok bool + anchor *xdrCellAnchor + drawRel *xlsxRelationship + ) + wsDr.Lock() + defer wsDr.Unlock() + for _, anchor = range wsDr.TwoCellAnchor { + if anchor.From != nil && anchor.Pic != nil { + if anchor.From.Col == col && anchor.From.Row == row { + if drawRel = f.getDrawingRelationships(drawingRelationships, + anchor.Pic.BlipFill.Blip.Embed); drawRel != nil { + if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok { + ret = filepath.Base(drawRel.Target) + if buffer, _ := f.Pkg.Load(strings.Replace(drawRel.Target, "..", "xl", -1)); buffer != nil { + buf = buffer.([]byte) + } + return + } + } + } + } + } + return +} + +// getDrawingRelationships provides a function to get drawing relationships +// from xl/drawings/_rels/drawing%s.xml.rels by given file name and +// relationship ID. +func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship { + if drawingRels := f.relsReader(rels); drawingRels != nil { + drawingRels.Lock() + defer drawingRels.Unlock() + for _, v := range drawingRels.Relationships { + if v.ID == rID { + return &v + } + } + } + return nil +} + +// drawingsWriter provides a function to save xl/drawings/drawing%d.xml after +// serialize structure. +func (f *File) drawingsWriter() { + f.Drawings.Range(func(path, d interface{}) bool { + if d != nil { + v, _ := xml.Marshal(d.(*xlsxWsDr)) + f.saveFileList(path.(string), v) + } + return true + }) +} + +// drawingResize calculate the height and width after resizing. +func (f *File) drawingResize(sheet, cell string, width, height float64, formatSet *formatPicture) (w, h, c, r int, err error) { + var mergeCells []MergeCell + mergeCells, err = f.GetMergeCells(sheet) + if err != nil { + return + } + var rng []int + var inMergeCell bool + if c, r, err = CellNameToCoordinates(cell); err != nil { + return + } + cellWidth, cellHeight := f.getColWidth(sheet, c), f.getRowHeight(sheet, r) + for _, mergeCell := range mergeCells { + if inMergeCell { + continue + } + if inMergeCell, err = f.checkCellInArea(cell, mergeCell[0]); err != nil { + return + } + if inMergeCell { + rng, _ = areaRangeToCoordinates(mergeCell.GetStartAxis(), mergeCell.GetEndAxis()) + _ = sortCoordinates(rng) + } + } + if inMergeCell { + cellWidth, cellHeight = 0, 0 + c, r = rng[0], rng[1] + for col := rng[0]; col <= rng[2]; col++ { + cellWidth += f.getColWidth(sheet, col) + } + for row := rng[1]; row <= rng[3]; row++ { + cellHeight += f.getRowHeight(sheet, row) + } + } + if float64(cellWidth) < width { + asp := float64(cellWidth) / width + width, height = float64(cellWidth), height*asp + } + if float64(cellHeight) < height { + asp := float64(cellHeight) / height + height, width = float64(cellHeight), width*asp + } + width, height = width-float64(formatSet.OffsetX), height-float64(formatSet.OffsetY) + w, h = int(width*formatSet.XScale), int(height*formatSet.YScale) + return +} diff --git a/vendor/github.com/xuri/excelize/v2/pivotTable.go b/vendor/github.com/xuri/excelize/v2/pivotTable.go new file mode 100644 index 00000000..437d22f0 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/pivotTable.go @@ -0,0 +1,714 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/xml" + "fmt" + "strconv" + "strings" +) + +// PivotTableOption directly maps the format settings of the pivot table. +// +// PivotTableStyleName: The built-in pivot table style names +// +// PivotStyleLight1 - PivotStyleLight28 +// PivotStyleMedium1 - PivotStyleMedium28 +// PivotStyleDark1 - PivotStyleDark28 +// +type PivotTableOption struct { + pivotTableSheetName string + DataRange string + PivotTableRange string + Rows []PivotTableField + Columns []PivotTableField + Data []PivotTableField + Filter []PivotTableField + RowGrandTotals bool + ColGrandTotals bool + ShowDrill bool + UseAutoFormatting bool + PageOverThenDown bool + MergeItem bool + CompactData bool + ShowError bool + ShowRowHeaders bool + ShowColHeaders bool + ShowRowStripes bool + ShowColStripes bool + ShowLastColumn bool + PivotTableStyleName string +} + +// PivotTableField directly maps the field settings of the pivot table. +// Subtotal specifies the aggregation function that applies to this data +// field. The default value is sum. The possible values for this attribute +// are: +// +// Average +// Count +// CountNums +// Max +// Min +// Product +// StdDev +// StdDevp +// Sum +// Var +// Varp +// +// Name specifies the name of the data field. Maximum 255 characters +// are allowed in data field name, excess characters will be truncated. +type PivotTableField struct { + Compact bool + Data string + Name string + Outline bool + Subtotal string + DefaultSubtotal bool +} + +// AddPivotTable provides the method to add pivot table by given pivot table +// options. Note that the same fields can not in Columns, Rows and Filter +// fields at the same time. +// +// For example, create a pivot table on the Sheet1!$G$2:$M$34 area with the +// region Sheet1!$A$1:$E$31 as the data source, summarize by sum for sales: +// +// package main +// +// import ( +// "fmt" +// "math/rand" +// +// "github.com/xuri/excelize/v2" +// ) +// +// func main() { +// f := excelize.NewFile() +// // Create some data in a sheet +// month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} +// year := []int{2017, 2018, 2019} +// types := []string{"Meat", "Dairy", "Beverages", "Produce"} +// region := []string{"East", "West", "North", "South"} +// f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}) +// for i := 0; i < 30; i++ { +// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)]) +// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)]) +// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)]) +// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000)) +// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)]) +// } +// if err := f.AddPivotTable(&excelize.PivotTableOption{ +// DataRange: "Sheet1!$A$1:$E$31", +// PivotTableRange: "Sheet1!$G$2:$M$34", +// Rows: []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}}, +// Filter: []excelize.PivotTableField{{Data: "Region"}}, +// Columns: []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}}, +// Data: []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}}, +// RowGrandTotals: true, +// ColGrandTotals: true, +// ShowDrill: true, +// ShowRowHeaders: true, +// ShowColHeaders: true, +// ShowLastColumn: true, +// }); err != nil { +// fmt.Println(err) +// } +// if err := f.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// } +// +func (f *File) AddPivotTable(opt *PivotTableOption) error { + // parameter validation + _, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opt) + if err != nil { + return err + } + + pivotTableID := f.countPivotTables() + 1 + pivotCacheID := f.countPivotCache() + 1 + + sheetRelationshipsPivotTableXML := "../pivotTables/pivotTable" + strconv.Itoa(pivotTableID) + ".xml" + pivotTableXML := strings.Replace(sheetRelationshipsPivotTableXML, "..", "xl", -1) + pivotCacheXML := "xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(pivotCacheID) + ".xml" + err = f.addPivotCache(pivotCacheXML, opt) + if err != nil { + return err + } + + // workbook pivot cache + workBookPivotCacheRID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipPivotCache, fmt.Sprintf("/xl/pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "") + cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID) + + pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels" + // rId not used + _ = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "") + err = f.addPivotTable(cacheID, pivotTableID, pivotTableXML, opt) + if err != nil { + return err + } + pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels" + f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "") + f.addContentTypePart(pivotTableID, "pivotTable") + f.addContentTypePart(pivotCacheID, "pivotCache") + + return nil +} + +// parseFormatPivotTableSet provides a function to validate pivot table +// properties. +func (f *File) parseFormatPivotTableSet(opt *PivotTableOption) (*xlsxWorksheet, string, error) { + if opt == nil { + return nil, "", ErrParameterRequired + } + pivotTableSheetName, _, err := f.adjustRange(opt.PivotTableRange) + if err != nil { + return nil, "", fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error()) + } + opt.pivotTableSheetName = pivotTableSheetName + dataRange := f.getDefinedNameRefTo(opt.DataRange, pivotTableSheetName) + if dataRange == "" { + dataRange = opt.DataRange + } + dataSheetName, _, err := f.adjustRange(dataRange) + if err != nil { + return nil, "", fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error()) + } + dataSheet, err := f.workSheetReader(dataSheetName) + if err != nil { + return dataSheet, "", err + } + pivotTableSheetPath, ok := f.sheetMap[trimSheetName(pivotTableSheetName)] + if !ok { + return dataSheet, pivotTableSheetPath, fmt.Errorf("sheet %s is not exist", pivotTableSheetName) + } + return dataSheet, pivotTableSheetPath, err +} + +// adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31 +func (f *File) adjustRange(rangeStr string) (string, []int, error) { + if len(rangeStr) < 1 { + return "", []int{}, ErrParameterRequired + } + rng := strings.Split(rangeStr, "!") + if len(rng) != 2 { + return "", []int{}, ErrParameterInvalid + } + trimRng := strings.Replace(rng[1], "$", "", -1) + coordinates, err := areaRefToCoordinates(trimRng) + if err != nil { + return rng[0], []int{}, err + } + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] + if x1 == x2 && y1 == y2 { + return rng[0], []int{}, ErrParameterInvalid + } + + // Correct the coordinate area, such correct C1:B3 to B1:C3. + if x2 < x1 { + x1, x2 = x2, x1 + } + + if y2 < y1 { + y1, y2 = y2, y1 + } + return rng[0], []int{x1, y1, x2, y2}, nil +} + +// getPivotFieldsOrder provides a function to get order list of pivot table +// fields. +func (f *File) getPivotFieldsOrder(opt *PivotTableOption) ([]string, error) { + var order []string + dataRange := f.getDefinedNameRefTo(opt.DataRange, opt.pivotTableSheetName) + if dataRange == "" { + dataRange = opt.DataRange + } + dataSheet, coordinates, err := f.adjustRange(dataRange) + if err != nil { + return order, fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error()) + } + for col := coordinates[0]; col <= coordinates[2]; col++ { + coordinate, _ := CoordinatesToCellName(col, coordinates[1]) + name, err := f.GetCellValue(dataSheet, coordinate) + if err != nil { + return order, err + } + order = append(order, name) + } + return order, nil +} + +// addPivotCache provides a function to create a pivot cache by given properties. +func (f *File) addPivotCache(pivotCacheXML string, opt *PivotTableOption) error { + // validate data range + definedNameRef := true + dataRange := f.getDefinedNameRefTo(opt.DataRange, opt.pivotTableSheetName) + if dataRange == "" { + definedNameRef = false + dataRange = opt.DataRange + } + dataSheet, coordinates, err := f.adjustRange(dataRange) + if err != nil { + return fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error()) + } + // data range has been checked + order, _ := f.getPivotFieldsOrder(opt) + hCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1]) + vCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3]) + pc := xlsxPivotCacheDefinition{ + SaveData: false, + RefreshOnLoad: true, + CreatedVersion: pivotTableVersion, + RefreshedVersion: pivotTableVersion, + MinRefreshableVersion: pivotTableVersion, + CacheSource: &xlsxCacheSource{ + Type: "worksheet", + WorksheetSource: &xlsxWorksheetSource{ + Ref: hCell + ":" + vCell, + Sheet: dataSheet, + }, + }, + CacheFields: &xlsxCacheFields{}, + } + if definedNameRef { + pc.CacheSource.WorksheetSource = &xlsxWorksheetSource{Name: opt.DataRange} + } + for _, name := range order { + rowOptions, rowOk := f.getPivotTableFieldOptions(name, opt.Rows) + columnOptions, colOk := f.getPivotTableFieldOptions(name, opt.Columns) + sharedItems := xlsxSharedItems{ + Count: 0, + } + s := xlsxString{} + if (rowOk && !rowOptions.DefaultSubtotal) || (colOk && !columnOptions.DefaultSubtotal) { + s = xlsxString{ + V: "", + } + sharedItems.Count++ + sharedItems.S = &s + } + + pc.CacheFields.CacheField = append(pc.CacheFields.CacheField, &xlsxCacheField{ + Name: name, + SharedItems: &sharedItems, + }) + } + pc.CacheFields.Count = len(pc.CacheFields.CacheField) + pivotCache, err := xml.Marshal(pc) + f.saveFileList(pivotCacheXML, pivotCache) + return err +} + +// addPivotTable provides a function to create a pivot table by given pivot +// table ID and properties. +func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, opt *PivotTableOption) error { + // validate pivot table range + _, coordinates, err := f.adjustRange(opt.PivotTableRange) + if err != nil { + return fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error()) + } + + hCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1]) + vCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3]) + + pivotTableStyle := func() string { + if opt.PivotTableStyleName == "" { + return "PivotStyleLight16" + } + return opt.PivotTableStyleName + } + pt := xlsxPivotTableDefinition{ + Name: fmt.Sprintf("Pivot Table%d", pivotTableID), + CacheID: cacheID, + RowGrandTotals: &opt.RowGrandTotals, + ColGrandTotals: &opt.ColGrandTotals, + UpdatedVersion: pivotTableVersion, + MinRefreshableVersion: pivotTableVersion, + ShowDrill: &opt.ShowDrill, + UseAutoFormatting: &opt.UseAutoFormatting, + PageOverThenDown: &opt.PageOverThenDown, + MergeItem: &opt.MergeItem, + CreatedVersion: pivotTableVersion, + CompactData: &opt.CompactData, + ShowError: &opt.ShowError, + DataCaption: "Values", + Location: &xlsxLocation{ + Ref: hCell + ":" + vCell, + FirstDataCol: 1, + FirstDataRow: 1, + FirstHeaderRow: 1, + }, + PivotFields: &xlsxPivotFields{}, + RowItems: &xlsxRowItems{ + Count: 1, + I: []*xlsxI{ + { + []*xlsxX{{}, {}}, + }, + }, + }, + ColItems: &xlsxColItems{ + Count: 1, + I: []*xlsxI{{}}, + }, + PivotTableStyleInfo: &xlsxPivotTableStyleInfo{ + Name: pivotTableStyle(), + ShowRowHeaders: opt.ShowRowHeaders, + ShowColHeaders: opt.ShowColHeaders, + ShowRowStripes: opt.ShowRowStripes, + ShowColStripes: opt.ShowColStripes, + ShowLastColumn: opt.ShowLastColumn, + }, + } + + // pivot fields + _ = f.addPivotFields(&pt, opt) + + // count pivot fields + pt.PivotFields.Count = len(pt.PivotFields.PivotField) + + // data range has been checked + _ = f.addPivotRowFields(&pt, opt) + _ = f.addPivotColFields(&pt, opt) + _ = f.addPivotPageFields(&pt, opt) + _ = f.addPivotDataFields(&pt, opt) + + pivotTable, err := xml.Marshal(pt) + f.saveFileList(pivotTableXML, pivotTable) + return err +} + +// addPivotRowFields provides a method to add row fields for pivot table by +// given pivot table options. +func (f *File) addPivotRowFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error { + // row fields + rowFieldsIndex, err := f.getPivotFieldsIndex(opt.Rows, opt) + if err != nil { + return err + } + for _, fieldIdx := range rowFieldsIndex { + if pt.RowFields == nil { + pt.RowFields = &xlsxRowFields{} + } + pt.RowFields.Field = append(pt.RowFields.Field, &xlsxField{ + X: fieldIdx, + }) + } + + // count row fields + if pt.RowFields != nil { + pt.RowFields.Count = len(pt.RowFields.Field) + } + return err +} + +// addPivotPageFields provides a method to add page fields for pivot table by +// given pivot table options. +func (f *File) addPivotPageFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error { + // page fields + pageFieldsIndex, err := f.getPivotFieldsIndex(opt.Filter, opt) + if err != nil { + return err + } + pageFieldsName := f.getPivotTableFieldsName(opt.Filter) + for idx, pageField := range pageFieldsIndex { + if pt.PageFields == nil { + pt.PageFields = &xlsxPageFields{} + } + pt.PageFields.PageField = append(pt.PageFields.PageField, &xlsxPageField{ + Name: pageFieldsName[idx], + Fld: pageField, + }) + } + + // count page fields + if pt.PageFields != nil { + pt.PageFields.Count = len(pt.PageFields.PageField) + } + return err +} + +// addPivotDataFields provides a method to add data fields for pivot table by +// given pivot table options. +func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error { + // data fields + dataFieldsIndex, err := f.getPivotFieldsIndex(opt.Data, opt) + if err != nil { + return err + } + dataFieldsSubtotals := f.getPivotTableFieldsSubtotal(opt.Data) + dataFieldsName := f.getPivotTableFieldsName(opt.Data) + for idx, dataField := range dataFieldsIndex { + if pt.DataFields == nil { + pt.DataFields = &xlsxDataFields{} + } + pt.DataFields.DataField = append(pt.DataFields.DataField, &xlsxDataField{ + Name: dataFieldsName[idx], + Fld: dataField, + Subtotal: dataFieldsSubtotals[idx], + }) + } + + // count data fields + if pt.DataFields != nil { + pt.DataFields.Count = len(pt.DataFields.DataField) + } + return err +} + +// inPivotTableField provides a method to check if an element is present in +// pivot table fields list, and return the index of its location, otherwise +// return -1. +func inPivotTableField(a []PivotTableField, x string) int { + for idx, n := range a { + if x == n.Data { + return idx + } + } + return -1 +} + +// addPivotColFields create pivot column fields by given pivot table +// definition and option. +func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error { + if len(opt.Columns) == 0 { + if len(opt.Data) <= 1 { + return nil + } + pt.ColFields = &xlsxColFields{} + // in order to create pivot table in case there is no input from Columns + pt.ColFields.Count = 1 + pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{ + X: -2, + }) + return nil + } + + pt.ColFields = &xlsxColFields{} + + // col fields + colFieldsIndex, err := f.getPivotFieldsIndex(opt.Columns, opt) + if err != nil { + return err + } + for _, fieldIdx := range colFieldsIndex { + pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{ + X: fieldIdx, + }) + } + + // in order to create pivot in case there is many Columns and Data + if len(opt.Data) > 1 { + pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{ + X: -2, + }) + } + + // count col fields + pt.ColFields.Count = len(pt.ColFields.Field) + return err +} + +// addPivotFields create pivot fields based on the column order of the first +// row in the data region by given pivot table definition and option. +func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error { + order, err := f.getPivotFieldsOrder(opt) + if err != nil { + return err + } + x := 0 + for _, name := range order { + if inPivotTableField(opt.Rows, name) != -1 { + rowOptions, ok := f.getPivotTableFieldOptions(name, opt.Rows) + var items []*xlsxItem + if !ok || !rowOptions.DefaultSubtotal { + items = append(items, &xlsxItem{X: &x}) + } else { + items = append(items, &xlsxItem{T: "default"}) + } + + pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{ + Name: f.getPivotTableFieldName(name, opt.Rows), + Axis: "axisRow", + Compact: &rowOptions.Compact, + Outline: &rowOptions.Outline, + DefaultSubtotal: &rowOptions.DefaultSubtotal, + Items: &xlsxItems{ + Count: len(items), + Item: items, + }, + }) + continue + } + if inPivotTableField(opt.Filter, name) != -1 { + pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{ + Axis: "axisPage", + Name: f.getPivotTableFieldName(name, opt.Columns), + Items: &xlsxItems{ + Count: 1, + Item: []*xlsxItem{ + {T: "default"}, + }, + }, + }) + continue + } + if inPivotTableField(opt.Columns, name) != -1 { + columnOptions, ok := f.getPivotTableFieldOptions(name, opt.Columns) + var items []*xlsxItem + if !ok || !columnOptions.DefaultSubtotal { + items = append(items, &xlsxItem{X: &x}) + } else { + items = append(items, &xlsxItem{T: "default"}) + } + pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{ + Name: f.getPivotTableFieldName(name, opt.Columns), + Axis: "axisCol", + Compact: &columnOptions.Compact, + Outline: &columnOptions.Outline, + DefaultSubtotal: &columnOptions.DefaultSubtotal, + Items: &xlsxItems{ + Count: len(items), + Item: items, + }, + }) + continue + } + if inPivotTableField(opt.Data, name) != -1 { + pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{ + DataField: true, + }) + continue + } + pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{}) + } + return err +} + +// countPivotTables provides a function to get drawing files count storage in +// the folder xl/pivotTables. +func (f *File) countPivotTables() int { + count := 0 + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/pivotTables/pivotTable") { + count++ + } + return true + }) + return count +} + +// countPivotCache provides a function to get drawing files count storage in +// the folder xl/pivotCache. +func (f *File) countPivotCache() int { + count := 0 + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") { + count++ + } + return true + }) + return count +} + +// getPivotFieldsIndex convert the column of the first row in the data region +// to a sequential index by given fields and pivot option. +func (f *File) getPivotFieldsIndex(fields []PivotTableField, opt *PivotTableOption) ([]int, error) { + var pivotFieldsIndex []int + orders, err := f.getPivotFieldsOrder(opt) + if err != nil { + return pivotFieldsIndex, err + } + for _, field := range fields { + if pos := inStrSlice(orders, field.Data, true); pos != -1 { + pivotFieldsIndex = append(pivotFieldsIndex, pos) + } + } + return pivotFieldsIndex, nil +} + +// getPivotTableFieldsSubtotal prepare fields subtotal by given pivot table fields. +func (f *File) getPivotTableFieldsSubtotal(fields []PivotTableField) []string { + field := make([]string, len(fields)) + enums := []string{"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp", "sum", "var", "varp"} + inEnums := func(enums []string, val string) string { + for _, enum := range enums { + if strings.EqualFold(enum, val) { + return enum + } + } + return "sum" + } + for idx, fld := range fields { + field[idx] = inEnums(enums, fld.Subtotal) + } + return field +} + +// getPivotTableFieldsName prepare fields name list by given pivot table +// fields. +func (f *File) getPivotTableFieldsName(fields []PivotTableField) []string { + field := make([]string, len(fields)) + for idx, fld := range fields { + if len(fld.Name) > MaxFieldLength { + field[idx] = fld.Name[:MaxFieldLength] + continue + } + field[idx] = fld.Name + } + return field +} + +// getPivotTableFieldName prepare field name by given pivot table fields. +func (f *File) getPivotTableFieldName(name string, fields []PivotTableField) string { + fieldsName := f.getPivotTableFieldsName(fields) + for idx, field := range fields { + if field.Data == name { + return fieldsName[idx] + } + } + return "" +} + +// getPivotTableFieldOptions return options for specific field by given field name. +func (f *File) getPivotTableFieldOptions(name string, fields []PivotTableField) (options PivotTableField, ok bool) { + for _, field := range fields { + if field.Data == name { + options, ok = field, true + return + } + } + return +} + +// addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml. +func (f *File) addWorkbookPivotCache(RID int) int { + wb := f.workbookReader() + if wb.PivotCaches == nil { + wb.PivotCaches = &xlsxPivotCaches{} + } + cacheID := 1 + for _, pivotCache := range wb.PivotCaches.PivotCache { + if pivotCache.CacheID > cacheID { + cacheID = pivotCache.CacheID + } + } + cacheID++ + wb.PivotCaches.PivotCache = append(wb.PivotCaches.PivotCache, xlsxPivotCache{ + CacheID: cacheID, + RID: fmt.Sprintf("rId%d", RID), + }) + return cacheID +} diff --git a/vendor/github.com/xuri/excelize/v2/rows.go b/vendor/github.com/xuri/excelize/v2/rows.go new file mode 100644 index 00000000..e0918bc6 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/rows.go @@ -0,0 +1,858 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "log" + "math" + "math/big" + "os" + "strconv" + + "github.com/mohae/deepcopy" +) + +// GetRows return all the rows in a sheet by given worksheet name +// (case sensitive), returned as a two-dimensional array, where the value of +// the cell is converted to the string type. If the cell format can be +// applied to the value of the cell, the applied value will be used, +// otherwise the original value will be used. GetRows fetched the rows with +// value or formula cells, the tail continuously empty cell will be skipped. +// For example: +// +// rows, err := f.GetRows("Sheet1") +// if err != nil { +// fmt.Println(err) +// return +// } +// for _, row := range rows { +// for _, colCell := range row { +// fmt.Print(colCell, "\t") +// } +// fmt.Println() +// } +// +func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) { + rows, err := f.Rows(sheet) + if err != nil { + return nil, err + } + results, cur, max := make([][]string, 0, 64), 0, 0 + for rows.Next() { + cur++ + row, err := rows.Columns(opts...) + if err != nil { + break + } + results = append(results, row) + if len(row) > 0 { + max = cur + } + } + return results[:max], rows.Close() +} + +// Rows defines an iterator to a sheet. +type Rows struct { + err error + curRow, seekRow int + needClose, rawCellValue bool + sheet string + f *File + tempFile *os.File + sst *xlsxSST + decoder *xml.Decoder + token xml.Token +} + +// Next will return true if find the next row element. +func (rows *Rows) Next() bool { + rows.seekRow++ + if rows.curRow >= rows.seekRow { + return true + } + for { + token, _ := rows.decoder.Token() + if token == nil { + return false + } + switch xmlElement := token.(type) { + case xml.StartElement: + if xmlElement.Name.Local == "row" { + rows.curRow++ + if rowNum, _ := attrValToInt("r", xmlElement.Attr); rowNum != 0 { + rows.curRow = rowNum + } + rows.token = token + return true + } + case xml.EndElement: + if xmlElement.Name.Local == "sheetData" { + return false + } + } + } +} + +// Error will return the error when the error occurs. +func (rows *Rows) Error() error { + return rows.err +} + +// Close closes the open worksheet XML file in the system temporary +// directory. +func (rows *Rows) Close() error { + if rows.tempFile != nil { + return rows.tempFile.Close() + } + return nil +} + +// Columns return the current row's column values. +func (rows *Rows) Columns(opts ...Options) ([]string, error) { + if rows.curRow > rows.seekRow { + return nil, nil + } + var rowIterator rowXMLIterator + var token xml.Token + rows.rawCellValue, rows.sst = parseOptions(opts...).RawCellValue, rows.f.sharedStringsReader() + for { + if rows.token != nil { + token = rows.token + } else if token, _ = rows.decoder.Token(); token == nil { + break + } + switch xmlElement := token.(type) { + case xml.StartElement: + rowIterator.inElement = xmlElement.Name.Local + if rowIterator.inElement == "row" { + rowNum := 0 + if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 { + rows.curRow = rowNum + } else if rows.token == nil { + rows.curRow++ + } + if rows.curRow > rows.seekRow { + rows.token = nil + return rowIterator.columns, rowIterator.err + } + } + if rows.rowXMLHandler(&rowIterator, &xmlElement, rows.rawCellValue); rowIterator.err != nil { + rows.token = nil + return rowIterator.columns, rowIterator.err + } + rows.token = nil + case xml.EndElement: + if xmlElement.Name.Local == "sheetData" { + return rowIterator.columns, rowIterator.err + } + } + } + return rowIterator.columns, rowIterator.err +} + +// appendSpace append blank characters to slice by given length and source slice. +func appendSpace(l int, s []string) []string { + for i := 1; i < l; i++ { + s = append(s, "") + } + return s +} + +// ErrSheetNotExist defines an error of sheet is not exist +type ErrSheetNotExist struct { + SheetName string +} + +func (err ErrSheetNotExist) Error() string { + return fmt.Sprintf("sheet %s is not exist", err.SheetName) +} + +// rowXMLIterator defined runtime use field for the worksheet row SAX parser. +type rowXMLIterator struct { + err error + inElement string + cellCol int + columns []string +} + +// rowXMLHandler parse the row XML element of the worksheet. +func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, raw bool) { + if rowIterator.inElement == "c" { + rowIterator.cellCol++ + colCell := xlsxC{} + _ = rows.decoder.DecodeElement(&colCell, xmlElement) + if colCell.R != "" { + if rowIterator.cellCol, _, rowIterator.err = CellNameToCoordinates(colCell.R); rowIterator.err != nil { + return + } + } + blank := rowIterator.cellCol - len(rowIterator.columns) + if val, _ := colCell.getValueFrom(rows.f, rows.sst, raw); val != "" || colCell.F != nil { + rowIterator.columns = append(appendSpace(blank, rowIterator.columns), val) + } + } +} + +// Rows returns a rows iterator, used for streaming reading data for a +// worksheet with a large data. For example: +// +// rows, err := f.Rows("Sheet1") +// if err != nil { +// fmt.Println(err) +// return +// } +// for rows.Next() { +// row, err := rows.Columns() +// if err != nil { +// fmt.Println(err) +// } +// for _, colCell := range row { +// fmt.Print(colCell, "\t") +// } +// fmt.Println() +// } +// if err = rows.Close(); err != nil { +// fmt.Println(err) +// } +// +func (f *File) Rows(sheet string) (*Rows, error) { + name, ok := f.sheetMap[trimSheetName(sheet)] + if !ok { + return nil, ErrSheetNotExist{sheet} + } + if ws, ok := f.Sheet.Load(name); ok && ws != nil { + worksheet := ws.(*xlsxWorksheet) + worksheet.Lock() + defer worksheet.Unlock() + // flush data + output, _ := xml.Marshal(worksheet) + f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) + } + var err error + rows := Rows{f: f, sheet: name} + rows.needClose, rows.decoder, rows.tempFile, err = f.xmlDecoder(name) + return &rows, err +} + +// getFromStringItem build shared string item offset list from system temporary +// file at one time, and return value by given to string index. +func (f *File) getFromStringItem(index int) string { + if f.sharedStringTemp != nil { + if len(f.sharedStringItem) <= index { + return strconv.Itoa(index) + } + offsetRange := f.sharedStringItem[index] + buf := make([]byte, offsetRange[1]-offsetRange[0]) + if _, err := f.sharedStringTemp.ReadAt(buf, int64(offsetRange[0])); err != nil { + return strconv.Itoa(index) + } + return string(buf) + } + needClose, decoder, tempFile, err := f.xmlDecoder(defaultXMLPathSharedStrings) + if needClose && err == nil { + defer tempFile.Close() + } + f.sharedStringItem = [][]uint{} + f.sharedStringTemp, _ = ioutil.TempFile(os.TempDir(), "excelize-") + f.tempFiles.Store(defaultTempFileSST, f.sharedStringTemp.Name()) + var ( + inElement string + i, offset uint + ) + for { + token, _ := decoder.Token() + if token == nil { + break + } + switch xmlElement := token.(type) { + case xml.StartElement: + inElement = xmlElement.Name.Local + if inElement == "si" { + si := xlsxSI{} + _ = decoder.DecodeElement(&si, &xmlElement) + + startIdx := offset + n, _ := f.sharedStringTemp.WriteString(si.String()) + offset += uint(n) + f.sharedStringItem = append(f.sharedStringItem, []uint{startIdx, offset}) + i++ + } + } + } + return f.getFromStringItem(index) +} + +// xmlDecoder creates XML decoder by given path in the zip from memory data +// or system temporary file. +func (f *File) xmlDecoder(name string) (bool, *xml.Decoder, *os.File, error) { + var ( + content []byte + err error + tempFile *os.File + ) + if content = f.readXML(name); len(content) > 0 { + return false, f.xmlNewDecoder(bytes.NewReader(content)), tempFile, err + } + tempFile, err = f.readTemp(name) + return true, f.xmlNewDecoder(tempFile), tempFile, err +} + +// SetRowHeight provides a function to set the height of a single row. For +// example, set the height of the first row in Sheet1: +// +// err := f.SetRowHeight("Sheet1", 1, 50) +// +func (f *File) SetRowHeight(sheet string, row int, height float64) error { + if row < 1 { + return newInvalidRowNumberError(row) + } + if height > MaxRowHeight { + return ErrMaxRowHeight + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + + prepareSheetXML(ws, 0, row) + + rowIdx := row - 1 + ws.SheetData.Row[rowIdx].Ht = height + ws.SheetData.Row[rowIdx].CustomHeight = true + return nil +} + +// getRowHeight provides a function to get row height in pixels by given sheet +// name and row number. +func (f *File) getRowHeight(sheet string, row int) int { + ws, _ := f.workSheetReader(sheet) + ws.Lock() + defer ws.Unlock() + for i := range ws.SheetData.Row { + v := &ws.SheetData.Row[i] + if v.R == row && v.Ht != 0 { + return int(convertRowHeightToPixels(v.Ht)) + } + } + // Optimization for when the row heights haven't changed. + return int(defaultRowHeightPixels) +} + +// GetRowHeight provides a function to get row height by given worksheet name +// and row number. For example, get the height of the first row in Sheet1: +// +// height, err := f.GetRowHeight("Sheet1", 1) +// +func (f *File) GetRowHeight(sheet string, row int) (float64, error) { + if row < 1 { + return defaultRowHeightPixels, newInvalidRowNumberError(row) + } + ht := defaultRowHeight + ws, err := f.workSheetReader(sheet) + if err != nil { + return ht, err + } + if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight { + ht = ws.SheetFormatPr.DefaultRowHeight + } + if row > len(ws.SheetData.Row) { + return ht, nil // it will be better to use 0, but we take care with BC + } + for _, v := range ws.SheetData.Row { + if v.R == row && v.Ht != 0 { + return v.Ht, nil + } + } + // Optimization for when the row heights haven't changed. + return ht, nil +} + +// sharedStringsReader provides a function to get the pointer to the structure +// after deserialization of xl/sharedStrings.xml. +func (f *File) sharedStringsReader() *xlsxSST { + var err error + f.Lock() + defer f.Unlock() + relPath := f.getWorkbookRelsPath() + if f.SharedStrings == nil { + var sharedStrings xlsxSST + ss := f.readXML(defaultXMLPathSharedStrings) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))). + Decode(&sharedStrings); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + if sharedStrings.Count == 0 { + sharedStrings.Count = len(sharedStrings.SI) + } + if sharedStrings.UniqueCount == 0 { + sharedStrings.UniqueCount = sharedStrings.Count + } + f.SharedStrings = &sharedStrings + for i := range sharedStrings.SI { + if sharedStrings.SI[i].T != nil { + f.sharedStringsMap[sharedStrings.SI[i].T.Val] = i + } + } + f.addContentTypePart(0, "sharedStrings") + rels := f.relsReader(relPath) + for _, rel := range rels.Relationships { + if rel.Target == "/xl/sharedStrings.xml" { + return f.SharedStrings + } + } + // Update workbook.xml.rels + f.addRels(relPath, SourceRelationshipSharedStrings, "/xl/sharedStrings.xml", "") + } + + return f.SharedStrings +} + +// getValueFrom return a value from a column/row cell, this function is +// intended to be used with for range on rows an argument with the spreadsheet +// opened file. +func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) { + f.Lock() + defer f.Unlock() + switch c.T { + case "b": + if !raw { + if c.V == "1" { + return "TRUE", nil + } + if c.V == "0" { + return "FALSE", nil + } + } + return f.formattedValue(c.S, c.V, raw), nil + case "s": + if c.V != "" { + xlsxSI := 0 + xlsxSI, _ = strconv.Atoi(c.V) + if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok { + return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw), nil + } + if len(d.SI) > xlsxSI { + return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw), nil + } + } + return f.formattedValue(c.S, c.V, raw), nil + case "str": + return f.formattedValue(c.S, c.V, raw), nil + case "inlineStr": + if c.IS != nil { + return f.formattedValue(c.S, c.IS.String(), raw), nil + } + return f.formattedValue(c.S, c.V, raw), nil + default: + if isNum, precision := isNumeric(c.V); isNum && !raw { + if precision == 0 { + c.V = roundPrecision(c.V, 15) + } else { + c.V = roundPrecision(c.V, -1) + } + } + return f.formattedValue(c.S, c.V, raw), nil + } +} + +// roundPrecision provides a function to format floating-point number text +// with precision, if the given text couldn't be parsed to float, this will +// return the original string. +func roundPrecision(text string, prec int) string { + decimal := big.Float{} + if _, ok := decimal.SetString(text); ok { + flt, _ := decimal.Float64() + if prec == -1 { + return strconv.FormatFloat(flt, 'G', 15, 64) + } + return strconv.FormatFloat(flt, 'f', -1, 64) + } + return text +} + +// SetRowVisible provides a function to set visible of a single row by given +// worksheet name and Excel row number. For example, hide row 2 in Sheet1: +// +// err := f.SetRowVisible("Sheet1", 2, false) +// +func (f *File) SetRowVisible(sheet string, row int, visible bool) error { + if row < 1 { + return newInvalidRowNumberError(row) + } + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + prepareSheetXML(ws, 0, row) + ws.SheetData.Row[row-1].Hidden = !visible + return nil +} + +// GetRowVisible provides a function to get visible of a single row by given +// worksheet name and Excel row number. For example, get visible state of row +// 2 in Sheet1: +// +// visible, err := f.GetRowVisible("Sheet1", 2) +// +func (f *File) GetRowVisible(sheet string, row int) (bool, error) { + if row < 1 { + return false, newInvalidRowNumberError(row) + } + + ws, err := f.workSheetReader(sheet) + if err != nil { + return false, err + } + if row > len(ws.SheetData.Row) { + return false, nil + } + return !ws.SheetData.Row[row-1].Hidden, nil +} + +// SetRowOutlineLevel provides a function to set outline level number of a +// single row by given worksheet name and Excel row number. The value of +// parameter 'level' is 1-7. For example, outline row 2 in Sheet1 to level 1: +// +// err := f.SetRowOutlineLevel("Sheet1", 2, 1) +// +func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error { + if row < 1 { + return newInvalidRowNumberError(row) + } + if level > 7 || level < 1 { + return ErrOutlineLevel + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + prepareSheetXML(ws, 0, row) + ws.SheetData.Row[row-1].OutlineLevel = level + return nil +} + +// GetRowOutlineLevel provides a function to get outline level number of a +// single row by given worksheet name and Excel row number. For example, get +// outline number of row 2 in Sheet1: +// +// level, err := f.GetRowOutlineLevel("Sheet1", 2) +// +func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) { + if row < 1 { + return 0, newInvalidRowNumberError(row) + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return 0, err + } + if row > len(ws.SheetData.Row) { + return 0, nil + } + return ws.SheetData.Row[row-1].OutlineLevel, nil +} + +// RemoveRow provides a function to remove single row by given worksheet name +// and Excel row number. For example, remove row 3 in Sheet1: +// +// err := f.RemoveRow("Sheet1", 3) +// +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. +func (f *File) RemoveRow(sheet string, row int) error { + if row < 1 { + return newInvalidRowNumberError(row) + } + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if row > len(ws.SheetData.Row) { + return f.adjustHelper(sheet, rows, row, -1) + } + keep := 0 + for rowIdx := 0; rowIdx < len(ws.SheetData.Row); rowIdx++ { + v := &ws.SheetData.Row[rowIdx] + if v.R != row { + ws.SheetData.Row[keep] = *v + keep++ + } + } + ws.SheetData.Row = ws.SheetData.Row[:keep] + return f.adjustHelper(sheet, rows, row, -1) +} + +// InsertRow provides a function to insert a new row after given Excel row +// number starting from 1. For example, create a new row before row 3 in +// Sheet1: +// +// err := f.InsertRow("Sheet1", 3) +// +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. +func (f *File) InsertRow(sheet string, row int) error { + if row < 1 { + return newInvalidRowNumberError(row) + } + return f.adjustHelper(sheet, rows, row, 1) +} + +// DuplicateRow inserts a copy of specified row (by its Excel row number) below +// +// err := f.DuplicateRow("Sheet1", 2) +// +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. +func (f *File) DuplicateRow(sheet string, row int) error { + return f.DuplicateRowTo(sheet, row, row+1) +} + +// DuplicateRowTo inserts a copy of specified row by it Excel number +// to specified row position moving down exists rows after target position +// +// err := f.DuplicateRowTo("Sheet1", 2, 7) +// +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. +func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { + if row < 1 { + return newInvalidRowNumberError(row) + } + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + + if row2 < 1 || row == row2 { + return nil + } + + var ok bool + var rowCopy xlsxRow + + for i, r := range ws.SheetData.Row { + if r.R == row { + rowCopy = deepcopy.Copy(ws.SheetData.Row[i]).(xlsxRow) + ok = true + break + } + } + + if err := f.adjustHelper(sheet, rows, row2, 1); err != nil { + return err + } + + if !ok { + return nil + } + + idx2 := -1 + for i, r := range ws.SheetData.Row { + if r.R == row2 { + idx2 = i + break + } + } + if idx2 == -1 && len(ws.SheetData.Row) >= row2 { + return nil + } + + rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...) + f.adjustSingleRowDimensions(&rowCopy, row2) + + if idx2 != -1 { + ws.SheetData.Row[idx2] = rowCopy + } else { + ws.SheetData.Row = append(ws.SheetData.Row, rowCopy) + } + return f.duplicateMergeCells(sheet, ws, row, row2) +} + +// duplicateMergeCells merge cells in the destination row if there are single +// row merged cells in the copied row. +func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 int) error { + if ws.MergeCells == nil { + return nil + } + if row > row2 { + row++ + } + for _, rng := range ws.MergeCells.Cells { + coordinates, err := areaRefToCoordinates(rng.Ref) + if err != nil { + return err + } + if coordinates[1] < row2 && row2 < coordinates[3] { + return nil + } + } + for i := 0; i < len(ws.MergeCells.Cells); i++ { + areaData := ws.MergeCells.Cells[i] + coordinates, _ := areaRefToCoordinates(areaData.Ref) + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] + if y1 == y2 && y1 == row { + from, _ := CoordinatesToCellName(x1, row2) + to, _ := CoordinatesToCellName(x2, row2) + if err := f.MergeCell(sheet, from, to); err != nil { + return err + } + } + } + return nil +} + +// checkRow provides a function to check and fill each column element for all +// rows and make that is continuous in a worksheet of XML. For example: +// +// +// +// +// +// +// +// +// in this case, we should to change it to +// +// +// +// +// +// +// +// +// +// +// +// Noteice: this method could be very slow for large spreadsheets (more than +// 3000 rows one sheet). +func checkRow(ws *xlsxWorksheet) error { + for rowIdx := range ws.SheetData.Row { + rowData := &ws.SheetData.Row[rowIdx] + + colCount := len(rowData.C) + if colCount == 0 { + continue + } + // check and fill the cell without r attribute in a row element + rCount := 0 + for idx, cell := range rowData.C { + rCount++ + if cell.R != "" { + lastR, _, err := CellNameToCoordinates(cell.R) + if err != nil { + return err + } + if lastR > rCount { + rCount = lastR + } + continue + } + rowData.C[idx].R, _ = CoordinatesToCellName(rCount, rowIdx+1) + } + lastCol, _, err := CellNameToCoordinates(rowData.C[colCount-1].R) + if err != nil { + return err + } + + if colCount < lastCol { + oldList := rowData.C + newlist := make([]xlsxC, 0, lastCol) + + rowData.C = ws.SheetData.Row[rowIdx].C[:0] + + for colIdx := 0; colIdx < lastCol; colIdx++ { + cellName, err := CoordinatesToCellName(colIdx+1, rowIdx+1) + if err != nil { + return err + } + newlist = append(newlist, xlsxC{R: cellName}) + } + + rowData.C = newlist + + for colIdx := range oldList { + colData := &oldList[colIdx] + colNum, _, err := CellNameToCoordinates(colData.R) + if err != nil { + return err + } + ws.SheetData.Row[rowIdx].C[colNum-1] = *colData + } + } + } + return nil +} + +// SetRowStyle provides a function to set the style of rows by given worksheet +// name, row range, and style ID. Note that this will overwrite the existing +// styles for the rows, it won't append or merge style with existing styles. +// +// For example set style of row 1 on Sheet1: +// +// err = f.SetRowStyle("Sheet1", 1, 1, styleID) +// +// Set style of rows 1 to 10 on Sheet1: +// +// err = f.SetRowStyle("Sheet1", 1, 10, styleID) +// +func (f *File) SetRowStyle(sheet string, start, end, styleID int) error { + if end < start { + start, end = end, start + } + if start < 1 { + return newInvalidRowNumberError(start) + } + if end > TotalRows { + return ErrMaxRows + } + if styleID < 0 { + return newInvalidStyleID(styleID) + } + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + prepareSheetXML(ws, 0, end) + for row := start - 1; row < end; row++ { + ws.SheetData.Row[row].S = styleID + ws.SheetData.Row[row].CustomFormat = true + } + return nil +} + +// convertRowHeightToPixels provides a function to convert the height of a +// cell from user's units to pixels. If the height hasn't been set by the user +// we use the default value. If the row is hidden it has a value of zero. +func convertRowHeightToPixels(height float64) float64 { + var pixels float64 + if height == 0 { + return pixels + } + pixels = math.Ceil(4.0 / 3.0 * height) + return pixels +} diff --git a/vendor/github.com/xuri/excelize/v2/shape.go b/vendor/github.com/xuri/excelize/v2/shape.go new file mode 100644 index 00000000..514171a8 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/shape.go @@ -0,0 +1,493 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/json" + "strconv" + "strings" +) + +// parseFormatShapeSet provides a function to parse the format settings of the +// shape with default value. +func parseFormatShapeSet(formatSet string) (*formatShape, error) { + format := formatShape{ + Width: 160, + Height: 160, + Format: formatPicture{ + FPrintsWithSheet: true, + FLocksWithSheet: false, + NoChangeAspect: false, + OffsetX: 0, + OffsetY: 0, + XScale: 1.0, + YScale: 1.0, + }, + Line: formatLine{Width: 1}, + } + err := json.Unmarshal([]byte(formatSet), &format) + return &format, err +} + +// AddShape provides the method to add shape in a sheet by given worksheet +// index, shape format set (such as offset, scale, aspect ratio setting and +// print settings) and properties set. For example, add text box (rect shape) +// in Sheet1: +// +// err := f.AddShape("Sheet1", "G6", `{ +// "type": "rect", +// "color": +// { +// "line": "#4286F4", +// "fill": "#8eb9ff" +// }, +// "paragraph": [ +// { +// "text": "Rectangle Shape", +// "font": +// { +// "bold": true, +// "italic": true, +// "family": "Times New Roman", +// "size": 36, +// "color": "#777777", +// "underline": "sng" +// } +// }], +// "width": 180, +// "height": 90, +// "line": +// { +// "width": 1.2 +// } +// }`) +// +// The following shows the type of shape supported by excelize: +// +// accentBorderCallout1 (Callout 1 with Border and Accent Shape) +// accentBorderCallout2 (Callout 2 with Border and Accent Shape) +// accentBorderCallout3 (Callout 3 with Border and Accent Shape) +// accentCallout1 (Callout 1 Shape) +// accentCallout2 (Callout 2 Shape) +// accentCallout3 (Callout 3 Shape) +// actionButtonBackPrevious (Back or Previous Button Shape) +// actionButtonBeginning (Beginning Button Shape) +// actionButtonBlank (Blank Button Shape) +// actionButtonDocument (Document Button Shape) +// actionButtonEnd (End Button Shape) +// actionButtonForwardNext (Forward or Next Button Shape) +// actionButtonHelp (Help Button Shape) +// actionButtonHome (Home Button Shape) +// actionButtonInformation (Information Button Shape) +// actionButtonMovie (Movie Button Shape) +// actionButtonReturn (Return Button Shape) +// actionButtonSound (Sound Button Shape) +// arc (Curved Arc Shape) +// bentArrow (Bent Arrow Shape) +// bentConnector2 (Bent Connector 2 Shape) +// bentConnector3 (Bent Connector 3 Shape) +// bentConnector4 (Bent Connector 4 Shape) +// bentConnector5 (Bent Connector 5 Shape) +// bentUpArrow (Bent Up Arrow Shape) +// bevel (Bevel Shape) +// blockArc (Block Arc Shape) +// borderCallout1 (Callout 1 with Border Shape) +// borderCallout2 (Callout 2 with Border Shape) +// borderCallout3 (Callout 3 with Border Shape) +// bracePair (Brace Pair Shape) +// bracketPair (Bracket Pair Shape) +// callout1 (Callout 1 Shape) +// callout2 (Callout 2 Shape) +// callout3 (Callout 3 Shape) +// can (Can Shape) +// chartPlus (Chart Plus Shape) +// chartStar (Chart Star Shape) +// chartX (Chart X Shape) +// chevron (Chevron Shape) +// chord (Chord Shape) +// circularArrow (Circular Arrow Shape) +// cloud (Cloud Shape) +// cloudCallout (Callout Cloud Shape) +// corner (Corner Shape) +// cornerTabs (Corner Tabs Shape) +// cube (Cube Shape) +// curvedConnector2 (Curved Connector 2 Shape) +// curvedConnector3 (Curved Connector 3 Shape) +// curvedConnector4 (Curved Connector 4 Shape) +// curvedConnector5 (Curved Connector 5 Shape) +// curvedDownArrow (Curved Down Arrow Shape) +// curvedLeftArrow (Curved Left Arrow Shape) +// curvedRightArrow (Curved Right Arrow Shape) +// curvedUpArrow (Curved Up Arrow Shape) +// decagon (Decagon Shape) +// diagStripe (Diagonal Stripe Shape) +// diamond (Diamond Shape) +// dodecagon (Dodecagon Shape) +// donut (Donut Shape) +// doubleWave (Double Wave Shape) +// downArrow (Down Arrow Shape) +// downArrowCallout (Callout Down Arrow Shape) +// ellipse (Ellipse Shape) +// ellipseRibbon (Ellipse Ribbon Shape) +// ellipseRibbon2 (Ellipse Ribbon 2 Shape) +// flowChartAlternateProcess (Alternate Process Flow Shape) +// flowChartCollate (Collate Flow Shape) +// flowChartConnector (Connector Flow Shape) +// flowChartDecision (Decision Flow Shape) +// flowChartDelay (Delay Flow Shape) +// flowChartDisplay (Display Flow Shape) +// flowChartDocument (Document Flow Shape) +// flowChartExtract (Extract Flow Shape) +// flowChartInputOutput (Input Output Flow Shape) +// flowChartInternalStorage (Internal Storage Flow Shape) +// flowChartMagneticDisk (Magnetic Disk Flow Shape) +// flowChartMagneticDrum (Magnetic Drum Flow Shape) +// flowChartMagneticTape (Magnetic Tape Flow Shape) +// flowChartManualInput (Manual Input Flow Shape) +// flowChartManualOperation (Manual Operation Flow Shape) +// flowChartMerge (Merge Flow Shape) +// flowChartMultidocument (Multi-Document Flow Shape) +// flowChartOfflineStorage (Offline Storage Flow Shape) +// flowChartOffpageConnector (Off-Page Connector Flow Shape) +// flowChartOnlineStorage (Online Storage Flow Shape) +// flowChartOr (Or Flow Shape) +// flowChartPredefinedProcess (Predefined Process Flow Shape) +// flowChartPreparation (Preparation Flow Shape) +// flowChartProcess (Process Flow Shape) +// flowChartPunchedCard (Punched Card Flow Shape) +// flowChartPunchedTape (Punched Tape Flow Shape) +// flowChartSort (Sort Flow Shape) +// flowChartSummingJunction (Summing Junction Flow Shape) +// flowChartTerminator (Terminator Flow Shape) +// foldedCorner (Folded Corner Shape) +// frame (Frame Shape) +// funnel (Funnel Shape) +// gear6 (Gear 6 Shape) +// gear9 (Gear 9 Shape) +// halfFrame (Half Frame Shape) +// heart (Heart Shape) +// heptagon (Heptagon Shape) +// hexagon (Hexagon Shape) +// homePlate (Home Plate Shape) +// horizontalScroll (Horizontal Scroll Shape) +// irregularSeal1 (Irregular Seal 1 Shape) +// irregularSeal2 (Irregular Seal 2 Shape) +// leftArrow (Left Arrow Shape) +// leftArrowCallout (Callout Left Arrow Shape) +// leftBrace (Left Brace Shape) +// leftBracket (Left Bracket Shape) +// leftCircularArrow (Left Circular Arrow Shape) +// leftRightArrow (Left Right Arrow Shape) +// leftRightArrowCallout (Callout Left Right Arrow Shape) +// leftRightCircularArrow (Left Right Circular Arrow Shape) +// leftRightRibbon (Left Right Ribbon Shape) +// leftRightUpArrow (Left Right Up Arrow Shape) +// leftUpArrow (Left Up Arrow Shape) +// lightningBolt (Lightning Bolt Shape) +// line (Line Shape) +// lineInv (Line Inverse Shape) +// mathDivide (Divide Math Shape) +// mathEqual (Equal Math Shape) +// mathMinus (Minus Math Shape) +// mathMultiply (Multiply Math Shape) +// mathNotEqual (Not Equal Math Shape) +// mathPlus (Plus Math Shape) +// moon (Moon Shape) +// nonIsoscelesTrapezoid (Non-Isosceles Trapezoid Shape) +// noSmoking (No Smoking Shape) +// notchedRightArrow (Notched Right Arrow Shape) +// octagon (Octagon Shape) +// parallelogram (Parallelogram Shape) +// pentagon (Pentagon Shape) +// pie (Pie Shape) +// pieWedge (Pie Wedge Shape) +// plaque (Plaque Shape) +// plaqueTabs (Plaque Tabs Shape) +// plus (Plus Shape) +// quadArrow (Quad-Arrow Shape) +// quadArrowCallout (Callout Quad-Arrow Shape) +// rect (Rectangle Shape) +// ribbon (Ribbon Shape) +// ribbon2 (Ribbon 2 Shape) +// rightArrow (Right Arrow Shape) +// rightArrowCallout (Callout Right Arrow Shape) +// rightBrace (Right Brace Shape) +// rightBracket (Right Bracket Shape) +// round1Rect (One Round Corner Rectangle Shape) +// round2DiagRect (Two Diagonal Round Corner Rectangle Shape) +// round2SameRect (Two Same-side Round Corner Rectangle Shape) +// roundRect (Round Corner Rectangle Shape) +// rtTriangle (Right Triangle Shape) +// smileyFace (Smiley Face Shape) +// snip1Rect (One Snip Corner Rectangle Shape) +// snip2DiagRect (Two Diagonal Snip Corner Rectangle Shape) +// snip2SameRect (Two Same-side Snip Corner Rectangle Shape) +// snipRoundRect (One Snip One Round Corner Rectangle Shape) +// squareTabs (Square Tabs Shape) +// star10 (Ten Pointed Star Shape) +// star12 (Twelve Pointed Star Shape) +// star16 (Sixteen Pointed Star Shape) +// star24 (Twenty Four Pointed Star Shape) +// star32 (Thirty Two Pointed Star Shape) +// star4 (Four Pointed Star Shape) +// star5 (Five Pointed Star Shape) +// star6 (Six Pointed Star Shape) +// star7 (Seven Pointed Star Shape) +// star8 (Eight Pointed Star Shape) +// straightConnector1 (Straight Connector 1 Shape) +// stripedRightArrow (Striped Right Arrow Shape) +// sun (Sun Shape) +// swooshArrow (Swoosh Arrow Shape) +// teardrop (Teardrop Shape) +// trapezoid (Trapezoid Shape) +// triangle (Triangle Shape) +// upArrow (Up Arrow Shape) +// upArrowCallout (Callout Up Arrow Shape) +// upDownArrow (Up Down Arrow Shape) +// upDownArrowCallout (Callout Up Down Arrow Shape) +// uturnArrow (U-Turn Arrow Shape) +// verticalScroll (Vertical Scroll Shape) +// wave (Wave Shape) +// wedgeEllipseCallout (Callout Wedge Ellipse Shape) +// wedgeRectCallout (Callout Wedge Rectangle Shape) +// wedgeRoundRectCallout (Callout Wedge Round Rectangle Shape) +// +// The following shows the type of text underline supported by excelize: +// +// none +// words +// sng +// dbl +// heavy +// dotted +// dottedHeavy +// dash +// dashHeavy +// dashLong +// dashLongHeavy +// dotDash +// dotDashHeavy +// dotDotDash +// dotDotDashHeavy +// wavy +// wavyHeavy +// wavyDbl +// +func (f *File) AddShape(sheet, cell, format string) error { + formatSet, err := parseFormatShapeSet(format) + if err != nil { + return err + } + // Read sheet data. + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + // Add first shape for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. + drawingID := f.countDrawings() + 1 + drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + + if ws.Drawing != nil { + // The worksheet already has a shape or chart relationships, use the relationships drawing ../drawings/drawing%d.xml. + sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) + drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml")) + drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1) + } else { + // Add first shape for given sheet. + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + f.addSheetDrawing(sheet, rID) + f.addSheetNameSpace(sheet, SourceRelationship) + } + err = f.addDrawingShape(sheet, drawingXML, cell, formatSet) + if err != nil { + return err + } + f.addContentTypePart(drawingID, "drawings") + return err +} + +// addDrawingShape provides a function to add preset geometry by given sheet, +// drawingXMLand format sets. +func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *formatShape) error { + fromCol, fromRow, err := CellNameToCoordinates(cell) + if err != nil { + return err + } + colIdx := fromCol - 1 + rowIdx := fromRow - 1 + + textUnderlineType := map[string]bool{ + "none": true, + "words": true, + "sng": true, + "dbl": true, + "heavy": true, + "dotted": true, + "dottedHeavy": true, + "dash": true, + "dashHeavy": true, + "dashLong": true, + "dashLongHeavy": true, + "dotDash": true, + "dotDashHeavy": true, + "dotDotDash": true, + "dotDotDashHeavy": true, + "wavy": true, + "wavyHeavy": true, + "wavyDbl": true, + } + + width := int(float64(formatSet.Width) * formatSet.Format.XScale) + height := int(float64(formatSet.Height) * formatSet.Format.YScale) + + colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.Format.OffsetX, formatSet.Format.OffsetY, + width, height) + content, cNvPrID := f.drawingParser(drawingXML) + twoCellAnchor := xdrCellAnchor{} + twoCellAnchor.EditAs = formatSet.Format.Positioning + from := xlsxFrom{} + from.Col = colStart + from.ColOff = formatSet.Format.OffsetX * EMU + from.Row = rowStart + from.RowOff = formatSet.Format.OffsetY * EMU + to := xlsxTo{} + to.Col = colEnd + to.ColOff = x2 * EMU + to.Row = rowEnd + to.RowOff = y2 * EMU + twoCellAnchor.From = &from + twoCellAnchor.To = &to + shape := xdrSp{ + NvSpPr: &xdrNvSpPr{ + CNvPr: &xlsxCNvPr{ + ID: cNvPrID, + Name: "Shape " + strconv.Itoa(cNvPrID), + }, + CNvSpPr: &xdrCNvSpPr{ + TxBox: true, + }, + }, + SpPr: &xlsxSpPr{ + PrstGeom: xlsxPrstGeom{ + Prst: formatSet.Type, + }, + }, + Style: &xdrStyle{ + LnRef: setShapeRef(formatSet.Color.Line, 2), + FillRef: setShapeRef(formatSet.Color.Fill, 1), + EffectRef: setShapeRef(formatSet.Color.Effect, 0), + FontRef: &aFontRef{ + Idx: "minor", + SchemeClr: &attrValString{ + Val: stringPtr("tx1"), + }, + }, + }, + TxBody: &xdrTxBody{ + BodyPr: &aBodyPr{ + VertOverflow: "clip", + HorzOverflow: "clip", + Wrap: "none", + RtlCol: false, + Anchor: "t", + }, + }, + } + if formatSet.Line.Width != 1 { + shape.SpPr.Ln = xlsxLineProperties{ + W: f.ptToEMUs(formatSet.Line.Width), + } + } + if len(formatSet.Paragraph) < 1 { + formatSet.Paragraph = []formatShapeParagraph{ + { + Font: Font{ + Bold: false, + Italic: false, + Underline: "none", + Family: f.GetDefaultFont(), + Size: 11, + Color: "#000000", + }, + Text: " ", + }, + } + } + for _, p := range formatSet.Paragraph { + u := p.Font.Underline + _, ok := textUnderlineType[u] + if !ok { + u = "none" + } + text := p.Text + if text == "" { + text = " " + } + paragraph := &aP{ + R: &aR{ + RPr: aRPr{ + I: p.Font.Italic, + B: p.Font.Bold, + Lang: "en-US", + AltLang: "en-US", + U: u, + Sz: p.Font.Size * 100, + Latin: &aLatin{Typeface: p.Font.Family}, + }, + T: text, + }, + EndParaRPr: &aEndParaRPr{ + Lang: "en-US", + }, + } + srgbClr := strings.Replace(strings.ToUpper(p.Font.Color), "#", "", -1) + if len(srgbClr) == 6 { + paragraph.R.RPr.SolidFill = &aSolidFill{ + SrgbClr: &attrValString{ + Val: stringPtr(srgbClr), + }, + } + } + shape.TxBody.P = append(shape.TxBody.P, paragraph) + } + twoCellAnchor.Sp = &shape + twoCellAnchor.ClientData = &xdrClientData{ + FLocksWithSheet: formatSet.Format.FLocksWithSheet, + FPrintsWithSheet: formatSet.Format.FPrintsWithSheet, + } + content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) + f.Drawings.Store(drawingXML, content) + return err +} + +// setShapeRef provides a function to set color with hex model by given actual +// color value. +func setShapeRef(color string, i int) *aRef { + if color == "" { + return &aRef{ + Idx: 0, + ScrgbClr: &aScrgbClr{ + R: 0, + G: 0, + B: 0, + }, + } + } + return &aRef{ + Idx: i, + SrgbClr: &attrValString{ + Val: stringPtr(strings.Replace(strings.ToUpper(color), "#", "", -1)), + }, + } +} diff --git a/vendor/github.com/xuri/excelize/v2/sheet.go b/vendor/github.com/xuri/excelize/v2/sheet.go new file mode 100644 index 00000000..3986cd86 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/sheet.go @@ -0,0 +1,1833 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "unicode/utf16" + "unicode/utf8" + + "github.com/mohae/deepcopy" +) + +// NewSheet provides the function to create a new sheet by given a worksheet +// name and returns the index of the sheets in the workbook +// (spreadsheet) after it appended. Note that the worksheet names are not +// case-sensitive, when creating a new spreadsheet file, the default +// worksheet named `Sheet1` will be created. +func (f *File) NewSheet(name string) int { + // Check if the worksheet already exists + index := f.GetSheetIndex(name) + if index != -1 { + return index + } + f.DeleteSheet(name) + f.SheetCount++ + wb := f.workbookReader() + sheetID := 0 + for _, v := range wb.Sheets.Sheet { + if v.SheetID > sheetID { + sheetID = v.SheetID + } + } + sheetID++ + // Update docProps/app.xml + f.setAppXML() + // Update [Content_Types].xml + f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet) + // Create new sheet /xl/worksheets/sheet%d.xml + f.setSheet(sheetID, name) + // Update workbook.xml.rels + rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipWorkSheet, fmt.Sprintf("/xl/worksheets/sheet%d.xml", sheetID), "") + // Update workbook.xml + f.setWorkbook(name, sheetID, rID) + return f.GetSheetIndex(name) +} + +// contentTypesReader provides a function to get the pointer to the +// [Content_Types].xml structure after deserialization. +func (f *File) contentTypesReader() *xlsxTypes { + var err error + + if f.ContentTypes == nil { + f.ContentTypes = new(xlsxTypes) + f.ContentTypes.Lock() + defer f.ContentTypes.Unlock() + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))). + Decode(f.ContentTypes); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + } + return f.ContentTypes +} + +// contentTypesWriter provides a function to save [Content_Types].xml after +// serialize structure. +func (f *File) contentTypesWriter() { + if f.ContentTypes != nil { + output, _ := xml.Marshal(f.ContentTypes) + f.saveFileList(defaultXMLPathContentTypes, output) + } +} + +// getWorksheetPath construct a target XML as xl/worksheets/sheet%d by split +// path, compatible with different types of relative paths in +// workbook.xml.rels, for example: worksheets/sheet%d.xml +// and /xl/worksheets/sheet%d.xml +func (f *File) getWorksheetPath(relTarget string) (path string) { + path = filepath.ToSlash(strings.TrimPrefix( + strings.Replace(filepath.Clean(fmt.Sprintf("%s/%s", filepath.Dir(f.getWorkbookPath()), relTarget)), "\\", "/", -1), "/")) + if strings.HasPrefix(relTarget, "/") { + path = filepath.ToSlash(strings.TrimPrefix(strings.Replace(filepath.Clean(relTarget), "\\", "/", -1), "/")) + } + return path +} + +// mergeExpandedCols merge expanded columns. +func (f *File) mergeExpandedCols(ws *xlsxWorksheet) { + sort.Slice(ws.Cols.Col, func(i, j int) bool { + return ws.Cols.Col[i].Min < ws.Cols.Col[j].Min + }) + var columns []xlsxCol + for i, n := 0, len(ws.Cols.Col); i < n; { + left := i + for i++; i < n && reflect.DeepEqual( + xlsxCol{ + BestFit: ws.Cols.Col[i-1].BestFit, + Collapsed: ws.Cols.Col[i-1].Collapsed, + CustomWidth: ws.Cols.Col[i-1].CustomWidth, + Hidden: ws.Cols.Col[i-1].Hidden, + Max: ws.Cols.Col[i-1].Max + 1, + Min: ws.Cols.Col[i-1].Min + 1, + OutlineLevel: ws.Cols.Col[i-1].OutlineLevel, + Phonetic: ws.Cols.Col[i-1].Phonetic, + Style: ws.Cols.Col[i-1].Style, + Width: ws.Cols.Col[i-1].Width, + }, ws.Cols.Col[i]); i++ { + } + column := deepcopy.Copy(ws.Cols.Col[left]).(xlsxCol) + if left < i-1 { + column.Max = ws.Cols.Col[i-1].Min + } + columns = append(columns, column) + } + ws.Cols.Col = columns +} + +// workSheetWriter provides a function to save xl/worksheets/sheet%d.xml after +// serialize structure. +func (f *File) workSheetWriter() { + var arr []byte + buffer := bytes.NewBuffer(arr) + encoder := xml.NewEncoder(buffer) + f.Sheet.Range(func(p, ws interface{}) bool { + if ws != nil { + sheet := ws.(*xlsxWorksheet) + if sheet.MergeCells != nil && len(sheet.MergeCells.Cells) > 0 { + _ = f.mergeOverlapCells(sheet) + } + if sheet.Cols != nil && len(sheet.Cols.Col) > 0 { + f.mergeExpandedCols(sheet) + } + for k, v := range sheet.SheetData.Row { + sheet.SheetData.Row[k].C = trimCell(v.C) + } + if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil { + f.addNameSpaces(p.(string), SourceRelationship) + } + if sheet.DecodeAlternateContent != nil { + sheet.AlternateContent = &xlsxAlternateContent{ + Content: sheet.DecodeAlternateContent.Content, + XMLNSMC: SourceRelationshipCompatibility.Value, + } + } + sheet.DecodeAlternateContent = nil + // reusing buffer + _ = encoder.Encode(sheet) + f.saveFileList(p.(string), replaceRelationshipsBytes(f.replaceNameSpaceBytes(p.(string), buffer.Bytes()))) + ok := f.checked[p.(string)] + if ok { + f.Sheet.Delete(p.(string)) + f.checked[p.(string)] = false + } + buffer.Reset() + } + return true + }) +} + +// trimCell provides a function to trim blank cells which created by fillColumns. +func trimCell(column []xlsxC) []xlsxC { + rowFull := true + for i := range column { + rowFull = column[i].hasValue() && rowFull + } + if rowFull { + return column + } + col := make([]xlsxC, len(column)) + i := 0 + for _, c := range column { + if c.hasValue() { + col[i] = c + i++ + } + } + return col[:i] +} + +// setContentTypes provides a function to read and update property of contents +// type of the spreadsheet. +func (f *File) setContentTypes(partName, contentType string) { + content := f.contentTypesReader() + content.Lock() + defer content.Unlock() + content.Overrides = append(content.Overrides, xlsxOverride{ + PartName: partName, + ContentType: contentType, + }) +} + +// setSheet provides a function to update sheet property by given index. +func (f *File) setSheet(index int, name string) { + ws := xlsxWorksheet{ + Dimension: &xlsxDimension{Ref: "A1"}, + SheetViews: &xlsxSheetViews{ + SheetView: []xlsxSheetView{{WorkbookViewID: 0}}, + }, + } + sheetXMLPath := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml" + f.sheetMap[trimSheetName(name)] = sheetXMLPath + f.Sheet.Store(sheetXMLPath, &ws) + f.xmlAttr[sheetXMLPath] = []xml.Attr{NameSpaceSpreadSheet} +} + +// relsWriter provides a function to save relationships after +// serialize structure. +func (f *File) relsWriter() { + f.Relationships.Range(func(path, rel interface{}) bool { + if rel != nil { + output, _ := xml.Marshal(rel.(*xlsxRelationships)) + if strings.HasPrefix(path.(string), "xl/worksheets/sheet/rels/sheet") { + output = f.replaceNameSpaceBytes(path.(string), output) + } + f.saveFileList(path.(string), replaceRelationshipsBytes(output)) + } + return true + }) +} + +// setAppXML update docProps/app.xml file of XML. +func (f *File) setAppXML() { + f.saveFileList(defaultXMLPathDocPropsApp, []byte(templateDocpropsApp)) +} + +// replaceRelationshipsBytes; Some tools that read spreadsheet files have very +// strict requirements about the structure of the input XML. This function is +// a horrible hack to fix that after the XML marshalling is completed. +func replaceRelationshipsBytes(content []byte) []byte { + oldXmlns := []byte(`xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships`) + newXmlns := []byte("r") + return bytesReplace(content, oldXmlns, newXmlns, -1) +} + +// SetActiveSheet provides a function to set the default active sheet of the +// workbook by a given index. Note that the active index is different from the +// ID returned by function GetSheetMap(). It should be greater or equal to 0 +// and less than the total worksheet numbers. +func (f *File) SetActiveSheet(index int) { + if index < 0 { + index = 0 + } + wb := f.workbookReader() + for activeTab := range wb.Sheets.Sheet { + if activeTab == index { + if wb.BookViews == nil { + wb.BookViews = &xlsxBookViews{} + } + if len(wb.BookViews.WorkBookView) > 0 { + wb.BookViews.WorkBookView[0].ActiveTab = activeTab + } else { + wb.BookViews.WorkBookView = append(wb.BookViews.WorkBookView, xlsxWorkBookView{ + ActiveTab: activeTab, + }) + } + } + } + for idx, name := range f.GetSheetList() { + ws, err := f.workSheetReader(name) + if err != nil { + // Chartsheet or dialogsheet + return + } + if ws.SheetViews == nil { + ws.SheetViews = &xlsxSheetViews{ + SheetView: []xlsxSheetView{{WorkbookViewID: 0}}, + } + } + if len(ws.SheetViews.SheetView) > 0 { + ws.SheetViews.SheetView[0].TabSelected = false + } + if index == idx { + if len(ws.SheetViews.SheetView) > 0 { + ws.SheetViews.SheetView[0].TabSelected = true + } else { + ws.SheetViews.SheetView = append(ws.SheetViews.SheetView, xlsxSheetView{ + TabSelected: true, + }) + } + } + } +} + +// GetActiveSheetIndex provides a function to get active sheet index of the +// spreadsheet. If not found the active sheet will be return integer 0. +func (f *File) GetActiveSheetIndex() (index int) { + sheetID := f.getActiveSheetID() + wb := f.workbookReader() + if wb != nil { + for idx, sheet := range wb.Sheets.Sheet { + if sheet.SheetID == sheetID { + index = idx + return + } + } + } + return +} + +// getActiveSheetID provides a function to get active sheet ID of the +// spreadsheet. If not found the active sheet will be return integer 0. +func (f *File) getActiveSheetID() int { + wb := f.workbookReader() + if wb != nil { + if wb.BookViews != nil && len(wb.BookViews.WorkBookView) > 0 { + activeTab := wb.BookViews.WorkBookView[0].ActiveTab + if len(wb.Sheets.Sheet) > activeTab && wb.Sheets.Sheet[activeTab].SheetID != 0 { + return wb.Sheets.Sheet[activeTab].SheetID + } + } + if len(wb.Sheets.Sheet) >= 1 { + return wb.Sheets.Sheet[0].SheetID + } + } + return 0 +} + +// SetSheetName provides a function to set the worksheet name by given old and +// new worksheet names. Maximum 31 characters are allowed in sheet title and +// this function only changes the name of the sheet and will not update the +// sheet name in the formula or reference associated with the cell. So there +// may be problem formula error or reference missing. +func (f *File) SetSheetName(oldName, newName string) { + oldName = trimSheetName(oldName) + newName = trimSheetName(newName) + if newName == oldName { + return + } + content := f.workbookReader() + for k, v := range content.Sheets.Sheet { + if v.Name == oldName { + content.Sheets.Sheet[k].Name = newName + f.sheetMap[newName] = f.sheetMap[oldName] + delete(f.sheetMap, oldName) + } + } +} + +// GetSheetName provides a function to get the sheet name of the workbook by +// the given sheet index. If the given sheet index is invalid, it will return +// an empty string. +func (f *File) GetSheetName(index int) (name string) { + for idx, sheet := range f.GetSheetList() { + if idx == index { + name = sheet + return + } + } + return +} + +// getSheetID provides a function to get worksheet ID of the spreadsheet by +// given sheet name. If given worksheet name is invalid, will return an +// integer type value -1. +func (f *File) getSheetID(name string) int { + for sheetID, sheet := range f.GetSheetMap() { + if sheet == trimSheetName(name) { + return sheetID + } + } + return -1 +} + +// GetSheetIndex provides a function to get a sheet index of the workbook by +// the given sheet name, the sheet names are not case-sensitive. If the given +// sheet name is invalid or sheet doesn't exist, it will return an integer +// type value -1. +func (f *File) GetSheetIndex(name string) int { + for index, sheet := range f.GetSheetList() { + if strings.EqualFold(sheet, trimSheetName(name)) { + return index + } + } + return -1 +} + +// GetSheetMap provides a function to get worksheets, chart sheets, dialog +// sheets ID and name map of the workbook. For example: +// +// f, err := excelize.OpenFile("Book1.xlsx") +// if err != nil { +// return +// } +// defer func() { +// if err := f.Close(); err != nil { +// fmt.Println(err) +// } +// }() +// for index, name := range f.GetSheetMap() { +// fmt.Println(index, name) +// } +// +func (f *File) GetSheetMap() map[int]string { + wb := f.workbookReader() + sheetMap := map[int]string{} + if wb != nil { + for _, sheet := range wb.Sheets.Sheet { + sheetMap[sheet.SheetID] = sheet.Name + } + } + return sheetMap +} + +// GetSheetList provides a function to get worksheets, chart sheets, and +// dialog sheets name list of the workbook. +func (f *File) GetSheetList() (list []string) { + wb := f.workbookReader() + if wb != nil { + for _, sheet := range wb.Sheets.Sheet { + list = append(list, sheet.Name) + } + } + return +} + +// getSheetMap provides a function to get worksheet name and XML file path map +// of the spreadsheet. +func (f *File) getSheetMap() map[string]string { + maps := map[string]string{} + for _, v := range f.workbookReader().Sheets.Sheet { + for _, rel := range f.relsReader(f.getWorkbookRelsPath()).Relationships { + if rel.ID == v.ID { + sheetXMLPath := f.getWorksheetPath(rel.Target) + if _, ok := f.Pkg.Load(sheetXMLPath); ok { + maps[v.Name] = sheetXMLPath + } + if _, ok := f.tempFiles.Load(sheetXMLPath); ok { + maps[v.Name] = sheetXMLPath + } + } + } + } + return maps +} + +// SetSheetBackground provides a function to set background picture by given +// worksheet name and file path. +func (f *File) SetSheetBackground(sheet, picture string) error { + var err error + // Check picture exists first. + if _, err = os.Stat(picture); os.IsNotExist(err) { + return err + } + ext, ok := supportImageTypes[path.Ext(picture)] + if !ok { + return ErrImgExt + } + file, _ := ioutil.ReadFile(filepath.Clean(picture)) + name := f.addMedia(file, ext) + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") + f.addSheetPicture(sheet, rID) + f.addSheetNameSpace(sheet, SourceRelationship) + f.setContentTypePartImageExtensions() + return err +} + +// DeleteSheet provides a function to delete worksheet in a workbook by given +// worksheet name, the sheet names are not case-sensitive. The sheet names are +// not case-sensitive. Use this method with caution, which will affect +// changes in references such as formulas, charts, and so on. If there is any +// referenced value of the deleted worksheet, it will cause a file error when +// you open it. This function will be invalid when only the one worksheet is +// left. +func (f *File) DeleteSheet(name string) { + if f.SheetCount == 1 || f.GetSheetIndex(name) == -1 { + return + } + sheetName := trimSheetName(name) + wb := f.workbookReader() + wbRels := f.relsReader(f.getWorkbookRelsPath()) + activeSheetName := f.GetSheetName(f.GetActiveSheetIndex()) + deleteLocalSheetID := f.GetSheetIndex(name) + deleteAndAdjustDefinedNames(wb, deleteLocalSheetID) + + for idx, sheet := range wb.Sheets.Sheet { + if !strings.EqualFold(sheet.Name, sheetName) { + continue + } + + wb.Sheets.Sheet = append(wb.Sheets.Sheet[:idx], wb.Sheets.Sheet[idx+1:]...) + var sheetXML, rels string + if wbRels != nil { + for _, rel := range wbRels.Relationships { + if rel.ID == sheet.ID { + sheetXML = f.getWorksheetPath(rel.Target) + rels = "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[sheetName], "xl/worksheets/") + ".rels" + } + } + } + target := f.deleteSheetFromWorkbookRels(sheet.ID) + f.deleteSheetFromContentTypes(target) + f.deleteCalcChain(sheet.SheetID, "") + delete(f.sheetMap, sheet.Name) + f.Pkg.Delete(sheetXML) + f.Pkg.Delete(rels) + f.Relationships.Delete(rels) + f.Sheet.Delete(sheetXML) + delete(f.xmlAttr, sheetXML) + f.SheetCount-- + } + f.SetActiveSheet(f.GetSheetIndex(activeSheetName)) +} + +// deleteAndAdjustDefinedNames delete and adjust defined name in the workbook +// by given worksheet ID. +func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) { + if wb == nil || wb.DefinedNames == nil { + return + } + for idx := 0; idx < len(wb.DefinedNames.DefinedName); idx++ { + dn := wb.DefinedNames.DefinedName[idx] + if dn.LocalSheetID != nil { + localSheetID := *dn.LocalSheetID + if localSheetID == deleteLocalSheetID { + wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...) + idx-- + } else if localSheetID > deleteLocalSheetID { + wb.DefinedNames.DefinedName[idx].LocalSheetID = intPtr(*dn.LocalSheetID - 1) + } + } + } +} + +// deleteSheetFromWorkbookRels provides a function to remove worksheet +// relationships by given relationships ID in the file workbook.xml.rels. +func (f *File) deleteSheetFromWorkbookRels(rID string) string { + content := f.relsReader(f.getWorkbookRelsPath()) + content.Lock() + defer content.Unlock() + for k, v := range content.Relationships { + if v.ID == rID { + content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...) + return v.Target + } + } + return "" +} + +// deleteSheetFromContentTypes provides a function to remove worksheet +// relationships by given target name in the file [Content_Types].xml. +func (f *File) deleteSheetFromContentTypes(target string) { + if !strings.HasPrefix(target, "/") { + target = "/xl/" + target + } + content := f.contentTypesReader() + content.Lock() + defer content.Unlock() + for k, v := range content.Overrides { + if v.PartName == target { + content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) + } + } +} + +// CopySheet provides a function to duplicate a worksheet by gave source and +// target worksheet index. Note that currently doesn't support duplicate +// workbooks that contain tables, charts or pictures. For Example: +// +// // Sheet1 already exists... +// index := f.NewSheet("Sheet2") +// err := f.CopySheet(1, index) +// return err +// +func (f *File) CopySheet(from, to int) error { + if from < 0 || to < 0 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" { + return ErrSheetIdx + } + return f.copySheet(from, to) +} + +// copySheet provides a function to duplicate a worksheet by gave source and +// target worksheet name. +func (f *File) copySheet(from, to int) error { + fromSheet := f.GetSheetName(from) + sheet, err := f.workSheetReader(fromSheet) + if err != nil { + return err + } + worksheet := deepcopy.Copy(sheet).(*xlsxWorksheet) + toSheetID := strconv.Itoa(f.getSheetID(f.GetSheetName(to))) + sheetXMLPath := "xl/worksheets/sheet" + toSheetID + ".xml" + if len(worksheet.SheetViews.SheetView) > 0 { + worksheet.SheetViews.SheetView[0].TabSelected = false + } + worksheet.Drawing = nil + worksheet.TableParts = nil + worksheet.PageSetUp = nil + f.Sheet.Store(sheetXMLPath, worksheet) + toRels := "xl/worksheets/_rels/sheet" + toSheetID + ".xml.rels" + fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(f.getSheetID(fromSheet)) + ".xml.rels" + if rels, ok := f.Pkg.Load(fromRels); ok && rels != nil { + f.Pkg.Store(toRels, rels.([]byte)) + } + fromSheetXMLPath := f.sheetMap[trimSheetName(fromSheet)] + fromSheetAttr := f.xmlAttr[fromSheetXMLPath] + f.xmlAttr[sheetXMLPath] = fromSheetAttr + return err +} + +// SetSheetVisible provides a function to set worksheet visible by given worksheet +// name. A workbook must contain at least one visible worksheet. If the given +// worksheet has been activated, this setting will be invalidated. Sheet state +// values as defined by https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetstatevalues +// +// visible +// hidden +// veryHidden +// +// For example, hide Sheet1: +// +// err := f.SetSheetVisible("Sheet1", false) +// +func (f *File) SetSheetVisible(name string, visible bool) error { + name = trimSheetName(name) + content := f.workbookReader() + if visible { + for k, v := range content.Sheets.Sheet { + if v.Name == name { + content.Sheets.Sheet[k].State = "" + } + } + return nil + } + count := 0 + for _, v := range content.Sheets.Sheet { + if v.State != "hidden" { + count++ + } + } + for k, v := range content.Sheets.Sheet { + ws, err := f.workSheetReader(v.Name) + if err != nil { + return err + } + tabSelected := false + if len(ws.SheetViews.SheetView) > 0 { + tabSelected = ws.SheetViews.SheetView[0].TabSelected + } + if v.Name == name && count > 1 && !tabSelected { + content.Sheets.Sheet[k].State = "hidden" + } + } + return nil +} + +// parseFormatPanesSet provides a function to parse the panes settings. +func parseFormatPanesSet(formatSet string) (*formatPanes, error) { + format := formatPanes{} + err := json.Unmarshal([]byte(formatSet), &format) + return &format, err +} + +// SetPanes provides a function to create and remove freeze panes and split panes +// by given worksheet name and panes format set. +// +// activePane defines the pane that is active. The possible values for this +// attribute are defined in the following table: +// +// Enumeration Value | Description +// --------------------------------+------------------------------------------------------------- +// bottomLeft (Bottom Left Pane) | Bottom left pane, when both vertical and horizontal +// | splits are applied. +// | +// | This value is also used when only a horizontal split has +// | been applied, dividing the pane into upper and lower +// | regions. In that case, this value specifies the bottom +// | pane. +// | +// bottomRight (Bottom Right Pane) | Bottom right pane, when both vertical and horizontal +// | splits are applied. +// | +// topLeft (Top Left Pane) | Top left pane, when both vertical and horizontal splits +// | are applied. +// | +// | This value is also used when only a horizontal split has +// | been applied, dividing the pane into upper and lower +// | regions. In that case, this value specifies the top pane. +// | +// | This value is also used when only a vertical split has +// | been applied, dividing the pane into right and left +// | regions. In that case, this value specifies the left pane +// | +// topRight (Top Right Pane) | Top right pane, when both vertical and horizontal +// | splits are applied. +// | +// | This value is also used when only a vertical split has +// | been applied, dividing the pane into right and left +// | regions. In that case, this value specifies the right +// | pane. +// +// Pane state type is restricted to the values supported currently listed in the following table: +// +// Enumeration Value | Description +// --------------------------------+------------------------------------------------------------- +// frozen (Frozen) | Panes are frozen, but were not split being frozen. In +// | this state, when the panes are unfrozen again, a single +// | pane results, with no split. +// | +// | In this state, the split bars are not adjustable. +// | +// split (Split) | Panes are split, but not frozen. In this state, the split +// | bars are adjustable by the user. +// +// x_split (Horizontal Split Position): Horizontal position of the split, in +// 1/20th of a point; 0 (zero) if none. If the pane is frozen, this value +// indicates the number of columns visible in the top pane. +// +// y_split (Vertical Split Position): Vertical position of the split, in 1/20th +// of a point; 0 (zero) if none. If the pane is frozen, this value indicates the +// number of rows visible in the left pane. The possible values for this +// attribute are defined by the W3C XML Schema double datatype. +// +// top_left_cell: Location of the top left visible cell in the bottom right pane +// (when in Left-To-Right mode). +// +// sqref (Sequence of References): Range of the selection. Can be non-contiguous +// set of ranges. +// +// An example of how to freeze column A in the Sheet1 and set the active cell on +// Sheet1!K16: +// +// f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`) +// +// An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell +// ranges on Sheet1!A11:XFD11: +// +// f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`) +// +// An example of how to create split panes in the Sheet1 and set the active cell +// on Sheet1!J60: +// +// f.SetPanes("Sheet1", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`) +// +// An example of how to unfreeze and remove all panes on Sheet1: +// +// f.SetPanes("Sheet1", `{"freeze":false,"split":false}`) +// +func (f *File) SetPanes(sheet, panes string) error { + fs, _ := parseFormatPanesSet(panes) + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + p := &xlsxPane{ + ActivePane: fs.ActivePane, + TopLeftCell: fs.TopLeftCell, + XSplit: float64(fs.XSplit), + YSplit: float64(fs.YSplit), + } + if fs.Freeze { + p.State = "frozen" + } + ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = p + if !(fs.Freeze) && !(fs.Split) { + if len(ws.SheetViews.SheetView) > 0 { + ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil + } + } + var s []*xlsxSelection + for _, p := range fs.Panes { + s = append(s, &xlsxSelection{ + ActiveCell: p.ActiveCell, + Pane: p.Pane, + SQRef: p.SQRef, + }) + } + ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Selection = s + return err +} + +// GetSheetVisible provides a function to get worksheet visible by given worksheet +// name. For example, get visible state of Sheet1: +// +// f.GetSheetVisible("Sheet1") +// +func (f *File) GetSheetVisible(name string) bool { + content := f.workbookReader() + visible := false + for k, v := range content.Sheets.Sheet { + if v.Name == trimSheetName(name) { + if content.Sheets.Sheet[k].State == "" || content.Sheets.Sheet[k].State == "visible" { + visible = true + } + } + } + return visible +} + +// SearchSheet provides a function to get coordinates by given worksheet name, +// cell value, and regular expression. The function doesn't support searching +// on the calculated result, formatted numbers and conditional lookup +// currently. If it is a merged cell, it will return the coordinates of the +// upper left corner of the merged area. +// +// An example of search the coordinates of the value of "100" on Sheet1: +// +// result, err := f.SearchSheet("Sheet1", "100") +// +// An example of search the coordinates where the numerical value in the range +// of "0-9" of Sheet1 is described: +// +// result, err := f.SearchSheet("Sheet1", "[0-9]", true) +// +func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) { + var ( + regSearch bool + result []string + ) + for _, r := range reg { + regSearch = r + } + name, ok := f.sheetMap[trimSheetName(sheet)] + if !ok { + return result, ErrSheetNotExist{sheet} + } + if ws, ok := f.Sheet.Load(name); ok && ws != nil { + // flush data + output, _ := xml.Marshal(ws.(*xlsxWorksheet)) + f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) + } + return f.searchSheet(name, value, regSearch) +} + +// searchSheet provides a function to get coordinates by given worksheet name, +// cell value, and regular expression. +func (f *File) searchSheet(name, value string, regSearch bool) (result []string, err error) { + var ( + cellName, inElement string + cellCol, row int + d *xlsxSST + ) + + d = f.sharedStringsReader() + decoder := f.xmlNewDecoder(bytes.NewReader(f.readBytes(name))) + for { + var token xml.Token + token, err = decoder.Token() + if err != nil || token == nil { + if err == io.EOF { + err = nil + } + break + } + switch xmlElement := token.(type) { + case xml.StartElement: + inElement = xmlElement.Name.Local + if inElement == "row" { + row, err = attrValToInt("r", xmlElement.Attr) + if err != nil { + return + } + } + if inElement == "c" { + colCell := xlsxC{} + _ = decoder.DecodeElement(&colCell, &xmlElement) + val, _ := colCell.getValueFrom(f, d, false) + if regSearch { + regex := regexp.MustCompile(value) + if !regex.MatchString(val) { + continue + } + } else { + if val != value { + continue + } + } + cellCol, _, err = CellNameToCoordinates(colCell.R) + if err != nil { + return result, err + } + cellName, err = CoordinatesToCellName(cellCol, row) + if err != nil { + return result, err + } + result = append(result, cellName) + } + default: + } + } + return +} + +// attrValToInt provides a function to convert the local names to an integer +// by given XML attributes and specified names. +func attrValToInt(name string, attrs []xml.Attr) (val int, err error) { + for _, attr := range attrs { + if attr.Name.Local == name { + val, err = strconv.Atoi(attr.Value) + if err != nil { + return + } + } + } + return +} + +// SetHeaderFooter provides a function to set headers and footers by given +// worksheet name and the control characters. +// +// Headers and footers are specified using the following settings fields: +// +// Fields | Description +// ------------------+----------------------------------------------------------- +// AlignWithMargins | Align header footer margins with page margins +// DifferentFirst | Different first-page header and footer indicator +// DifferentOddEven | Different odd and even page headers and footers indicator +// ScaleWithDoc | Scale header and footer with document scaling +// OddFooter | Odd Page Footer +// OddHeader | Odd Header +// EvenFooter | Even Page Footer +// EvenHeader | Even Page Header +// FirstFooter | First Page Footer +// FirstHeader | First Page Header +// +// The following formatting codes can be used in 6 string type fields: +// OddHeader, OddFooter, EvenHeader, EvenFooter, FirstFooter, FirstHeader +// +// Formatting Code | Description +// ------------------------+------------------------------------------------------------------------- +// && | The character "&" +// | +// &font-size | Size of the text font, where font-size is a decimal font size in points +// | +// &"font name,font type" | A text font-name string, font name, and a text font-type string, +// | font type +// | +// &"-,Regular" | Regular text format. Toggles bold and italic modes to off +// | +// &A | Current worksheet's tab name +// | +// &B or &"-,Bold" | Bold text format, from off to on, or vice versa. The default mode is off +// | +// &D | Current date +// | +// &C | Center section +// | +// &E | Double-underline text format +// | +// &F | Current workbook's file name +// | +// &G | Drawing object as background +// | +// &H | Shadow text format +// | +// &I or &"-,Italic" | Italic text format +// | +// &K | Text font color +// | +// | An RGB Color is specified as RRGGBB +// | +// | A Theme Color is specified as TTSNNN where TT is the theme color Id, +// | S is either "+" or "-" of the tint/shade value, and NNN is the +// | tint/shade value +// | +// &L | Left section +// | +// &N | Total number of pages +// | +// &O | Outline text format +// | +// &P[[+|-]n] | Without the optional suffix, the current page number in decimal +// | +// &R | Right section +// | +// &S | Strikethrough text format +// | +// &T | Current time +// | +// &U | Single-underline text format. If double-underline mode is on, the next +// | occurrence in a section specifier toggles double-underline mode to off; +// | otherwise, it toggles single-underline mode, from off to on, or vice +// | versa. The default mode is off +// | +// &X | Superscript text format +// | +// &Y | Subscript text format +// | +// &Z | Current workbook's file path +// +// For example: +// +// err := f.SetHeaderFooter("Sheet1", &excelize.FormatHeaderFooter{ +// DifferentFirst: true, +// DifferentOddEven: true, +// OddHeader: "&R&P", +// OddFooter: "&C&F", +// EvenHeader: "&L&P", +// EvenFooter: "&L&D&R&T", +// FirstHeader: `&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D`, +// }) +// +// This example shows: +// +// - The first page has its own header and footer +// +// - Odd and even-numbered pages have different headers and footers +// +// - Current page number in the right section of odd-page headers +// +// - Current workbook's file name in the center section of odd-page footers +// +// - Current page number in the left section of even-page headers +// +// - Current date in the left section and the current time in the right section +// of even-page footers +// +// - The text "Center Bold Header" on the first line of the center section of +// the first page, and the date on the second line of the center section of +// that same page +// +// - No footer on the first page +// +func (f *File) SetHeaderFooter(sheet string, settings *FormatHeaderFooter) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if settings == nil { + ws.HeaderFooter = nil + return err + } + + v := reflect.ValueOf(*settings) + // Check 6 string type fields: OddHeader, OddFooter, EvenHeader, EvenFooter, + // FirstFooter, FirstHeader + for i := 4; i < v.NumField()-1; i++ { + if len(utf16.Encode([]rune(v.Field(i).String()))) > MaxFieldLength { + return newFieldLengthError(v.Type().Field(i).Name) + } + } + ws.HeaderFooter = &xlsxHeaderFooter{ + AlignWithMargins: settings.AlignWithMargins, + DifferentFirst: settings.DifferentFirst, + DifferentOddEven: settings.DifferentOddEven, + ScaleWithDoc: settings.ScaleWithDoc, + OddHeader: settings.OddHeader, + OddFooter: settings.OddFooter, + EvenHeader: settings.EvenHeader, + EvenFooter: settings.EvenFooter, + FirstFooter: settings.FirstFooter, + FirstHeader: settings.FirstHeader, + } + return err +} + +// ProtectSheet provides a function to prevent other users from accidentally +// or deliberately changing, moving, or deleting data in a worksheet. The +// optional field AlgorithmName specified hash algorithm, support XOR, MD4, +// MD5, SHA1, SHA256, SHA384, and SHA512 currently, if no hash algorithm +// specified, will be using the XOR algorithm as default. For example, +// protect Sheet1 with protection settings: +// +// err := f.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{ +// AlgorithmName: "SHA-512", +// Password: "password", +// EditScenarios: false, +// }) +// +func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if settings == nil { + settings = &FormatSheetProtection{ + EditObjects: true, + EditScenarios: true, + SelectLockedCells: true, + } + } + ws.SheetProtection = &xlsxSheetProtection{ + AutoFilter: settings.AutoFilter, + DeleteColumns: settings.DeleteColumns, + DeleteRows: settings.DeleteRows, + FormatCells: settings.FormatCells, + FormatColumns: settings.FormatColumns, + FormatRows: settings.FormatRows, + InsertColumns: settings.InsertColumns, + InsertHyperlinks: settings.InsertHyperlinks, + InsertRows: settings.InsertRows, + Objects: settings.EditObjects, + PivotTables: settings.PivotTables, + Scenarios: settings.EditScenarios, + SelectLockedCells: settings.SelectLockedCells, + SelectUnlockedCells: settings.SelectUnlockedCells, + Sheet: true, + Sort: settings.Sort, + } + if settings.Password != "" { + if settings.AlgorithmName == "" { + ws.SheetProtection.Password = genSheetPasswd(settings.Password) + return err + } + hashValue, saltValue, err := genISOPasswdHash(settings.Password, settings.AlgorithmName, "", int(sheetProtectionSpinCount)) + if err != nil { + return err + } + ws.SheetProtection.Password = "" + ws.SheetProtection.AlgorithmName = settings.AlgorithmName + ws.SheetProtection.SaltValue = saltValue + ws.SheetProtection.HashValue = hashValue + ws.SheetProtection.SpinCount = int(sheetProtectionSpinCount) + } + return err +} + +// UnprotectSheet provides a function to remove protection for a sheet, +// specified the second optional password parameter to remove sheet +// protection with password verification. +func (f *File) UnprotectSheet(sheet string, password ...string) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + // password verification + if len(password) > 0 { + if ws.SheetProtection == nil { + return ErrUnprotectSheet + } + if ws.SheetProtection.AlgorithmName == "" && ws.SheetProtection.Password != genSheetPasswd(password[0]) { + return ErrUnprotectSheetPassword + } + if ws.SheetProtection.AlgorithmName != "" { + // check with given salt value + hashValue, _, err := genISOPasswdHash(password[0], ws.SheetProtection.AlgorithmName, ws.SheetProtection.SaltValue, ws.SheetProtection.SpinCount) + if err != nil { + return err + } + if ws.SheetProtection.HashValue != hashValue { + return ErrUnprotectSheetPassword + } + } + } + ws.SheetProtection = nil + return err +} + +// trimSheetName provides a function to trim invalid characters by given worksheet +// name. +func trimSheetName(name string) string { + if strings.ContainsAny(name, ":\\/?*[]") || utf8.RuneCountInString(name) > 31 { + r := make([]rune, 0, 31) + for _, v := range name { + switch v { + case 58, 92, 47, 63, 42, 91, 93: // replace :\/?*[] + continue + default: + r = append(r, v) + } + if len(r) == 31 { + break + } + } + name = string(r) + } + return name +} + +// PageLayoutOption is an option of a page layout of a worksheet. See +// SetPageLayout(). +type PageLayoutOption interface { + setPageLayout(layout *xlsxPageSetUp) +} + +// PageLayoutOptionPtr is a writable PageLayoutOption. See GetPageLayout(). +type PageLayoutOptionPtr interface { + PageLayoutOption + getPageLayout(layout *xlsxPageSetUp) +} + +type ( + // BlackAndWhite specified print black and white. + BlackAndWhite bool + // FirstPageNumber specified the first printed page number. If no value is + // specified, then 'automatic' is assumed. + FirstPageNumber uint + // PageLayoutOrientation defines the orientation of page layout for a + // worksheet. + PageLayoutOrientation string + // PageLayoutPaperSize defines the paper size of the worksheet. + PageLayoutPaperSize int + // FitToHeight specified the number of vertical pages to fit on. + FitToHeight int + // FitToWidth specified the number of horizontal pages to fit on. + FitToWidth int + // PageLayoutScale defines the print scaling. This attribute is restricted + // to value ranging from 10 (10%) to 400 (400%). This setting is + // overridden when fitToWidth and/or fitToHeight are in use. + PageLayoutScale uint +) + +const ( + // OrientationPortrait indicates page layout orientation id portrait. + OrientationPortrait = "portrait" + // OrientationLandscape indicates page layout orientation id landscape. + OrientationLandscape = "landscape" +) + +// setPageLayout provides a method to set the print black and white for the +// worksheet. +func (p BlackAndWhite) setPageLayout(ps *xlsxPageSetUp) { + ps.BlackAndWhite = bool(p) +} + +// getPageLayout provides a method to get the print black and white for the +// worksheet. +func (p *BlackAndWhite) getPageLayout(ps *xlsxPageSetUp) { + if ps == nil { + *p = false + return + } + *p = BlackAndWhite(ps.BlackAndWhite) +} + +// setPageLayout provides a method to set the first printed page number for +// the worksheet. +func (p FirstPageNumber) setPageLayout(ps *xlsxPageSetUp) { + if 0 < int(p) { + ps.FirstPageNumber = strconv.Itoa(int(p)) + ps.UseFirstPageNumber = true + } +} + +// getPageLayout provides a method to get the first printed page number for +// the worksheet. +func (p *FirstPageNumber) getPageLayout(ps *xlsxPageSetUp) { + if ps != nil && ps.UseFirstPageNumber { + if number, _ := strconv.Atoi(ps.FirstPageNumber); number != 0 { + *p = FirstPageNumber(number) + return + } + } + *p = 1 +} + +// setPageLayout provides a method to set the orientation for the worksheet. +func (o PageLayoutOrientation) setPageLayout(ps *xlsxPageSetUp) { + ps.Orientation = string(o) +} + +// getPageLayout provides a method to get the orientation for the worksheet. +func (o *PageLayoutOrientation) getPageLayout(ps *xlsxPageSetUp) { + // Excel default: portrait + if ps == nil || ps.Orientation == "" { + *o = OrientationPortrait + return + } + *o = PageLayoutOrientation(ps.Orientation) +} + +// setPageLayout provides a method to set the paper size for the worksheet. +func (p PageLayoutPaperSize) setPageLayout(ps *xlsxPageSetUp) { + ps.PaperSize = intPtr(int(p)) +} + +// getPageLayout provides a method to get the paper size for the worksheet. +func (p *PageLayoutPaperSize) getPageLayout(ps *xlsxPageSetUp) { + // Excel default: 1 + if ps == nil || ps.PaperSize == nil { + *p = 1 + return + } + *p = PageLayoutPaperSize(*ps.PaperSize) +} + +// setPageLayout provides a method to set the fit to height for the worksheet. +func (p FitToHeight) setPageLayout(ps *xlsxPageSetUp) { + if int(p) > 0 { + ps.FitToHeight = intPtr(int(p)) + } +} + +// getPageLayout provides a method to get the fit to height for the worksheet. +func (p *FitToHeight) getPageLayout(ps *xlsxPageSetUp) { + if ps == nil || ps.FitToHeight == nil { + *p = 1 + return + } + *p = FitToHeight(*ps.FitToHeight) +} + +// setPageLayout provides a method to set the fit to width for the worksheet. +func (p FitToWidth) setPageLayout(ps *xlsxPageSetUp) { + if int(p) > 0 { + ps.FitToWidth = intPtr(int(p)) + } +} + +// getPageLayout provides a method to get the fit to width for the worksheet. +func (p *FitToWidth) getPageLayout(ps *xlsxPageSetUp) { + if ps == nil || ps.FitToWidth == nil { + *p = 1 + return + } + *p = FitToWidth(*ps.FitToWidth) +} + +// setPageLayout provides a method to set the scale for the worksheet. +func (p PageLayoutScale) setPageLayout(ps *xlsxPageSetUp) { + if 10 <= int(p) && int(p) <= 400 { + ps.Scale = int(p) + } +} + +// getPageLayout provides a method to get the scale for the worksheet. +func (p *PageLayoutScale) getPageLayout(ps *xlsxPageSetUp) { + if ps == nil || ps.Scale < 10 || ps.Scale > 400 { + *p = 100 + return + } + *p = PageLayoutScale(ps.Scale) +} + +// SetPageLayout provides a function to sets worksheet page layout. +// +// Available options: +// +// BlackAndWhite(bool) +// FirstPageNumber(uint) +// PageLayoutOrientation(string) +// PageLayoutPaperSize(int) +// FitToHeight(int) +// FitToWidth(int) +// PageLayoutScale(uint) +// +// The following shows the paper size sorted by excelize index number: +// +// Index | Paper Size +// -------+----------------------------------------------- +// 1 | Letter paper (8.5 in. by 11 in.) +// 2 | Letter small paper (8.5 in. by 11 in.) +// 3 | Tabloid paper (11 in. by 17 in.) +// 4 | Ledger paper (17 in. by 11 in.) +// 5 | Legal paper (8.5 in. by 14 in.) +// 6 | Statement paper (5.5 in. by 8.5 in.) +// 7 | Executive paper (7.25 in. by 10.5 in.) +// 8 | A3 paper (297 mm by 420 mm) +// 9 | A4 paper (210 mm by 297 mm) +// 10 | A4 small paper (210 mm by 297 mm) +// 11 | A5 paper (148 mm by 210 mm) +// 12 | B4 paper (250 mm by 353 mm) +// 13 | B5 paper (176 mm by 250 mm) +// 14 | Folio paper (8.5 in. by 13 in.) +// 15 | Quarto paper (215 mm by 275 mm) +// 16 | Standard paper (10 in. by 14 in.) +// 17 | Standard paper (11 in. by 17 in.) +// 18 | Note paper (8.5 in. by 11 in.) +// 19 | #9 envelope (3.875 in. by 8.875 in.) +// 20 | #10 envelope (4.125 in. by 9.5 in.) +// 21 | #11 envelope (4.5 in. by 10.375 in.) +// 22 | #12 envelope (4.75 in. by 11 in.) +// 23 | #14 envelope (5 in. by 11.5 in.) +// 24 | C paper (17 in. by 22 in.) +// 25 | D paper (22 in. by 34 in.) +// 26 | E paper (34 in. by 44 in.) +// 27 | DL envelope (110 mm by 220 mm) +// 28 | C5 envelope (162 mm by 229 mm) +// 29 | C3 envelope (324 mm by 458 mm) +// 30 | C4 envelope (229 mm by 324 mm) +// 31 | C6 envelope (114 mm by 162 mm) +// 32 | C65 envelope (114 mm by 229 mm) +// 33 | B4 envelope (250 mm by 353 mm) +// 34 | B5 envelope (176 mm by 250 mm) +// 35 | B6 envelope (176 mm by 125 mm) +// 36 | Italy envelope (110 mm by 230 mm) +// 37 | Monarch envelope (3.875 in. by 7.5 in.). +// 38 | 6 3/4 envelope (3.625 in. by 6.5 in.) +// 39 | US standard fanfold (14.875 in. by 11 in.) +// 40 | German standard fanfold (8.5 in. by 12 in.) +// 41 | German legal fanfold (8.5 in. by 13 in.) +// 42 | ISO B4 (250 mm by 353 mm) +// 43 | Japanese postcard (100 mm by 148 mm) +// 44 | Standard paper (9 in. by 11 in.) +// 45 | Standard paper (10 in. by 11 in.) +// 46 | Standard paper (15 in. by 11 in.) +// 47 | Invite envelope (220 mm by 220 mm) +// 50 | Letter extra paper (9.275 in. by 12 in.) +// 51 | Legal extra paper (9.275 in. by 15 in.) +// 52 | Tabloid extra paper (11.69 in. by 18 in.) +// 53 | A4 extra paper (236 mm by 322 mm) +// 54 | Letter transverse paper (8.275 in. by 11 in.) +// 55 | A4 transverse paper (210 mm by 297 mm) +// 56 | Letter extra transverse paper (9.275 in. by 12 in.) +// 57 | SuperA/SuperA/A4 paper (227 mm by 356 mm) +// 58 | SuperB/SuperB/A3 paper (305 mm by 487 mm) +// 59 | Letter plus paper (8.5 in. by 12.69 in.) +// 60 | A4 plus paper (210 mm by 330 mm) +// 61 | A5 transverse paper (148 mm by 210 mm) +// 62 | JIS B5 transverse paper (182 mm by 257 mm) +// 63 | A3 extra paper (322 mm by 445 mm) +// 64 | A5 extra paper (174 mm by 235 mm) +// 65 | ISO B5 extra paper (201 mm by 276 mm) +// 66 | A2 paper (420 mm by 594 mm) +// 67 | A3 transverse paper (297 mm by 420 mm) +// 68 | A3 extra transverse paper (322 mm by 445 mm) +// 69 | Japanese Double Postcard (200 mm x 148 mm) +// 70 | A6 (105 mm x 148 mm) +// 71 | Japanese Envelope Kaku #2 +// 72 | Japanese Envelope Kaku #3 +// 73 | Japanese Envelope Chou #3 +// 74 | Japanese Envelope Chou #4 +// 75 | Letter Rotated (11in x 8 1/2 11 in) +// 76 | A3 Rotated (420 mm x 297 mm) +// 77 | A4 Rotated (297 mm x 210 mm) +// 78 | A5 Rotated (210 mm x 148 mm) +// 79 | B4 (JIS) Rotated (364 mm x 257 mm) +// 80 | B5 (JIS) Rotated (257 mm x 182 mm) +// 81 | Japanese Postcard Rotated (148 mm x 100 mm) +// 82 | Double Japanese Postcard Rotated (148 mm x 200 mm) +// 83 | A6 Rotated (148 mm x 105 mm) +// 84 | Japanese Envelope Kaku #2 Rotated +// 85 | Japanese Envelope Kaku #3 Rotated +// 86 | Japanese Envelope Chou #3 Rotated +// 87 | Japanese Envelope Chou #4 Rotated +// 88 | B6 (JIS) (128 mm x 182 mm) +// 89 | B6 (JIS) Rotated (182 mm x 128 mm) +// 90 | (12 in x 11 in) +// 91 | Japanese Envelope You #4 +// 92 | Japanese Envelope You #4 Rotated +// 93 | PRC 16K (146 mm x 215 mm) +// 94 | PRC 32K (97 mm x 151 mm) +// 95 | PRC 32K(Big) (97 mm x 151 mm) +// 96 | PRC Envelope #1 (102 mm x 165 mm) +// 97 | PRC Envelope #2 (102 mm x 176 mm) +// 98 | PRC Envelope #3 (125 mm x 176 mm) +// 99 | PRC Envelope #4 (110 mm x 208 mm) +// 100 | PRC Envelope #5 (110 mm x 220 mm) +// 101 | PRC Envelope #6 (120 mm x 230 mm) +// 102 | PRC Envelope #7 (160 mm x 230 mm) +// 103 | PRC Envelope #8 (120 mm x 309 mm) +// 104 | PRC Envelope #9 (229 mm x 324 mm) +// 105 | PRC Envelope #10 (324 mm x 458 mm) +// 106 | PRC 16K Rotated +// 107 | PRC 32K Rotated +// 108 | PRC 32K(Big) Rotated +// 109 | PRC Envelope #1 Rotated (165 mm x 102 mm) +// 110 | PRC Envelope #2 Rotated (176 mm x 102 mm) +// 111 | PRC Envelope #3 Rotated (176 mm x 125 mm) +// 112 | PRC Envelope #4 Rotated (208 mm x 110 mm) +// 113 | PRC Envelope #5 Rotated (220 mm x 110 mm) +// 114 | PRC Envelope #6 Rotated (230 mm x 120 mm) +// 115 | PRC Envelope #7 Rotated (230 mm x 160 mm) +// 116 | PRC Envelope #8 Rotated (309 mm x 120 mm) +// 117 | PRC Envelope #9 Rotated (324 mm x 229 mm) +// 118 | PRC Envelope #10 Rotated (458 mm x 324 mm) +// +func (f *File) SetPageLayout(sheet string, opts ...PageLayoutOption) error { + s, err := f.workSheetReader(sheet) + if err != nil { + return err + } + ps := s.PageSetUp + if ps == nil { + ps = new(xlsxPageSetUp) + s.PageSetUp = ps + } + + for _, opt := range opts { + opt.setPageLayout(ps) + } + return err +} + +// GetPageLayout provides a function to gets worksheet page layout. +// +// Available options: +// PageLayoutOrientation(string) +// PageLayoutPaperSize(int) +// FitToHeight(int) +// FitToWidth(int) +func (f *File) GetPageLayout(sheet string, opts ...PageLayoutOptionPtr) error { + s, err := f.workSheetReader(sheet) + if err != nil { + return err + } + ps := s.PageSetUp + + for _, opt := range opts { + opt.getPageLayout(ps) + } + return err +} + +// SetDefinedName provides a function to set the defined names of the workbook +// or worksheet. If not specified scope, the default scope is workbook. +// For example: +// +// f.SetDefinedName(&excelize.DefinedName{ +// Name: "Amount", +// RefersTo: "Sheet1!$A$2:$D$5", +// Comment: "defined name comment", +// Scope: "Sheet2", +// }) +// +func (f *File) SetDefinedName(definedName *DefinedName) error { + wb := f.workbookReader() + d := xlsxDefinedName{ + Name: definedName.Name, + Comment: definedName.Comment, + Data: definedName.RefersTo, + } + if definedName.Scope != "" { + if sheetIndex := f.GetSheetIndex(definedName.Scope); sheetIndex >= 0 { + d.LocalSheetID = &sheetIndex + } + } + if wb.DefinedNames != nil { + for _, dn := range wb.DefinedNames.DefinedName { + var scope string + if dn.LocalSheetID != nil { + scope = f.GetSheetName(*dn.LocalSheetID) + } + if scope == definedName.Scope && dn.Name == definedName.Name { + return ErrDefinedNameDuplicate + } + } + wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d) + return nil + } + wb.DefinedNames = &xlsxDefinedNames{ + DefinedName: []xlsxDefinedName{d}, + } + return nil +} + +// DeleteDefinedName provides a function to delete the defined names of the +// workbook or worksheet. If not specified scope, the default scope is +// workbook. For example: +// +// f.DeleteDefinedName(&excelize.DefinedName{ +// Name: "Amount", +// Scope: "Sheet2", +// }) +// +func (f *File) DeleteDefinedName(definedName *DefinedName) error { + wb := f.workbookReader() + if wb.DefinedNames != nil { + for idx, dn := range wb.DefinedNames.DefinedName { + scope := "Workbook" + deleteScope := definedName.Scope + if deleteScope == "" { + deleteScope = "Workbook" + } + if dn.LocalSheetID != nil { + scope = f.GetSheetName(*dn.LocalSheetID) + } + if scope == deleteScope && dn.Name == definedName.Name { + wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...) + return nil + } + } + } + return ErrDefinedNameScope +} + +// GetDefinedName provides a function to get the defined names of the workbook +// or worksheet. +func (f *File) GetDefinedName() []DefinedName { + var definedNames []DefinedName + wb := f.workbookReader() + if wb.DefinedNames != nil { + for _, dn := range wb.DefinedNames.DefinedName { + definedName := DefinedName{ + Name: dn.Name, + Comment: dn.Comment, + RefersTo: dn.Data, + Scope: "Workbook", + } + if dn.LocalSheetID != nil && *dn.LocalSheetID >= 0 { + definedName.Scope = f.GetSheetName(*dn.LocalSheetID) + } + definedNames = append(definedNames, definedName) + } + } + return definedNames +} + +// GroupSheets provides a function to group worksheets by given worksheets +// name. Group worksheets must contain an active worksheet. +func (f *File) GroupSheets(sheets []string) error { + // check an active worksheet in group worksheets + var inActiveSheet bool + activeSheet := f.GetActiveSheetIndex() + sheetMap := f.GetSheetList() + for idx, sheetName := range sheetMap { + for _, s := range sheets { + if s == sheetName && idx == activeSheet { + inActiveSheet = true + } + } + } + if !inActiveSheet { + return ErrGroupSheets + } + // check worksheet exists + var wss []*xlsxWorksheet + for _, sheet := range sheets { + worksheet, err := f.workSheetReader(sheet) + if err != nil { + return err + } + wss = append(wss, worksheet) + } + for _, ws := range wss { + sheetViews := ws.SheetViews.SheetView + if len(sheetViews) > 0 { + for idx := range sheetViews { + ws.SheetViews.SheetView[idx].TabSelected = true + } + continue + } + } + return nil +} + +// UngroupSheets provides a function to ungroup worksheets. +func (f *File) UngroupSheets() error { + activeSheet := f.GetActiveSheetIndex() + for index, sheet := range f.GetSheetList() { + if activeSheet == index { + continue + } + ws, _ := f.workSheetReader(sheet) + sheetViews := ws.SheetViews.SheetView + if len(sheetViews) > 0 { + for idx := range sheetViews { + ws.SheetViews.SheetView[idx].TabSelected = false + } + } + } + return nil +} + +// InsertPageBreak create a page break to determine where the printed page +// ends and where begins the next one by given worksheet name and axis, so the +// content before the page break will be printed on one page and after the +// page break on another. +func (f *File) InsertPageBreak(sheet, cell string) (err error) { + var ws *xlsxWorksheet + var row, col int + rowBrk, colBrk := -1, -1 + if ws, err = f.workSheetReader(sheet); err != nil { + return + } + if col, row, err = CellNameToCoordinates(cell); err != nil { + return + } + col-- + row-- + if col == row && col == 0 { + return + } + if ws.RowBreaks == nil { + ws.RowBreaks = &xlsxBreaks{} + } + if ws.ColBreaks == nil { + ws.ColBreaks = &xlsxBreaks{} + } + + for idx, brk := range ws.RowBreaks.Brk { + if brk.ID == row { + rowBrk = idx + } + } + for idx, brk := range ws.ColBreaks.Brk { + if brk.ID == col { + colBrk = idx + } + } + + if row != 0 && rowBrk == -1 { + ws.RowBreaks.Brk = append(ws.RowBreaks.Brk, &xlsxBrk{ + ID: row, + Max: 16383, + Man: true, + }) + ws.RowBreaks.ManualBreakCount++ + } + if col != 0 && colBrk == -1 { + ws.ColBreaks.Brk = append(ws.ColBreaks.Brk, &xlsxBrk{ + ID: col, + Max: 1048575, + Man: true, + }) + ws.ColBreaks.ManualBreakCount++ + } + ws.RowBreaks.Count = len(ws.RowBreaks.Brk) + ws.ColBreaks.Count = len(ws.ColBreaks.Brk) + return +} + +// RemovePageBreak remove a page break by given worksheet name and axis. +func (f *File) RemovePageBreak(sheet, cell string) (err error) { + var ws *xlsxWorksheet + var row, col int + if ws, err = f.workSheetReader(sheet); err != nil { + return + } + if col, row, err = CellNameToCoordinates(cell); err != nil { + return + } + col-- + row-- + if col == row && col == 0 { + return + } + removeBrk := func(ID int, brks []*xlsxBrk) []*xlsxBrk { + for i, brk := range brks { + if brk.ID == ID { + brks = append(brks[:i], brks[i+1:]...) + } + } + return brks + } + if ws.RowBreaks == nil || ws.ColBreaks == nil { + return + } + rowBrks := len(ws.RowBreaks.Brk) + colBrks := len(ws.ColBreaks.Brk) + if rowBrks > 0 && rowBrks == colBrks { + ws.RowBreaks.Brk = removeBrk(row, ws.RowBreaks.Brk) + ws.ColBreaks.Brk = removeBrk(col, ws.ColBreaks.Brk) + ws.RowBreaks.Count = len(ws.RowBreaks.Brk) + ws.ColBreaks.Count = len(ws.ColBreaks.Brk) + ws.RowBreaks.ManualBreakCount-- + ws.ColBreaks.ManualBreakCount-- + return + } + if rowBrks > 0 && rowBrks > colBrks { + ws.RowBreaks.Brk = removeBrk(row, ws.RowBreaks.Brk) + ws.RowBreaks.Count = len(ws.RowBreaks.Brk) + ws.RowBreaks.ManualBreakCount-- + return + } + if colBrks > 0 && colBrks > rowBrks { + ws.ColBreaks.Brk = removeBrk(col, ws.ColBreaks.Brk) + ws.ColBreaks.Count = len(ws.ColBreaks.Brk) + ws.ColBreaks.ManualBreakCount-- + } + return +} + +// relsReader provides a function to get the pointer to the structure +// after deserialization of xl/worksheets/_rels/sheet%d.xml.rels. +func (f *File) relsReader(path string) *xlsxRelationships { + var err error + rels, _ := f.Relationships.Load(path) + if rels == nil { + if _, ok := f.Pkg.Load(path); ok { + c := xlsxRelationships{} + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). + Decode(&c); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + f.Relationships.Store(path, &c) + } + } + if rels, _ = f.Relationships.Load(path); rels != nil { + return rels.(*xlsxRelationships) + } + return nil +} + +// fillSheetData ensures there are enough rows, and columns in the chosen +// row to accept data. Missing rows are backfilled and given their row number +// Uses the last populated row as a hint for the size of the next row to add +func prepareSheetXML(ws *xlsxWorksheet, col int, row int) { + ws.Lock() + defer ws.Unlock() + rowCount := len(ws.SheetData.Row) + sizeHint := 0 + var ht float64 + var customHeight bool + if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight { + ht = ws.SheetFormatPr.DefaultRowHeight + customHeight = true + } + if rowCount > 0 { + sizeHint = len(ws.SheetData.Row[rowCount-1].C) + } + if rowCount < row { + // append missing rows + for rowIdx := rowCount; rowIdx < row; rowIdx++ { + ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: rowIdx + 1, CustomHeight: customHeight, Ht: ht, C: make([]xlsxC, 0, sizeHint)}) + } + } + rowData := &ws.SheetData.Row[row-1] + fillColumns(rowData, col, row) +} + +// fillColumns fill cells in the column of the row as contiguous. +func fillColumns(rowData *xlsxRow, col, row int) { + cellCount := len(rowData.C) + if cellCount < col { + for colIdx := cellCount; colIdx < col; colIdx++ { + cellName, _ := CoordinatesToCellName(colIdx+1, row) + rowData.C = append(rowData.C, xlsxC{R: cellName}) + } + } +} + +// makeContiguousColumns make columns in specific rows as contiguous. +func makeContiguousColumns(ws *xlsxWorksheet, fromRow, toRow, colCount int) { + ws.Lock() + defer ws.Unlock() + for ; fromRow < toRow; fromRow++ { + rowData := &ws.SheetData.Row[fromRow-1] + fillColumns(rowData, colCount, fromRow) + } +} diff --git a/vendor/github.com/xuri/excelize/v2/sheetpr.go b/vendor/github.com/xuri/excelize/v2/sheetpr.go new file mode 100644 index 00000000..e8e6e9d4 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/sheetpr.go @@ -0,0 +1,580 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "strings" + +// SheetPrOption is an option of a view of a worksheet. See SetSheetPrOptions(). +type SheetPrOption interface { + setSheetPrOption(view *xlsxSheetPr) +} + +// SheetPrOptionPtr is a writable SheetPrOption. See GetSheetPrOptions(). +type SheetPrOptionPtr interface { + SheetPrOption + getSheetPrOption(view *xlsxSheetPr) +} + +type ( + // CodeName is an option used for SheetPrOption and WorkbookPrOption + CodeName string + // EnableFormatConditionsCalculation is a SheetPrOption + EnableFormatConditionsCalculation bool + // Published is a SheetPrOption + Published bool + // FitToPage is a SheetPrOption + FitToPage bool + // TabColor is a SheetPrOption + TabColor string + // AutoPageBreaks is a SheetPrOption + AutoPageBreaks bool + // OutlineSummaryBelow is an outlinePr, within SheetPr option + OutlineSummaryBelow bool +) + +// setSheetPrOption implements the SheetPrOption interface. +func (o OutlineSummaryBelow) setSheetPrOption(pr *xlsxSheetPr) { + if pr.OutlinePr == nil { + pr.OutlinePr = new(xlsxOutlinePr) + } + pr.OutlinePr.SummaryBelow = bool(o) +} + +// getSheetPrOption implements the SheetPrOptionPtr interface. +func (o *OutlineSummaryBelow) getSheetPrOption(pr *xlsxSheetPr) { + // Excel default: true + if pr == nil || pr.OutlinePr == nil { + *o = true + return + } + *o = OutlineSummaryBelow(defaultTrue(&pr.OutlinePr.SummaryBelow)) +} + +// setSheetPrOption implements the SheetPrOption interface and specifies a +// stable name of the sheet. +func (o CodeName) setSheetPrOption(pr *xlsxSheetPr) { + pr.CodeName = string(o) +} + +// getSheetPrOption implements the SheetPrOptionPtr interface and get the +// stable name of the sheet. +func (o *CodeName) getSheetPrOption(pr *xlsxSheetPr) { + if pr == nil { + *o = "" + return + } + *o = CodeName(pr.CodeName) +} + +// setSheetPrOption implements the SheetPrOption interface and flag indicating +// whether the conditional formatting calculations shall be evaluated. +func (o EnableFormatConditionsCalculation) setSheetPrOption(pr *xlsxSheetPr) { + pr.EnableFormatConditionsCalculation = boolPtr(bool(o)) +} + +// getSheetPrOption implements the SheetPrOptionPtr interface and get the +// settings of whether the conditional formatting calculations shall be +// evaluated. +func (o *EnableFormatConditionsCalculation) getSheetPrOption(pr *xlsxSheetPr) { + if pr == nil { + *o = true + return + } + *o = EnableFormatConditionsCalculation(defaultTrue(pr.EnableFormatConditionsCalculation)) +} + +// setSheetPrOption implements the SheetPrOption interface and flag indicating +// whether the worksheet is published. +func (o Published) setSheetPrOption(pr *xlsxSheetPr) { + pr.Published = boolPtr(bool(o)) +} + +// getSheetPrOption implements the SheetPrOptionPtr interface and get the +// settings of whether the worksheet is published. +func (o *Published) getSheetPrOption(pr *xlsxSheetPr) { + if pr == nil { + *o = true + return + } + *o = Published(defaultTrue(pr.Published)) +} + +// setSheetPrOption implements the SheetPrOption interface. +func (o FitToPage) setSheetPrOption(pr *xlsxSheetPr) { + if pr.PageSetUpPr == nil { + if !o { + return + } + pr.PageSetUpPr = new(xlsxPageSetUpPr) + } + pr.PageSetUpPr.FitToPage = bool(o) +} + +// getSheetPrOption implements the SheetPrOptionPtr interface. +func (o *FitToPage) getSheetPrOption(pr *xlsxSheetPr) { + // Excel default: false + if pr == nil || pr.PageSetUpPr == nil { + *o = false + return + } + *o = FitToPage(pr.PageSetUpPr.FitToPage) +} + +// setSheetPrOption implements the SheetPrOption interface and specifies a +// stable name of the sheet. +func (o TabColor) setSheetPrOption(pr *xlsxSheetPr) { + if pr.TabColor == nil { + if string(o) == "" { + return + } + pr.TabColor = new(xlsxTabColor) + } + pr.TabColor.RGB = getPaletteColor(string(o)) +} + +// getSheetPrOption implements the SheetPrOptionPtr interface and get the +// stable name of the sheet. +func (o *TabColor) getSheetPrOption(pr *xlsxSheetPr) { + if pr == nil || pr.TabColor == nil { + *o = "" + return + } + *o = TabColor(strings.TrimPrefix(pr.TabColor.RGB, "FF")) +} + +// setSheetPrOption implements the SheetPrOption interface. +func (o AutoPageBreaks) setSheetPrOption(pr *xlsxSheetPr) { + if pr.PageSetUpPr == nil { + if !o { + return + } + pr.PageSetUpPr = new(xlsxPageSetUpPr) + } + pr.PageSetUpPr.AutoPageBreaks = bool(o) +} + +// getSheetPrOption implements the SheetPrOptionPtr interface. +func (o *AutoPageBreaks) getSheetPrOption(pr *xlsxSheetPr) { + // Excel default: false + if pr == nil || pr.PageSetUpPr == nil { + *o = false + return + } + *o = AutoPageBreaks(pr.PageSetUpPr.AutoPageBreaks) +} + +// SetSheetPrOptions provides a function to sets worksheet properties. +// +// Available options: +// CodeName(string) +// EnableFormatConditionsCalculation(bool) +// Published(bool) +// FitToPage(bool) +// AutoPageBreaks(bool) +// OutlineSummaryBelow(bool) +func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error { + ws, err := f.workSheetReader(name) + if err != nil { + return err + } + pr := ws.SheetPr + if pr == nil { + pr = new(xlsxSheetPr) + ws.SheetPr = pr + } + + for _, opt := range opts { + opt.setSheetPrOption(pr) + } + return err +} + +// GetSheetPrOptions provides a function to gets worksheet properties. +// +// Available options: +// CodeName(string) +// EnableFormatConditionsCalculation(bool) +// Published(bool) +// FitToPage(bool) +// AutoPageBreaks(bool) +// OutlineSummaryBelow(bool) +func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error { + ws, err := f.workSheetReader(name) + if err != nil { + return err + } + pr := ws.SheetPr + + for _, opt := range opts { + opt.getSheetPrOption(pr) + } + return err +} + +type ( + // PageMarginBottom specifies the bottom margin for the page. + PageMarginBottom float64 + // PageMarginFooter specifies the footer margin for the page. + PageMarginFooter float64 + // PageMarginHeader specifies the header margin for the page. + PageMarginHeader float64 + // PageMarginLeft specifies the left margin for the page. + PageMarginLeft float64 + // PageMarginRight specifies the right margin for the page. + PageMarginRight float64 + // PageMarginTop specifies the top margin for the page. + PageMarginTop float64 +) + +// setPageMargins provides a method to set the bottom margin for the worksheet. +func (p PageMarginBottom) setPageMargins(pm *xlsxPageMargins) { + pm.Bottom = float64(p) +} + +// setPageMargins provides a method to get the bottom margin for the worksheet. +func (p *PageMarginBottom) getPageMargins(pm *xlsxPageMargins) { + // Excel default: 0.75 + if pm == nil || pm.Bottom == 0 { + *p = 0.75 + return + } + *p = PageMarginBottom(pm.Bottom) +} + +// setPageMargins provides a method to set the footer margin for the worksheet. +func (p PageMarginFooter) setPageMargins(pm *xlsxPageMargins) { + pm.Footer = float64(p) +} + +// setPageMargins provides a method to get the footer margin for the worksheet. +func (p *PageMarginFooter) getPageMargins(pm *xlsxPageMargins) { + // Excel default: 0.3 + if pm == nil || pm.Footer == 0 { + *p = 0.3 + return + } + *p = PageMarginFooter(pm.Footer) +} + +// setPageMargins provides a method to set the header margin for the worksheet. +func (p PageMarginHeader) setPageMargins(pm *xlsxPageMargins) { + pm.Header = float64(p) +} + +// setPageMargins provides a method to get the header margin for the worksheet. +func (p *PageMarginHeader) getPageMargins(pm *xlsxPageMargins) { + // Excel default: 0.3 + if pm == nil || pm.Header == 0 { + *p = 0.3 + return + } + *p = PageMarginHeader(pm.Header) +} + +// setPageMargins provides a method to set the left margin for the worksheet. +func (p PageMarginLeft) setPageMargins(pm *xlsxPageMargins) { + pm.Left = float64(p) +} + +// setPageMargins provides a method to get the left margin for the worksheet. +func (p *PageMarginLeft) getPageMargins(pm *xlsxPageMargins) { + // Excel default: 0.7 + if pm == nil || pm.Left == 0 { + *p = 0.7 + return + } + *p = PageMarginLeft(pm.Left) +} + +// setPageMargins provides a method to set the right margin for the worksheet. +func (p PageMarginRight) setPageMargins(pm *xlsxPageMargins) { + pm.Right = float64(p) +} + +// setPageMargins provides a method to get the right margin for the worksheet. +func (p *PageMarginRight) getPageMargins(pm *xlsxPageMargins) { + // Excel default: 0.7 + if pm == nil || pm.Right == 0 { + *p = 0.7 + return + } + *p = PageMarginRight(pm.Right) +} + +// setPageMargins provides a method to set the top margin for the worksheet. +func (p PageMarginTop) setPageMargins(pm *xlsxPageMargins) { + pm.Top = float64(p) +} + +// setPageMargins provides a method to get the top margin for the worksheet. +func (p *PageMarginTop) getPageMargins(pm *xlsxPageMargins) { + // Excel default: 0.75 + if pm == nil || pm.Top == 0 { + *p = 0.75 + return + } + *p = PageMarginTop(pm.Top) +} + +// PageMarginsOptions is an option of a page margin of a worksheet. See +// SetPageMargins(). +type PageMarginsOptions interface { + setPageMargins(layout *xlsxPageMargins) +} + +// PageMarginsOptionsPtr is a writable PageMarginsOptions. See +// GetPageMargins(). +type PageMarginsOptionsPtr interface { + PageMarginsOptions + getPageMargins(layout *xlsxPageMargins) +} + +// SetPageMargins provides a function to set worksheet page margins. +// +// Available options: +// PageMarginBottom(float64) +// PageMarginFooter(float64) +// PageMarginHeader(float64) +// PageMarginLeft(float64) +// PageMarginRight(float64) +// PageMarginTop(float64) +func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error { + s, err := f.workSheetReader(sheet) + if err != nil { + return err + } + pm := s.PageMargins + if pm == nil { + pm = new(xlsxPageMargins) + s.PageMargins = pm + } + + for _, opt := range opts { + opt.setPageMargins(pm) + } + return err +} + +// GetPageMargins provides a function to get worksheet page margins. +// +// Available options: +// PageMarginBottom(float64) +// PageMarginFooter(float64) +// PageMarginHeader(float64) +// PageMarginLeft(float64) +// PageMarginRight(float64) +// PageMarginTop(float64) +func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error { + s, err := f.workSheetReader(sheet) + if err != nil { + return err + } + pm := s.PageMargins + + for _, opt := range opts { + opt.getPageMargins(pm) + } + return err +} + +// SheetFormatPrOptions is an option of the formatting properties of a +// worksheet. See SetSheetFormatPr(). +type SheetFormatPrOptions interface { + setSheetFormatPr(formatPr *xlsxSheetFormatPr) +} + +// SheetFormatPrOptionsPtr is a writable SheetFormatPrOptions. See +// GetSheetFormatPr(). +type SheetFormatPrOptionsPtr interface { + SheetFormatPrOptions + getSheetFormatPr(formatPr *xlsxSheetFormatPr) +} + +type ( + // BaseColWidth specifies the number of characters of the maximum digit width + // of the normal style's font. This value does not include margin padding or + // extra padding for gridlines. It is only the number of characters. + BaseColWidth uint8 + // DefaultColWidth specifies the default column width measured as the number + // of characters of the maximum digit width of the normal style's font. + DefaultColWidth float64 + // DefaultRowHeight specifies the default row height measured in point size. + // Optimization so we don't have to write the height on all rows. This can be + // written out if most rows have custom height, to achieve the optimization. + DefaultRowHeight float64 + // CustomHeight specifies the custom height. + CustomHeight bool + // ZeroHeight specifies if rows are hidden. + ZeroHeight bool + // ThickTop specifies if rows have a thick top border by default. + ThickTop bool + // ThickBottom specifies if rows have a thick bottom border by default. + ThickBottom bool +) + +// setSheetFormatPr provides a method to set the number of characters of the +// maximum digit width of the normal style's font. +func (p BaseColWidth) setSheetFormatPr(fp *xlsxSheetFormatPr) { + fp.BaseColWidth = uint8(p) +} + +// setSheetFormatPr provides a method to set the number of characters of the +// maximum digit width of the normal style's font. +func (p *BaseColWidth) getSheetFormatPr(fp *xlsxSheetFormatPr) { + if fp == nil { + *p = 0 + return + } + *p = BaseColWidth(fp.BaseColWidth) +} + +// setSheetFormatPr provides a method to set the default column width measured +// as the number of characters of the maximum digit width of the normal +// style's font. +func (p DefaultColWidth) setSheetFormatPr(fp *xlsxSheetFormatPr) { + fp.DefaultColWidth = float64(p) +} + +// getSheetFormatPr provides a method to get the default column width measured +// as the number of characters of the maximum digit width of the normal +// style's font. +func (p *DefaultColWidth) getSheetFormatPr(fp *xlsxSheetFormatPr) { + if fp == nil { + *p = 0 + return + } + *p = DefaultColWidth(fp.DefaultColWidth) +} + +// setSheetFormatPr provides a method to set the default row height measured +// in point size. +func (p DefaultRowHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) { + fp.DefaultRowHeight = float64(p) +} + +// getSheetFormatPr provides a method to get the default row height measured +// in point size. +func (p *DefaultRowHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) { + if fp == nil { + *p = 15 + return + } + *p = DefaultRowHeight(fp.DefaultRowHeight) +} + +// setSheetFormatPr provides a method to set the custom height. +func (p CustomHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) { + fp.CustomHeight = bool(p) +} + +// getSheetFormatPr provides a method to get the custom height. +func (p *CustomHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) { + if fp == nil { + *p = false + return + } + *p = CustomHeight(fp.CustomHeight) +} + +// setSheetFormatPr provides a method to set if rows are hidden. +func (p ZeroHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) { + fp.ZeroHeight = bool(p) +} + +// getSheetFormatPr provides a method to get if rows are hidden. +func (p *ZeroHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) { + if fp == nil { + *p = false + return + } + *p = ZeroHeight(fp.ZeroHeight) +} + +// setSheetFormatPr provides a method to set if rows have a thick top border +// by default. +func (p ThickTop) setSheetFormatPr(fp *xlsxSheetFormatPr) { + fp.ThickTop = bool(p) +} + +// getSheetFormatPr provides a method to get if rows have a thick top border +// by default. +func (p *ThickTop) getSheetFormatPr(fp *xlsxSheetFormatPr) { + if fp == nil { + *p = false + return + } + *p = ThickTop(fp.ThickTop) +} + +// setSheetFormatPr provides a method to set if rows have a thick bottom +// border by default. +func (p ThickBottom) setSheetFormatPr(fp *xlsxSheetFormatPr) { + fp.ThickBottom = bool(p) +} + +// setSheetFormatPr provides a method to set if rows have a thick bottom +// border by default. +func (p *ThickBottom) getSheetFormatPr(fp *xlsxSheetFormatPr) { + if fp == nil { + *p = false + return + } + *p = ThickBottom(fp.ThickBottom) +} + +// SetSheetFormatPr provides a function to set worksheet formatting properties. +// +// Available options: +// BaseColWidth(uint8) +// DefaultColWidth(float64) +// DefaultRowHeight(float64) +// CustomHeight(bool) +// ZeroHeight(bool) +// ThickTop(bool) +// ThickBottom(bool) +func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOptions) error { + s, err := f.workSheetReader(sheet) + if err != nil { + return err + } + fp := s.SheetFormatPr + if fp == nil { + fp = new(xlsxSheetFormatPr) + s.SheetFormatPr = fp + } + for _, opt := range opts { + opt.setSheetFormatPr(fp) + } + return err +} + +// GetSheetFormatPr provides a function to get worksheet formatting properties. +// +// Available options: +// BaseColWidth(uint8) +// DefaultColWidth(float64) +// DefaultRowHeight(float64) +// CustomHeight(bool) +// ZeroHeight(bool) +// ThickTop(bool) +// ThickBottom(bool) +func (f *File) GetSheetFormatPr(sheet string, opts ...SheetFormatPrOptionsPtr) error { + s, err := f.workSheetReader(sheet) + if err != nil { + return err + } + fp := s.SheetFormatPr + for _, opt := range opts { + opt.getSheetFormatPr(fp) + } + return err +} diff --git a/vendor/github.com/xuri/excelize/v2/sheetview.go b/vendor/github.com/xuri/excelize/v2/sheetview.go new file mode 100644 index 00000000..bf8f0237 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/sheetview.go @@ -0,0 +1,241 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "fmt" + +// SheetViewOption is an option of a view of a worksheet. See +// SetSheetViewOptions(). +type SheetViewOption interface { + setSheetViewOption(view *xlsxSheetView) +} + +// SheetViewOptionPtr is a writable SheetViewOption. See +// GetSheetViewOptions(). +type SheetViewOptionPtr interface { + SheetViewOption + getSheetViewOption(view *xlsxSheetView) +} + +type ( + // DefaultGridColor is a SheetViewOption. It specifies a flag indicating + // that the consuming application should use the default grid lines color + // (system dependent). Overrides any color specified in colorId. + DefaultGridColor bool + // ShowFormulas is a SheetViewOption. It specifies a flag indicating + // whether this sheet should display formulas. + ShowFormulas bool + // ShowGridLines is a SheetViewOption. It specifies a flag indicating + // whether this sheet should display gridlines. + ShowGridLines bool + // ShowRowColHeaders is a SheetViewOption. It specifies a flag indicating + // whether the sheet should display row and column headings. + ShowRowColHeaders bool + // ShowZeros is a SheetViewOption. It specifies a flag indicating whether + // to "show a zero in cells that have zero value". When using a formula to + // reference another cell which is empty, the referenced value becomes 0 + // when the flag is true. (Default setting is true.) + ShowZeros bool + // RightToLeft is a SheetViewOption. It specifies a flag indicating whether + // the sheet is in 'right to left' display mode. When in this mode, Column + // A is on the far right, Column B ;is one column left of Column A, and so + // on. Also, information in cells is displayed in the Right to Left format. + RightToLeft bool + // ShowRuler is a SheetViewOption. It specifies a flag indicating this + // sheet should display ruler. + ShowRuler bool + // View is a SheetViewOption. It specifies a flag indicating how sheet is + // displayed, by default it uses empty string available options: normal, + // pageLayout, pageBreakPreview + View string + // TopLeftCell is a SheetViewOption. It specifies a location of the top + // left visible cell Location of the top left visible cell in the bottom + // right pane (when in Left-to-Right mode). + TopLeftCell string + // ZoomScale is a SheetViewOption. It specifies a window zoom magnification + // for current view representing percent values. This attribute is + // restricted to values ranging from 10 to 400. Horizontal & Vertical + // scale together. + ZoomScale float64 +) + +// Defaults for each option are described in XML schema for CT_SheetView + +func (o DefaultGridColor) setSheetViewOption(view *xlsxSheetView) { + view.DefaultGridColor = boolPtr(bool(o)) +} + +func (o *DefaultGridColor) getSheetViewOption(view *xlsxSheetView) { + *o = DefaultGridColor(defaultTrue(view.DefaultGridColor)) // Excel default: true +} + +func (o ShowFormulas) setSheetViewOption(view *xlsxSheetView) { + view.ShowFormulas = bool(o) // Excel default: false +} + +func (o *ShowFormulas) getSheetViewOption(view *xlsxSheetView) { + *o = ShowFormulas(view.ShowFormulas) // Excel default: false +} + +func (o ShowGridLines) setSheetViewOption(view *xlsxSheetView) { + view.ShowGridLines = boolPtr(bool(o)) +} + +func (o *ShowGridLines) getSheetViewOption(view *xlsxSheetView) { + *o = ShowGridLines(defaultTrue(view.ShowGridLines)) // Excel default: true +} + +func (o ShowRowColHeaders) setSheetViewOption(view *xlsxSheetView) { + view.ShowRowColHeaders = boolPtr(bool(o)) +} + +func (o *ShowRowColHeaders) getSheetViewOption(view *xlsxSheetView) { + *o = ShowRowColHeaders(defaultTrue(view.ShowRowColHeaders)) // Excel default: true +} + +func (o ShowZeros) setSheetViewOption(view *xlsxSheetView) { + view.ShowZeros = boolPtr(bool(o)) +} + +func (o *ShowZeros) getSheetViewOption(view *xlsxSheetView) { + *o = ShowZeros(defaultTrue(view.ShowZeros)) // Excel default: true +} + +func (o RightToLeft) setSheetViewOption(view *xlsxSheetView) { + view.RightToLeft = bool(o) // Excel default: false +} + +func (o *RightToLeft) getSheetViewOption(view *xlsxSheetView) { + *o = RightToLeft(view.RightToLeft) +} + +func (o ShowRuler) setSheetViewOption(view *xlsxSheetView) { + view.ShowRuler = boolPtr(bool(o)) +} + +func (o *ShowRuler) getSheetViewOption(view *xlsxSheetView) { + *o = ShowRuler(defaultTrue(view.ShowRuler)) // Excel default: true +} + +func (o View) setSheetViewOption(view *xlsxSheetView) { + view.View = string(o) +} + +func (o *View) getSheetViewOption(view *xlsxSheetView) { + if view.View != "" { + *o = View(view.View) + return + } + *o = "normal" +} + +func (o TopLeftCell) setSheetViewOption(view *xlsxSheetView) { + view.TopLeftCell = string(o) +} + +func (o *TopLeftCell) getSheetViewOption(view *xlsxSheetView) { + *o = TopLeftCell(view.TopLeftCell) +} + +func (o ZoomScale) setSheetViewOption(view *xlsxSheetView) { + // This attribute is restricted to values ranging from 10 to 400. + if float64(o) >= 10 && float64(o) <= 400 { + view.ZoomScale = float64(o) + } +} + +func (o *ZoomScale) getSheetViewOption(view *xlsxSheetView) { + *o = ZoomScale(view.ZoomScale) +} + +// getSheetView returns the SheetView object +func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error) { + ws, err := f.workSheetReader(sheet) + if err != nil { + return nil, err + } + if viewIndex < 0 { + if viewIndex < -len(ws.SheetViews.SheetView) { + return nil, fmt.Errorf("view index %d out of range", viewIndex) + } + viewIndex = len(ws.SheetViews.SheetView) + viewIndex + } else if viewIndex >= len(ws.SheetViews.SheetView) { + return nil, fmt.Errorf("view index %d out of range", viewIndex) + } + + return &(ws.SheetViews.SheetView[viewIndex]), err +} + +// SetSheetViewOptions sets sheet view options. The viewIndex may be negative +// and if so is counted backward (-1 is the last view). +// +// Available options: +// +// DefaultGridColor(bool) +// ShowFormulas(bool) +// ShowGridLines(bool) +// ShowRowColHeaders(bool) +// ShowZeros(bool) +// RightToLeft(bool) +// ShowRuler(bool) +// View(string) +// TopLeftCell(string) +// ZoomScale(float64) +// +// Example: +// +// err = f.SetSheetViewOptions("Sheet1", -1, ShowGridLines(false)) +// +func (f *File) SetSheetViewOptions(name string, viewIndex int, opts ...SheetViewOption) error { + view, err := f.getSheetView(name, viewIndex) + if err != nil { + return err + } + + for _, opt := range opts { + opt.setSheetViewOption(view) + } + return nil +} + +// GetSheetViewOptions gets the value of sheet view options. The viewIndex may +// be negative and if so is counted backward (-1 is the last view). +// +// Available options: +// +// DefaultGridColor(bool) +// ShowFormulas(bool) +// ShowGridLines(bool) +// ShowRowColHeaders(bool) +// ShowZeros(bool) +// RightToLeft(bool) +// ShowRuler(bool) +// View(string) +// TopLeftCell(string) +// ZoomScale(float64) +// +// Example: +// +// var showGridLines excelize.ShowGridLines +// err = f.GetSheetViewOptions("Sheet1", -1, &showGridLines) +// +func (f *File) GetSheetViewOptions(name string, viewIndex int, opts ...SheetViewOptionPtr) error { + view, err := f.getSheetView(name, viewIndex) + if err != nil { + return err + } + + for _, opt := range opts { + opt.getSheetViewOption(view) + } + return nil +} diff --git a/vendor/github.com/xuri/excelize/v2/sparkline.go b/vendor/github.com/xuri/excelize/v2/sparkline.go new file mode 100644 index 00000000..880724a4 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/sparkline.go @@ -0,0 +1,544 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/xml" + "io" + "strings" +) + +// addSparklineGroupByStyle provides a function to create x14:sparklineGroups +// element by given sparkline style ID. +func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup { + groups := []*xlsxX14SparklineGroup{ + { + ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262}, + ColorNegative: &xlsxTabColor{Theme: 5}, + ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262}, + ColorFirst: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921}, + ColorLast: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921}, + ColorHigh: &xlsxTabColor{Theme: 4}, + ColorLow: &xlsxTabColor{Theme: 4}, + }, // 0 + { + ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262}, + ColorNegative: &xlsxTabColor{Theme: 5}, + ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262}, + ColorFirst: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921}, + ColorLast: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921}, + ColorHigh: &xlsxTabColor{Theme: 4}, + ColorLow: &xlsxTabColor{Theme: 4}, + }, // 1 + { + ColorSeries: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262}, + ColorNegative: &xlsxTabColor{Theme: 6}, + ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262}, + ColorFirst: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921}, + ColorLast: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921}, + ColorHigh: &xlsxTabColor{Theme: 5}, + ColorLow: &xlsxTabColor{Theme: 5}, + }, // 2 + { + ColorSeries: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262}, + ColorNegative: &xlsxTabColor{Theme: 7}, + ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262}, + ColorFirst: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921}, + ColorLast: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921}, + ColorHigh: &xlsxTabColor{Theme: 6}, + ColorLow: &xlsxTabColor{Theme: 6}, + }, // 3 + { + ColorSeries: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262}, + ColorNegative: &xlsxTabColor{Theme: 8}, + ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262}, + ColorFirst: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921}, + ColorLast: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921}, + ColorHigh: &xlsxTabColor{Theme: 7}, + ColorLow: &xlsxTabColor{Theme: 7}, + }, // 4 + { + ColorSeries: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262}, + ColorNegative: &xlsxTabColor{Theme: 9}, + ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262}, + ColorFirst: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921}, + ColorLast: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921}, + ColorHigh: &xlsxTabColor{Theme: 8}, + ColorLow: &xlsxTabColor{Theme: 8}, + }, // 5 + { + ColorSeries: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262}, + ColorNegative: &xlsxTabColor{Theme: 4}, + ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262}, + ColorFirst: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921}, + ColorLast: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921}, + ColorHigh: &xlsxTabColor{Theme: 9}, + ColorLow: &xlsxTabColor{Theme: 9}, + }, // 6 + { + ColorSeries: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorNegative: &xlsxTabColor{Theme: 5}, + ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 5}, + ColorLow: &xlsxTabColor{Theme: 5}, + }, // 7 + { + ColorSeries: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorNegative: &xlsxTabColor{Theme: 6}, + ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + }, // 8 + { + ColorSeries: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorNegative: &xlsxTabColor{Theme: 7}, + ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + }, // 9 + { + ColorSeries: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorNegative: &xlsxTabColor{Theme: 8}, + ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + }, // 10 + { + ColorSeries: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorNegative: &xlsxTabColor{Theme: 9}, + ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + }, // 11 + { + ColorSeries: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorNegative: &xlsxTabColor{Theme: 4}, + ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + }, // 12 + { + ColorSeries: &xlsxTabColor{Theme: 4}, + ColorNegative: &xlsxTabColor{Theme: 5}, + ColorMarkers: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + }, // 13 + { + ColorSeries: &xlsxTabColor{Theme: 5}, + ColorNegative: &xlsxTabColor{Theme: 6}, + ColorMarkers: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + }, // 14 + { + ColorSeries: &xlsxTabColor{Theme: 6}, + ColorNegative: &xlsxTabColor{Theme: 7}, + ColorMarkers: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + }, // 15 + { + ColorSeries: &xlsxTabColor{Theme: 7}, + ColorNegative: &xlsxTabColor{Theme: 8}, + ColorMarkers: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + }, // 16 + { + ColorSeries: &xlsxTabColor{Theme: 8}, + ColorNegative: &xlsxTabColor{Theme: 9}, + ColorMarkers: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + }, // 17 + { + ColorSeries: &xlsxTabColor{Theme: 9}, + ColorNegative: &xlsxTabColor{Theme: 4}, + ColorMarkers: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + }, // 18 + { + ColorSeries: &xlsxTabColor{Theme: 4, Tint: 0.39997558519241921}, + ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262}, + ColorMarkers: &xlsxTabColor{Theme: 4, Tint: 0.79998168889431442}, + ColorFirst: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 4, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262}, + ColorLow: &xlsxTabColor{Theme: 4, Tint: -0.499984740745262}, + }, // 19 + { + ColorSeries: &xlsxTabColor{Theme: 5, Tint: 0.39997558519241921}, + ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262}, + ColorMarkers: &xlsxTabColor{Theme: 5, Tint: 0.79998168889431442}, + ColorFirst: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 5, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262}, + ColorLow: &xlsxTabColor{Theme: 5, Tint: -0.499984740745262}, + }, // 20 + { + ColorSeries: &xlsxTabColor{Theme: 6, Tint: 0.39997558519241921}, + ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262}, + ColorMarkers: &xlsxTabColor{Theme: 6, Tint: 0.79998168889431442}, + ColorFirst: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 6, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262}, + ColorLow: &xlsxTabColor{Theme: 6, Tint: -0.499984740745262}, + }, // 21 + { + ColorSeries: &xlsxTabColor{Theme: 7, Tint: 0.39997558519241921}, + ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262}, + ColorMarkers: &xlsxTabColor{Theme: 7, Tint: 0.79998168889431442}, + ColorFirst: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 7, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262}, + ColorLow: &xlsxTabColor{Theme: 7, Tint: -0.499984740745262}, + }, // 22 + { + ColorSeries: &xlsxTabColor{Theme: 8, Tint: 0.39997558519241921}, + ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262}, + ColorMarkers: &xlsxTabColor{Theme: 8, Tint: 0.79998168889431442}, + ColorFirst: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 8, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262}, + ColorLow: &xlsxTabColor{Theme: 8, Tint: -0.499984740745262}, + }, // 23 + { + ColorSeries: &xlsxTabColor{Theme: 9, Tint: 0.39997558519241921}, + ColorNegative: &xlsxTabColor{Theme: 0, Tint: -0.499984740745262}, + ColorMarkers: &xlsxTabColor{Theme: 9, Tint: 0.79998168889431442}, + ColorFirst: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 9, Tint: -0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262}, + ColorLow: &xlsxTabColor{Theme: 9, Tint: -0.499984740745262}, + }, // 24 + { + ColorSeries: &xlsxTabColor{Theme: 1, Tint: 0.499984740745262}, + ColorNegative: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893}, + ColorMarkers: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 1, Tint: 0.249977111117893}, + }, // 25 + { + ColorSeries: &xlsxTabColor{Theme: 1, Tint: 0.34998626667073579}, + ColorNegative: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893}, + ColorMarkers: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893}, + ColorFirst: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893}, + ColorLast: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893}, + ColorHigh: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893}, + ColorLow: &xlsxTabColor{Theme: 0, Tint: 0.249977111117893}, + }, // 26 + { + ColorSeries: &xlsxTabColor{RGB: "FF323232"}, + ColorNegative: &xlsxTabColor{RGB: "FFD00000"}, + ColorMarkers: &xlsxTabColor{RGB: "FFD00000"}, + ColorFirst: &xlsxTabColor{RGB: "FFD00000"}, + ColorLast: &xlsxTabColor{RGB: "FFD00000"}, + ColorHigh: &xlsxTabColor{RGB: "FFD00000"}, + ColorLow: &xlsxTabColor{RGB: "FFD00000"}, + }, // 27 + { + ColorSeries: &xlsxTabColor{RGB: "FF000000"}, + ColorNegative: &xlsxTabColor{RGB: "FF0070C0"}, + ColorMarkers: &xlsxTabColor{RGB: "FF0070C0"}, + ColorFirst: &xlsxTabColor{RGB: "FF0070C0"}, + ColorLast: &xlsxTabColor{RGB: "FF0070C0"}, + ColorHigh: &xlsxTabColor{RGB: "FF0070C0"}, + ColorLow: &xlsxTabColor{RGB: "FF0070C0"}, + }, // 28 + { + ColorSeries: &xlsxTabColor{RGB: "FF376092"}, + ColorNegative: &xlsxTabColor{RGB: "FFD00000"}, + ColorMarkers: &xlsxTabColor{RGB: "FFD00000"}, + ColorFirst: &xlsxTabColor{RGB: "FFD00000"}, + ColorLast: &xlsxTabColor{RGB: "FFD00000"}, + ColorHigh: &xlsxTabColor{RGB: "FFD00000"}, + ColorLow: &xlsxTabColor{RGB: "FFD00000"}, + }, // 29 + { + ColorSeries: &xlsxTabColor{RGB: "FF0070C0"}, + ColorNegative: &xlsxTabColor{RGB: "FF000000"}, + ColorMarkers: &xlsxTabColor{RGB: "FF000000"}, + ColorFirst: &xlsxTabColor{RGB: "FF000000"}, + ColorLast: &xlsxTabColor{RGB: "FF000000"}, + ColorHigh: &xlsxTabColor{RGB: "FF000000"}, + ColorLow: &xlsxTabColor{RGB: "FF000000"}, + }, // 30 + { + ColorSeries: &xlsxTabColor{RGB: "FF5F5F5F"}, + ColorNegative: &xlsxTabColor{RGB: "FFFFB620"}, + ColorMarkers: &xlsxTabColor{RGB: "FFD70077"}, + ColorFirst: &xlsxTabColor{RGB: "FF5687C2"}, + ColorLast: &xlsxTabColor{RGB: "FF359CEB"}, + ColorHigh: &xlsxTabColor{RGB: "FF56BE79"}, + ColorLow: &xlsxTabColor{RGB: "FFFF5055"}, + }, // 31 + { + ColorSeries: &xlsxTabColor{RGB: "FF5687C2"}, + ColorNegative: &xlsxTabColor{RGB: "FFFFB620"}, + ColorMarkers: &xlsxTabColor{RGB: "FFD70077"}, + ColorFirst: &xlsxTabColor{RGB: "FF777777"}, + ColorLast: &xlsxTabColor{RGB: "FF359CEB"}, + ColorHigh: &xlsxTabColor{RGB: "FF56BE79"}, + ColorLow: &xlsxTabColor{RGB: "FFFF5055"}, + }, // 32 + { + ColorSeries: &xlsxTabColor{RGB: "FFC6EFCE"}, + ColorNegative: &xlsxTabColor{RGB: "FFFFC7CE"}, + ColorMarkers: &xlsxTabColor{RGB: "FF8CADD6"}, + ColorFirst: &xlsxTabColor{RGB: "FFFFDC47"}, + ColorLast: &xlsxTabColor{RGB: "FFFFEB9C"}, + ColorHigh: &xlsxTabColor{RGB: "FF60D276"}, + ColorLow: &xlsxTabColor{RGB: "FFFF5367"}, + }, // 33 + { + ColorSeries: &xlsxTabColor{RGB: "FF00B050"}, + ColorNegative: &xlsxTabColor{RGB: "FFFF0000"}, + ColorMarkers: &xlsxTabColor{RGB: "FF0070C0"}, + ColorFirst: &xlsxTabColor{RGB: "FFFFC000"}, + ColorLast: &xlsxTabColor{RGB: "FFFFC000"}, + ColorHigh: &xlsxTabColor{RGB: "FF00B050"}, + ColorLow: &xlsxTabColor{RGB: "FFFF0000"}, + }, // 34 + { + ColorSeries: &xlsxTabColor{Theme: 3}, + ColorNegative: &xlsxTabColor{Theme: 9}, + ColorMarkers: &xlsxTabColor{Theme: 8}, + ColorFirst: &xlsxTabColor{Theme: 4}, + ColorLast: &xlsxTabColor{Theme: 5}, + ColorHigh: &xlsxTabColor{Theme: 6}, + ColorLow: &xlsxTabColor{Theme: 7}, + }, // 35 + { + ColorSeries: &xlsxTabColor{Theme: 1}, + ColorNegative: &xlsxTabColor{Theme: 9}, + ColorMarkers: &xlsxTabColor{Theme: 8}, + ColorFirst: &xlsxTabColor{Theme: 4}, + ColorLast: &xlsxTabColor{Theme: 5}, + ColorHigh: &xlsxTabColor{Theme: 6}, + ColorLow: &xlsxTabColor{Theme: 7}, + }, // 36 + } + return groups[ID] +} + +// AddSparkline provides a function to add sparklines to the worksheet by +// given formatting options. Sparklines are small charts that fit in a single +// cell and are used to show trends in data. Sparklines are a feature of Excel +// 2010 and later only. You can write them to an XLSX file that can be read by +// Excel 2007, but they won't be displayed. For example, add a grouped +// sparkline. Changes are applied to all three: +// +// err := f.AddSparkline("Sheet1", &excelize.SparklineOption{ +// Location: []string{"A1", "A2", "A3"}, +// Range: []string{"Sheet2!A1:J1", "Sheet2!A2:J2", "Sheet2!A3:J3"}, +// Markers: true, +// }) +// +// The following shows the formatting options of sparkline supported by excelize: +// +// Parameter | Description +// -----------+-------------------------------------------- +// Location | Required, must have the same number with 'Range' parameter +// Range | Required, must have the same number with 'Location' parameter +// Type | Enumeration value: line, column, win_loss +// Style | Value range: 0 - 35 +// Hight | Toggle sparkline high points +// Low | Toggle sparkline low points +// First | Toggle sparkline first points +// Last | Toggle sparkline last points +// Negative | Toggle sparkline negative points +// Markers | Toggle sparkline markers +// ColorAxis | An RGB Color is specified as RRGGBB +// Axis | Show sparkline axis +// +func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) { + var ( + ws *xlsxWorksheet + sparkType string + sparkTypes map[string]string + specifiedSparkTypes string + ok bool + group *xlsxX14SparklineGroup + groups *xlsxX14SparklineGroups + sparklineGroupsBytes, extBytes []byte + ) + + // parameter validation + if ws, err = f.parseFormatAddSparklineSet(sheet, opt); err != nil { + return + } + // Handle the sparkline type + sparkType = "line" + sparkTypes = map[string]string{"line": "line", "column": "column", "win_loss": "stacked"} + if opt.Type != "" { + if specifiedSparkTypes, ok = sparkTypes[opt.Type]; !ok { + err = ErrSparklineType + return + } + sparkType = specifiedSparkTypes + } + group = f.addSparklineGroupByStyle(opt.Style) + group.Type = sparkType + group.ColorAxis = &xlsxColor{RGB: "FF000000"} + group.DisplayEmptyCellsAs = "gap" + group.High = opt.High + group.Low = opt.Low + group.First = opt.First + group.Last = opt.Last + group.Negative = opt.Negative + group.DisplayXAxis = opt.Axis + group.Markers = opt.Markers + if opt.SeriesColor != "" { + group.ColorSeries = &xlsxTabColor{ + RGB: getPaletteColor(opt.SeriesColor), + } + } + if opt.Reverse { + group.RightToLeft = opt.Reverse + } + f.addSparkline(opt, group) + if ws.ExtLst.Ext != "" { // append mode ext + if err = f.appendSparkline(ws, group, groups); err != nil { + return + } + } else { + groups = &xlsxX14SparklineGroups{ + XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, + SparklineGroups: []*xlsxX14SparklineGroup{group}, + } + if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { + return + } + if extBytes, err = xml.Marshal(&xlsxWorksheetExt{ + URI: ExtURISparklineGroups, + Content: string(sparklineGroupsBytes), + }); err != nil { + return + } + ws.ExtLst.Ext = string(extBytes) + } + f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14) + return +} + +// parseFormatAddSparklineSet provides a function to validate sparkline +// properties. +func (f *File) parseFormatAddSparklineSet(sheet string, opt *SparklineOption) (*xlsxWorksheet, error) { + ws, err := f.workSheetReader(sheet) + if err != nil { + return ws, err + } + if opt == nil { + return ws, ErrParameterRequired + } + if len(opt.Location) < 1 { + return ws, ErrSparklineLocation + } + if len(opt.Range) < 1 { + return ws, ErrSparklineRange + } + // The ranges and locations must match.\ + if len(opt.Location) != len(opt.Range) { + return ws, ErrSparkline + } + if opt.Style < 0 || opt.Style > 35 { + return ws, ErrSparklineStyle + } + if ws.ExtLst == nil { + ws.ExtLst = &xlsxExtLst{} + } + return ws, err +} + +// addSparkline provides a function to create a sparkline in a sparkline group +// by given properties. +func (f *File) addSparkline(opt *SparklineOption, group *xlsxX14SparklineGroup) { + for idx, location := range opt.Location { + group.Sparklines.Sparkline = append(group.Sparklines.Sparkline, &xlsxX14Sparkline{ + F: opt.Range[idx], + Sqref: location, + }) + } +} + +// appendSparkline provides a function to append sparkline to sparkline +// groups. +func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, groups *xlsxX14SparklineGroups) (err error) { + var ( + idx int + decodeExtLst *decodeWorksheetExt + decodeSparklineGroups *decodeX14SparklineGroups + ext *xlsxWorksheetExt + sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte + ) + decodeExtLst = new(decodeWorksheetExt) + if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). + Decode(decodeExtLst); err != nil && err != io.EOF { + return + } + for idx, ext = range decodeExtLst.Ext { + if ext.URI == ExtURISparklineGroups { + decodeSparklineGroups = new(decodeX14SparklineGroups) + if err = f.xmlNewDecoder(strings.NewReader(ext.Content)). + Decode(decodeSparklineGroups); err != nil && err != io.EOF { + return + } + if sparklineGroupBytes, err = xml.Marshal(group); err != nil { + return + } + if groups == nil { + groups = &xlsxX14SparklineGroups{} + } + groups.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value + groups.Content = decodeSparklineGroups.Content + string(sparklineGroupBytes) + if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { + return + } + decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes) + } + } + if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil { + return + } + ws.ExtLst = &xlsxExtLst{ + Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), ""), + } + return +} diff --git a/vendor/github.com/xuri/excelize/v2/stream.go b/vendor/github.com/xuri/excelize/v2/stream.go new file mode 100644 index 00000000..c2eda68a --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/stream.go @@ -0,0 +1,626 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +// StreamWriter defined the type of stream writer. +type StreamWriter struct { + File *File + Sheet string + SheetID int + sheetWritten bool + cols string + worksheet *xlsxWorksheet + rawData bufferedWriter + mergeCellsCount int + mergeCells string + tableParts string +} + +// NewStreamWriter return stream writer struct by given worksheet name for +// generate new worksheet with large amounts of data. Note that after set +// rows, you must call the 'Flush' method to end the streaming writing +// process and ensure that the order of line numbers is ascending, the common +// API and stream API can't be work mixed to writing data on the worksheets, +// you can't get cell value when in-memory chunks data over 16MB. For +// example, set data for worksheet of size 102400 rows x 50 columns with +// numbers and style: +// +// file := excelize.NewFile() +// streamWriter, err := file.NewStreamWriter("Sheet1") +// if err != nil { +// fmt.Println(err) +// } +// styleID, err := file.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "#777777"}}) +// if err != nil { +// fmt.Println(err) +// } +// if err := streamWriter.SetRow("A1", []interface{}{excelize.Cell{StyleID: styleID, Value: "Data"}}, +// excelize.RowOpts{Height: 45, Hidden: false}); err != nil { +// fmt.Println(err) +// } +// for rowID := 2; rowID <= 102400; rowID++ { +// row := make([]interface{}, 50) +// for colID := 0; colID < 50; colID++ { +// row[colID] = rand.Intn(640000) +// } +// cell, _ := excelize.CoordinatesToCellName(1, rowID) +// if err := streamWriter.SetRow(cell, row); err != nil { +// fmt.Println(err) +// } +// } +// if err := streamWriter.Flush(); err != nil { +// fmt.Println(err) +// } +// if err := file.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// +// Set cell value and cell formula for a worksheet with stream writer: +// +// err := streamWriter.SetRow("A1", []interface{}{ +// excelize.Cell{Value: 1}, +// excelize.Cell{Value: 2}, +// excelize.Cell{Formula: "SUM(A1,B1)"}}); +// +// Set cell value and rows style for a worksheet with stream writer: +// +// err := streamWriter.SetRow("A1", []interface{}{ +// excelize.Cell{Value: 1}}, +// excelize.RowOpts{StyleID: styleID, Height: 20, Hidden: false}); +// +func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) { + sheetID := f.getSheetID(sheet) + if sheetID == -1 { + return nil, fmt.Errorf("sheet %s is not exist", sheet) + } + sw := &StreamWriter{ + File: f, + Sheet: sheet, + SheetID: sheetID, + } + var err error + sw.worksheet, err = f.workSheetReader(sheet) + if err != nil { + return nil, err + } + + sheetPath := f.sheetMap[trimSheetName(sheet)] + if f.streams == nil { + f.streams = make(map[string]*StreamWriter) + } + f.streams[sheetPath] = sw + + _, _ = sw.rawData.WriteString(xml.Header + ``, rID) + + sw.File.addContentTypePart(tableID, "table") + + b, _ := xml.Marshal(table) + sw.File.saveFileList(tableXML, b) + return nil +} + +// Extract values from a row in the StreamWriter. +func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err error) { + res = make([]string, vCol-hCol+1) + + r, err := sw.rawData.Reader() + if err != nil { + return nil, err + } + + dec := sw.File.xmlNewDecoder(r) + for { + token, err := dec.Token() + if err == io.EOF { + return res, nil + } + if err != nil { + return nil, err + } + startElement, ok := getRowElement(token, hRow) + if !ok { + continue + } + // decode cells + var row xlsxRow + if err := dec.DecodeElement(&row, &startElement); err != nil { + return nil, err + } + for _, c := range row.C { + col, _, err := CellNameToCoordinates(c.R) + if err != nil { + return nil, err + } + if col < hCol || col > vCol { + continue + } + res[col-hCol] = c.V + } + return res, nil + } +} + +// Check if the token is an XLSX row with the matching row number. +func getRowElement(token xml.Token, hRow int) (startElement xml.StartElement, ok bool) { + startElement, ok = token.(xml.StartElement) + if !ok { + return + } + ok = startElement.Name.Local == "row" + if !ok { + return + } + ok = false + for _, attr := range startElement.Attr { + if attr.Name.Local != "r" { + continue + } + row, _ := strconv.Atoi(attr.Value) + if row == hRow { + ok = true + return + } + } + return +} + +// Cell can be used directly in StreamWriter.SetRow to specify a style and +// a value. +type Cell struct { + StyleID int + Formula string + Value interface{} +} + +// RowOpts define the options for the set row, it can be used directly in +// StreamWriter.SetRow to specify the style and properties of the row. +type RowOpts struct { + Height float64 + Hidden bool + StyleID int +} + +// SetRow writes an array to stream rows by giving a worksheet name, starting +// coordinate and a pointer to an array of values. Note that you must call the +// 'Flush' method to end the streaming writing process. +// +// As a special case, if Cell is used as a value, then the Cell.StyleID will be +// applied to that cell. +func (sw *StreamWriter) SetRow(axis string, values []interface{}, opts ...RowOpts) error { + col, row, err := CellNameToCoordinates(axis) + if err != nil { + return err + } + if !sw.sheetWritten { + if len(sw.cols) > 0 { + _, _ = sw.rawData.WriteString("" + sw.cols + "") + } + _, _ = sw.rawData.WriteString(``) + sw.sheetWritten = true + } + attrs, err := marshalRowAttrs(opts...) + if err != nil { + return err + } + fmt.Fprintf(&sw.rawData, ``, row, attrs) + for i, val := range values { + axis, err := CoordinatesToCellName(col+i, row) + if err != nil { + return err + } + c := xlsxC{R: axis} + if v, ok := val.(Cell); ok { + c.S = v.StyleID + val = v.Value + setCellFormula(&c, v.Formula) + } else if v, ok := val.(*Cell); ok && v != nil { + c.S = v.StyleID + val = v.Value + setCellFormula(&c, v.Formula) + } + if err = sw.setCellValFunc(&c, val); err != nil { + _, _ = sw.rawData.WriteString(``) + return err + } + writeCell(&sw.rawData, c) + } + _, _ = sw.rawData.WriteString(``) + return sw.rawData.Sync() +} + +// marshalRowAttrs prepare attributes of the row by given options. +func marshalRowAttrs(opts ...RowOpts) (attrs string, err error) { + var opt *RowOpts + for i := range opts { + opt = &opts[i] + } + if opt == nil { + return + } + if opt.Height > MaxRowHeight { + err = ErrMaxRowHeight + return + } + if opt.StyleID > 0 { + attrs += fmt.Sprintf(` s="%d" customFormat="true"`, opt.StyleID) + } + if opt.Height > 0 { + attrs += fmt.Sprintf(` ht="%v" customHeight="true"`, opt.Height) + } + if opt.Hidden { + attrs += ` hidden="true"` + } + return +} + +// SetColWidth provides a function to set the width of a single column or +// multiple columns for the StreamWriter. Note that you must call +// the 'SetColWidth' function before the 'SetRow' function. For example set +// the width column B:C as 20: +// +// err := streamWriter.SetColWidth(2, 3, 20) +// +func (sw *StreamWriter) SetColWidth(min, max int, width float64) error { + if sw.sheetWritten { + return ErrStreamSetColWidth + } + if min > TotalColumns || max > TotalColumns { + return ErrColumnNumber + } + if min < 1 || max < 1 { + return ErrColumnNumber + } + if width > MaxColumnWidth { + return ErrColumnWidth + } + if min > max { + min, max = max, min + } + sw.cols += fmt.Sprintf(``, min, max, width) + return nil +} + +// MergeCell provides a function to merge cells by a given coordinate area for +// the StreamWriter. Don't create a merged cell that overlaps with another +// existing merged cell. +func (sw *StreamWriter) MergeCell(hCell, vCell string) error { + _, err := areaRangeToCoordinates(hCell, vCell) + if err != nil { + return err + } + sw.mergeCellsCount++ + sw.mergeCells += fmt.Sprintf(``, hCell, vCell) + return nil +} + +// setCellFormula provides a function to set formula of a cell. +func setCellFormula(c *xlsxC, formula string) { + if formula != "" { + c.F = &xlsxF{Content: formula} + } +} + +// setCellValFunc provides a function to set value of a cell. +func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) (err error) { + switch val := val.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + err = setCellIntFunc(c, val) + case float32: + c.T, c.V = setCellFloat(float64(val), -1, 32) + case float64: + c.T, c.V = setCellFloat(val, -1, 64) + case string: + c.T, c.V, c.XMLSpace = setCellStr(val) + case []byte: + c.T, c.V, c.XMLSpace = setCellStr(string(val)) + case time.Duration: + c.T, c.V = setCellDuration(val) + case time.Time: + var isNum bool + c.T, c.V, isNum, err = setCellTime(val) + if isNum && c.S == 0 { + style, _ := sw.File.NewStyle(&Style{NumFmt: 22}) + c.S = style + } + case bool: + c.T, c.V = setCellBool(val) + case nil: + c.T, c.V, c.XMLSpace = setCellStr("") + default: + c.T, c.V, c.XMLSpace = setCellStr(fmt.Sprint(val)) + } + return err +} + +// setCellIntFunc is a wrapper of SetCellInt. +func setCellIntFunc(c *xlsxC, val interface{}) (err error) { + switch val := val.(type) { + case int: + c.T, c.V = setCellInt(val) + case int8: + c.T, c.V = setCellInt(int(val)) + case int16: + c.T, c.V = setCellInt(int(val)) + case int32: + c.T, c.V = setCellInt(int(val)) + case int64: + c.T, c.V = setCellInt(int(val)) + case uint: + c.T, c.V = setCellInt(int(val)) + case uint8: + c.T, c.V = setCellInt(int(val)) + case uint16: + c.T, c.V = setCellInt(int(val)) + case uint32: + c.T, c.V = setCellInt(int(val)) + case uint64: + c.T, c.V = setCellInt(int(val)) + default: + } + return +} + +func writeCell(buf *bufferedWriter, c xlsxC) { + _, _ = buf.WriteString(``) + if c.F != nil { + _, _ = buf.WriteString(``) + _ = xml.EscapeText(buf, []byte(c.F.Content)) + _, _ = buf.WriteString(``) + } + if c.V != "" { + _, _ = buf.WriteString(``) + _ = xml.EscapeText(buf, []byte(c.V)) + _, _ = buf.WriteString(``) + } + _, _ = buf.WriteString(``) +} + +// Flush ending the streaming writing process. +func (sw *StreamWriter) Flush() error { + if !sw.sheetWritten { + _, _ = sw.rawData.WriteString(``) + sw.sheetWritten = true + } + _, _ = sw.rawData.WriteString(``) + bulkAppendFields(&sw.rawData, sw.worksheet, 8, 15) + if sw.mergeCellsCount > 0 { + sw.mergeCells = fmt.Sprintf(`%s`, sw.mergeCellsCount, sw.mergeCells) + } + _, _ = sw.rawData.WriteString(sw.mergeCells) + bulkAppendFields(&sw.rawData, sw.worksheet, 17, 38) + _, _ = sw.rawData.WriteString(sw.tableParts) + bulkAppendFields(&sw.rawData, sw.worksheet, 40, 40) + _, _ = sw.rawData.WriteString(``) + if err := sw.rawData.Flush(); err != nil { + return err + } + + sheetPath := sw.File.sheetMap[trimSheetName(sw.Sheet)] + sw.File.Sheet.Delete(sheetPath) + delete(sw.File.checked, sheetPath) + sw.File.Pkg.Delete(sheetPath) + + return nil +} + +// bulkAppendFields bulk-appends fields in a worksheet by specified field +// names order range. +func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) { + s := reflect.ValueOf(ws).Elem() + enc := xml.NewEncoder(w) + for i := 0; i < s.NumField(); i++ { + if from <= i && i <= to { + _ = enc.Encode(s.Field(i).Interface()) + } + } +} + +// bufferedWriter uses a temp file to store an extended buffer. Writes are +// always made to an in-memory buffer, which will always succeed. The buffer +// is written to the temp file with Sync, which may return an error. +// Therefore, Sync should be periodically called and the error checked. +type bufferedWriter struct { + tmp *os.File + buf bytes.Buffer +} + +// Write to the in-memory buffer. The err is always nil. +func (bw *bufferedWriter) Write(p []byte) (n int, err error) { + return bw.buf.Write(p) +} + +// WriteString wite to the in-memory buffer. The err is always nil. +func (bw *bufferedWriter) WriteString(p string) (n int, err error) { + return bw.buf.WriteString(p) +} + +// Reader provides read-access to the underlying buffer/file. +func (bw *bufferedWriter) Reader() (io.Reader, error) { + if bw.tmp == nil { + return bytes.NewReader(bw.buf.Bytes()), nil + } + if err := bw.Flush(); err != nil { + return nil, err + } + fi, err := bw.tmp.Stat() + if err != nil { + return nil, err + } + // os.File.ReadAt does not affect the cursor position and is safe to use here + return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil +} + +// Sync will write the in-memory buffer to a temp file, if the in-memory +// buffer has grown large enough. Any error will be returned. +func (bw *bufferedWriter) Sync() (err error) { + // Try to use local storage + if bw.buf.Len() < StreamChunkSize { + return nil + } + if bw.tmp == nil { + bw.tmp, err = ioutil.TempFile(os.TempDir(), "excelize-") + if err != nil { + // can not use local storage + return nil + } + } + return bw.Flush() +} + +// Flush the entire in-memory buffer to the temp file, if a temp file is being +// used. +func (bw *bufferedWriter) Flush() error { + if bw.tmp == nil { + return nil + } + _, err := bw.buf.WriteTo(bw.tmp) + if err != nil { + return err + } + bw.buf.Reset() + return nil +} + +// Close the underlying temp file and reset the in-memory buffer. +func (bw *bufferedWriter) Close() error { + bw.buf.Reset() + if bw.tmp == nil { + return nil + } + defer os.Remove(bw.tmp.Name()) + return bw.tmp.Close() +} diff --git a/vendor/github.com/xuri/excelize/v2/styles.go b/vendor/github.com/xuri/excelize/v2/styles.go new file mode 100644 index 00000000..c04ca3b3 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/styles.go @@ -0,0 +1,3061 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" + "io" + "log" + "math" + "reflect" + "strconv" + "strings" +) + +// Excel styles can reference number formats that are built-in, all of which +// have an id less than 164. Note that this number format code list is under +// English localization. +var builtInNumFmt = map[int]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: "hh:mm", + 21: "hh:mm:ss", + 22: "m/d/yy hh: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: "@", +} + +// langNumFmt defined number format code (with unicode values provided for +// language glyphs where they occur) in different language. +var langNumFmt = map[string]map[int]string{ + "zh-tw": { + 27: "[$-404]e/m/d", + 28: `[$-404]e"年"m"月"d"日"`, + 29: `[$-404]e"年"m"月"d"日"`, + 30: "m/d/yy", + 31: `yyyy"年"m"月"d"日"`, + 32: `hh"時"mm"分"`, + 33: `hh"時"mm"分"ss"秒"`, + 34: `上午/下午 hh"時"mm"分"`, + 35: `上午/下午 hh"時"mm"分"ss"秒"`, + 36: "[$-404]e/m/d", + 50: "[$-404]e/m/d", + 51: `[$-404]e"年"m"月"d"日"`, + 52: `上午/下午 hh"時"mm"分"`, + 53: `上午/下午 hh"時"mm"分"ss"秒"`, + 54: `[$-404]e"年"m"月"d"日"`, + 55: `上午/下午 hh"時"mm"分"`, + 56: `上午/下午 hh"時"mm"分"ss"秒"`, + 57: "[$-404]e/m/d", + 58: `[$-404]e"年"m"月"d"日"`, + }, + "zh-cn": { + 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"日"`, + }, + "zh-tw_unicode": { + 27: "[$-404]e/m/d", + 28: `[$-404]e"5E74"m"6708"d"65E5"`, + 29: `[$-404]e"5E74"m"6708"d"65E5"`, + 30: "m/d/yy", + 31: `yyyy"5E74"m"6708"d"65E5"`, + 32: `hh"6642"mm"5206"`, + 33: `hh"6642"mm"5206"ss"79D2"`, + 34: `4E0A5348/4E0B5348hh"6642"mm"5206"`, + 35: `4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2"`, + 36: "[$-404]e/m/d", + 50: "[$-404]e/m/d", + 51: `[$-404]e"5E74"m"6708"d"65E5"`, + 52: `4E0A5348/4E0B5348hh"6642"mm"5206"`, + 53: `4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2"`, + 54: `[$-404]e"5E74"m"6708"d"65E5"`, + 55: `4E0A5348/4E0B5348hh"6642"mm"5206"`, + 56: `4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2"`, + 57: "[$-404]e/m/d", + 58: `[$-404]e"5E74"m"6708"d"65E5"`, + }, + "zh-cn_unicode": { + 27: `yyyy"5E74"m"6708"`, + 28: `m"6708"d"65E5"`, + 29: `m"6708"d"65E5"`, + 30: "m-d-yy", + 31: `yyyy"5E74"m"6708"d"65E5"`, + 32: `h"65F6"mm"5206"`, + 33: `h"65F6"mm"5206"ss"79D2"`, + 34: `4E0A5348/4E0B5348h"65F6"mm"5206"`, + 35: `4E0A5348/4E0B5348h"65F6"mm"5206"ss"79D2"`, + 36: `yyyy"5E74"m"6708"`, + 50: `yyyy"5E74"m"6708"`, + 51: `m"6708"d"65E5"`, + 52: `yyyy"5E74"m"6708"`, + 53: `m"6708"d"65E5"`, + 54: `m"6708"d"65E5"`, + 55: `4E0A5348/4E0B5348h"65F6"mm"5206"`, + 56: `4E0A5348/4E0B5348h"65F6"mm"5206"ss"79D2"`, + 57: `yyyy"5E74"m"6708"`, + 58: `m"6708"d"65E5"`, + }, + "ja-jp": { + 27: "[$-411]ge.m.d", + 28: `[$-411]ggge"年"m"月"d"日"`, + 29: `[$-411]ggge"年"m"月"d"日"`, + 30: "m/d/yy", + 31: `yyyy"年"m"月"d"日"`, + 32: `h"時"mm"分"`, + 33: `h"時"mm"分"ss"秒"`, + 34: `yyyy"年"m"月"`, + 35: `m"月"d"日"`, + 36: "[$-411]ge.m.d", + 50: "[$-411]ge.m.d", + 51: `[$-411]ggge"年"m"月"d"日"`, + 52: `yyyy"年"m"月"`, + 53: `m"月"d"日"`, + 54: `[$-411]ggge"年"m"月"d"日"`, + 55: `yyyy"年"m"月"`, + 56: `m"月"d"日"`, + 57: "[$-411]ge.m.d", + 58: `[$-411]ggge"年"m"月"d"日"`, + }, + "ko-kr": { + 27: `yyyy"年" mm"月" dd"日"`, + 28: "mm-dd", + 29: "mm-dd", + 30: "mm-dd-yy", + 31: `yyyy"년" mm"월" dd"일"`, + 32: `h"시" mm"분"`, + 33: `h"시" mm"분" ss"초"`, + 34: `yyyy-mm-dd`, + 35: `yyyy-mm-dd`, + 36: `yyyy"年" mm"月" dd"日"`, + 50: `yyyy"年" mm"月" dd"日"`, + 51: "mm-dd", + 52: "yyyy-mm-dd", + 53: "yyyy-mm-dd", + 54: "mm-dd", + 55: "yyyy-mm-dd", + 56: "yyyy-mm-dd", + 57: `yyyy"年" mm"月" dd"日"`, + 58: "mm-dd", + }, + "ja-jp_unicode": { + 27: "[$-411]ge.m.d", + 28: `[$-411]ggge"5E74"m"6708"d"65E5"`, + 29: `[$-411]ggge"5E74"m"6708"d"65E5"`, + 30: "m/d/yy", + 31: `yyyy"5E74"m"6708"d"65E5"`, + 32: `h"6642"mm"5206"`, + 33: `h"6642"mm"5206"ss"79D2"`, + 34: `yyyy"5E74"m"6708"`, + 35: `m"6708"d"65E5"`, + 36: "[$-411]ge.m.d", + 50: "[$-411]ge.m.d", + 51: `[$-411]ggge"5E74"m"6708"d"65E5"`, + 52: `yyyy"5E74"m"6708"`, + 53: `m"6708"d"65E5"`, + 54: `[$-411]ggge"5E74"m"6708"d"65E5"`, + 55: `yyyy"5E74"m"6708"`, + 56: `m"6708"d"65E5"`, + 57: "[$-411]ge.m.d", + 58: `[$-411]ggge"5E74"m"6708"d"65E5"`, + }, + "ko-kr_unicode": { + 27: `yyyy"5E74" mm"6708" dd"65E5"`, + 28: "mm-dd", + 29: "mm-dd", + 30: "mm-dd-yy", + 31: `yyyy"B144" mm"C6D4" dd"C77C"`, + 32: `h"C2DC" mm"BD84"`, + 33: `h"C2DC" mm"BD84" ss"CD08"`, + 34: "yyyy-mm-dd", + 35: "yyyy-mm-dd", + 36: `yyyy"5E74" mm"6708" dd"65E5"`, + 50: `yyyy"5E74" mm"6708" dd"65E5"`, + 51: "mm-dd", + 52: "yyyy-mm-dd", + 53: "yyyy-mm-dd", + 54: "mm-dd", + 55: "yyyy-mm-dd", + 56: "yyyy-mm-dd", + 57: `yyyy"5E74" mm"6708" dd"65E5"`, + 58: "mm-dd", + }, + "th-th": { + 59: "t0", + 60: "t0.00", + 61: "t#,##0", + 62: "t#,##0.00", + 67: "t0%", + 68: "t0.00%", + 69: "t# ?/?", + 70: "t# ??/??", + 71: "ว/ด/ปปปป", + 72: "ว-ดดด-ปป", + 73: "ว-ดดด", + 74: "ดดด-ปป", + 75: "ช:นน", + 76: "ช:นน:ทท", + 77: "ว/ด/ปปปป ช:นน", + 78: "นน:ทท", + 79: "[ช]:นน:ทท", + 80: "นน:ทท.0", + 81: "d/m/bb", + }, + "th-th_unicode": { + 59: "t0", + 60: "t0.00", + 61: "t#,##0", + 62: "t#,##0.00", + 67: "t0%", + 68: "t0.00%", + 69: "t# ?/?", + 70: "t# ??/??", + 71: "0E27/0E14/0E1B0E1B0E1B0E1B", + 72: "0E27-0E140E140E14-0E1B0E1B", + 73: "0E27-0E140E140E14", + 74: "0E140E140E14-0E1B0E1B", + 75: "0E0A:0E190E19", + 76: "0E0A:0E190E19:0E170E17", + 77: "0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E19", + 78: "0E190E19:0E170E17", + 79: "[0E0A]:0E190E19:0E170E17", + 80: "0E190E19:0E170E17.0", + 81: "d/m/bb", + }, +} + +// currencyNumFmt defined the currency number format map. +var currencyNumFmt = map[int]string{ + 164: `"CN¥",##0.00`, + 165: "[$$-409]#,##0.00", + 166: "[$$-45C]#,##0.00", + 167: "[$$-1004]#,##0.00", + 168: "[$$-404]#,##0.00", + 169: "[$$-C09]#,##0.00", + 170: "[$$-2809]#,##0.00", + 171: "[$$-1009]#,##0.00", + 172: "[$$-2009]#,##0.00", + 173: "[$$-1409]#,##0.00", + 174: "[$$-4809]#,##0.00", + 175: "[$$-2C09]#,##0.00", + 176: "[$$-2409]#,##0.00", + 177: "[$$-1000]#,##0.00", + 178: `#,##0.00\ [$$-C0C]`, + 179: "[$$-475]#,##0.00", + 180: "[$$-83E]#,##0.00", + 181: `[$$-86B]\ #,##0.00`, + 182: `[$$-340A]\ #,##0.00`, + 183: "[$$-240A]#,##0.00", + 184: `[$$-300A]\ #,##0.00`, + 185: "[$$-440A]#,##0.00", + 186: "[$$-80A]#,##0.00", + 187: "[$$-500A]#,##0.00", + 188: "[$$-540A]#,##0.00", + 189: `[$$-380A]\ #,##0.00`, + 190: "[$£-809]#,##0.00", + 191: "[$£-491]#,##0.00", + 192: "[$£-452]#,##0.00", + 193: "[$¥-804]#,##0.00", + 194: "[$¥-411]#,##0.00", + 195: "[$¥-478]#,##0.00", + 196: "[$¥-451]#,##0.00", + 197: "[$¥-480]#,##0.00", + 198: "#,##0.00\\ [$\u058F-42B]", + 199: "[$\u060B-463]#,##0.00", + 200: "[$\u060B-48C]#,##0.00", + 201: "[$\u09F3-845]\\ #,##0.00", + 202: "#,##0.00[$\u17DB-453]", + 203: "[$\u20A1-140A]#,##0.00", + 204: "[$\u20A6-468]\\ #,##0.00", + 205: "[$\u20A6-470]\\ #,##0.00", + 206: "[$\u20A9-412]#,##0.00", + 207: "[$\u20AA-40D]\\ #,##0.00", + 208: "#,##0.00\\ [$\u20AB-42A]", + 209: "#,##0.00\\ [$\u20AC-42D]", + 210: "#,##0.00\\ [$\u20AC-47E]", + 211: "#,##0.00\\ [$\u20AC-403]", + 212: "#,##0.00\\ [$\u20AC-483]", + 213: "[$\u20AC-813]\\ #,##0.00", + 214: "[$\u20AC-413]\\ #,##0.00", + 215: "[$\u20AC-1809]#,##0.00", + 216: "#,##0.00\\ [$\u20AC-425]", + 217: "[$\u20AC-2]\\ #,##0.00", + 218: "#,##0.00\\ [$\u20AC-1]", + 219: "#,##0.00\\ [$\u20AC-40B]", + 220: "#,##0.00\\ [$\u20AC-80C]", + 221: "#,##0.00\\ [$\u20AC-40C]", + 222: "#,##0.00\\ [$\u20AC-140C]", + 223: "#,##0.00\\ [$\u20AC-180C]", + 224: "[$\u20AC-200C]#,##0.00", + 225: "#,##0.00\\ [$\u20AC-456]", + 226: "#,##0.00\\ [$\u20AC-C07]", + 227: "#,##0.00\\ [$\u20AC-407]", + 228: "#,##0.00\\ [$\u20AC-1007]", + 229: "#,##0.00\\ [$\u20AC-408]", + 230: "#,##0.00\\ [$\u20AC-243B]", + 231: "[$\u20AC-83C]#,##0.00", + 232: "[$\u20AC-410]\\ #,##0.00", + 233: "[$\u20AC-476]#,##0.00", + 234: "#,##0.00\\ [$\u20AC-2C1A]", + 235: "[$\u20AC-426]\\ #,##0.00", + 236: "#,##0.00\\ [$\u20AC-427]", + 237: "#,##0.00\\ [$\u20AC-82E]", + 238: "#,##0.00\\ [$\u20AC-46E]", + 239: "[$\u20AC-43A]#,##0.00", + 240: "#,##0.00\\ [$\u20AC-C3B]", + 241: "#,##0.00\\ [$\u20AC-482]", + 242: "#,##0.00\\ [$\u20AC-816]", + 243: "#,##0.00\\ [$\u20AC-301A]", + 244: "#,##0.00\\ [$\u20AC-203B]", + 245: "#,##0.00\\ [$\u20AC-41B]", + 246: "#,##0.00\\ [$\u20AC-424]", + 247: "#,##0.00\\ [$\u20AC-C0A]", + 248: "#,##0.00\\ [$\u20AC-81D]", + 249: "#,##0.00\\ [$\u20AC-484]", + 250: "#,##0.00\\ [$\u20AC-42E]", + 251: "[$\u20AC-462]\\ #,##0.00", + 252: "#,##0.00\\ [$₭-454]", + 253: "#,##0.00\\ [$₮-450]", + 254: "[$\u20AE-C50]#,##0.00", + 255: "[$\u20B1-3409]#,##0.00", + 256: "[$\u20B1-464]#,##0.00", + 257: "#,##0.00[$\u20B4-422]", + 258: "[$\u20B8-43F]#,##0.00", + 259: "[$\u20B9-460]#,##0.00", + 260: "[$\u20B9-4009]\\ #,##0.00", + 261: "[$\u20B9-447]\\ #,##0.00", + 262: "[$\u20B9-439]\\ #,##0.00", + 263: "[$\u20B9-44B]\\ #,##0.00", + 264: "[$\u20B9-860]#,##0.00", + 265: "[$\u20B9-457]\\ #,##0.00", + 266: "[$\u20B9-458]#,##0.00", + 267: "[$\u20B9-44E]\\ #,##0.00", + 268: "[$\u20B9-861]#,##0.00", + 269: "[$\u20B9-448]\\ #,##0.00", + 270: "[$\u20B9-446]\\ #,##0.00", + 271: "[$\u20B9-44F]\\ #,##0.00", + 272: "[$\u20B9-459]#,##0.00", + 273: "[$\u20B9-449]\\ #,##0.00", + 274: "[$\u20B9-820]#,##0.00", + 275: "#,##0.00\\ [$\u20BA-41F]", + 276: "#,##0.00\\ [$\u20BC-42C]", + 277: "#,##0.00\\ [$\u20BC-82C]", + 278: "#,##0.00\\ [$\u20BD-419]", + 279: "#,##0.00[$\u20BD-485]", + 280: "#,##0.00\\ [$\u20BE-437]", + 281: "[$B/.-180A]\\ #,##0.00", + 282: "[$Br-472]#,##0.00", + 283: "[$Br-477]#,##0.00", + 284: "#,##0.00[$Br-473]", + 285: "[$Bs-46B]\\ #,##0.00", + 286: "[$Bs-400A]\\ #,##0.00", + 287: "[$Bs.-200A]\\ #,##0.00", + 288: "[$BWP-832]\\ #,##0.00", + 289: "[$C$-4C0A]#,##0.00", + 290: "[$CA$-85D]#,##0.00", + 291: "[$CA$-47C]#,##0.00", + 292: "[$CA$-45D]#,##0.00", + 293: "[$CFA-340C]#,##0.00", + 294: "[$CFA-280C]#,##0.00", + 295: "#,##0.00\\ [$CFA-867]", + 296: "#,##0.00\\ [$CFA-488]", + 297: "#,##0.00\\ [$CHF-100C]", + 298: "[$CHF-1407]\\ #,##0.00", + 299: "[$CHF-807]\\ #,##0.00", + 300: "[$CHF-810]\\ #,##0.00", + 301: "[$CHF-417]\\ #,##0.00", + 302: "[$CLP-47A]\\ #,##0.00", + 303: "[$CN¥-850]#,##0.00", + 304: "#,##0.00\\ [$DZD-85F]", + 305: "[$FCFA-2C0C]#,##0.00", + 306: "#,##0.00\\ [$Ft-40E]", + 307: "[$G-3C0C]#,##0.00", + 308: "[$Gs.-3C0A]\\ #,##0.00", + 309: "[$GTQ-486]#,##0.00", + 310: "[$HK$-C04]#,##0.00", + 311: "[$HK$-3C09]#,##0.00", + 312: "#,##0.00\\ [$HRK-41A]", + 313: "[$IDR-3809]#,##0.00", + 314: "[$IQD-492]#,##0.00", + 315: "#,##0.00\\ [$ISK-40F]", + 316: "[$K-455]#,##0.00", + 317: "#,##0.00\\ [$K\u010D-405]", + 318: "#,##0.00\\ [$KM-141A]", + 319: "#,##0.00\\ [$KM-101A]", + 320: "#,##0.00\\ [$KM-181A]", + 321: "[$kr-438]\\ #,##0.00", + 322: "[$kr-43B]\\ #,##0.00", + 323: "#,##0.00\\ [$kr-83B]", + 324: "[$kr-414]\\ #,##0.00", + 325: "[$kr-814]\\ #,##0.00", + 326: "#,##0.00\\ [$kr-41D]", + 327: "[$kr.-406]\\ #,##0.00", + 328: "[$kr.-46F]\\ #,##0.00", + 329: "[$Ksh-441]#,##0.00", + 330: "[$L-818]#,##0.00", + 331: "[$L-819]#,##0.00", + 332: "[$L-480A]\\ #,##0.00", + 333: "#,##0.00\\ [$Lek\u00EB-41C]", + 334: "[$MAD-45F]#,##0.00", + 335: "[$MAD-380C]#,##0.00", + 336: "#,##0.00\\ [$MAD-105F]", + 337: "[$MOP$-1404]#,##0.00", + 338: "#,##0.00\\ [$MVR-465]_-", + 339: "#,##0.00[$Nfk-873]", + 340: "[$NGN-466]#,##0.00", + 341: "[$NGN-467]#,##0.00", + 342: "[$NGN-469]#,##0.00", + 343: "[$NGN-471]#,##0.00", + 344: "[$NOK-103B]\\ #,##0.00", + 345: "[$NOK-183B]\\ #,##0.00", + 346: "[$NZ$-481]#,##0.00", + 347: "[$PKR-859]\\ #,##0.00", + 348: "[$PYG-474]#,##0.00", + 349: "[$Q-100A]#,##0.00", + 350: "[$R-436]\\ #,##0.00", + 351: "[$R-1C09]\\ #,##0.00", + 352: "[$R-435]\\ #,##0.00", + 353: "[$R$-416]\\ #,##0.00", + 354: "[$RD$-1C0A]#,##0.00", + 355: "#,##0.00\\ [$RF-487]", + 356: "[$RM-4409]#,##0.00", + 357: "[$RM-43E]#,##0.00", + 358: "#,##0.00\\ [$RON-418]", + 359: "[$Rp-421]#,##0.00", + 360: "[$Rs-420]#,##0.00_-", + 361: "[$Rs.-849]\\ #,##0.00", + 362: "#,##0.00\\ [$RSD-81A]", + 363: "#,##0.00\\ [$RSD-C1A]", + 364: "#,##0.00\\ [$RUB-46D]", + 365: "#,##0.00\\ [$RUB-444]", + 366: "[$S/.-C6B]\\ #,##0.00", + 367: "[$S/.-280A]\\ #,##0.00", + 368: "#,##0.00\\ [$SEK-143B]", + 369: "#,##0.00\\ [$SEK-1C3B]", + 370: "#,##0.00\\ [$so\u02BBm-443]", + 371: "#,##0.00\\ [$so\u02BBm-843]", + 372: "#,##0.00\\ [$SYP-45A]", + 373: "[$THB-41E]#,##0.00", + 374: "#,##0.00[$TMT-442]", + 375: "[$US$-3009]#,##0.00", + 376: "[$ZAR-46C]\\ #,##0.00", + 377: "[$ZAR-430]#,##0.00", + 378: "[$ZAR-431]#,##0.00", + 379: "[$ZAR-432]\\ #,##0.00", + 380: "[$ZAR-433]#,##0.00", + 381: "[$ZAR-434]\\ #,##0.00", + 382: "#,##0.00\\ [$z\u0142-415]", + 383: "#,##0.00\\ [$\u0434\u0435\u043D-42F]", + 384: "#,##0.00\\ [$КМ-201A]", + 385: "#,##0.00\\ [$КМ-1C1A]", + 386: "#,##0.00\\ [$\u043B\u0432.-402]", + 387: "#,##0.00\\ [$р.-423]", + 388: "#,##0.00\\ [$\u0441\u043E\u043C-440]", + 389: "#,##0.00\\ [$\u0441\u043E\u043C-428]", + 390: "[$\u062C.\u0645.-C01]\\ #,##0.00_-", + 391: "[$\u062F.\u0623.-2C01]\\ #,##0.00_-", + 392: "[$\u062F.\u0625.-3801]\\ #,##0.00_-", + 393: "[$\u062F.\u0628.-3C01]\\ #,##0.00_-", + 394: "[$\u062F.\u062A.-1C01]\\ #,##0.00_-", + 395: "[$\u062F.\u062C.-1401]\\ #,##0.00_-", + 396: "[$\u062F.\u0639.-801]\\ #,##0.00_-", + 397: "[$\u062F.\u0643.-3401]\\ #,##0.00_-", + 398: "[$\u062F.\u0644.-1001]#,##0.00_-", + 399: "[$\u062F.\u0645.-1801]\\ #,##0.00_-", + 400: "[$\u0631-846]\\ #,##0.00", + 401: "[$\u0631.\u0633.-401]\\ #,##0.00_-", + 402: "[$\u0631.\u0639.-2001]\\ #,##0.00_-", + 403: "[$\u0631.\u0642.-4001]\\ #,##0.00_-", + 404: "[$\u0631.\u064A.-2401]\\ #,##0.00_-", + 405: "[$\u0631\u06CC\u0627\u0644-429]#,##0.00_-", + 406: "[$\u0644.\u0633.-2801]\\ #,##0.00_-", + 407: "[$\u0644.\u0644.-3001]\\ #,##0.00_-", + 408: "[$\u1265\u122D-45E]#,##0.00", + 409: "[$\u0930\u0942-461]#,##0.00", + 410: "[$\u0DBB\u0DD4.-45B]\\ #,##0.00", + 411: "[$ADP]\\ #,##0.00", + 412: "[$AED]\\ #,##0.00", + 413: "[$AFA]\\ #,##0.00", + 414: "[$AFN]\\ #,##0.00", + 415: "[$ALL]\\ #,##0.00", + 416: "[$AMD]\\ #,##0.00", + 417: "[$ANG]\\ #,##0.00", + 418: "[$AOA]\\ #,##0.00", + 419: "[$ARS]\\ #,##0.00", + 420: "[$ATS]\\ #,##0.00", + 421: "[$AUD]\\ #,##0.00", + 422: "[$AWG]\\ #,##0.00", + 423: "[$AZM]\\ #,##0.00", + 424: "[$AZN]\\ #,##0.00", + 425: "[$BAM]\\ #,##0.00", + 426: "[$BBD]\\ #,##0.00", + 427: "[$BDT]\\ #,##0.00", + 428: "[$BEF]\\ #,##0.00", + 429: "[$BGL]\\ #,##0.00", + 430: "[$BGN]\\ #,##0.00", + 431: "[$BHD]\\ #,##0.00", + 432: "[$BIF]\\ #,##0.00", + 433: "[$BMD]\\ #,##0.00", + 434: "[$BND]\\ #,##0.00", + 435: "[$BOB]\\ #,##0.00", + 436: "[$BOV]\\ #,##0.00", + 437: "[$BRL]\\ #,##0.00", + 438: "[$BSD]\\ #,##0.00", + 439: "[$BTN]\\ #,##0.00", + 440: "[$BWP]\\ #,##0.00", + 441: "[$BYR]\\ #,##0.00", + 442: "[$BZD]\\ #,##0.00", + 443: "[$CAD]\\ #,##0.00", + 444: "[$CDF]\\ #,##0.00", + 445: "[$CHE]\\ #,##0.00", + 446: "[$CHF]\\ #,##0.00", + 447: "[$CHW]\\ #,##0.00", + 448: "[$CLF]\\ #,##0.00", + 449: "[$CLP]\\ #,##0.00", + 450: "[$CNY]\\ #,##0.00", + 451: "[$COP]\\ #,##0.00", + 452: "[$COU]\\ #,##0.00", + 453: "[$CRC]\\ #,##0.00", + 454: "[$CSD]\\ #,##0.00", + 455: "[$CUC]\\ #,##0.00", + 456: "[$CVE]\\ #,##0.00", + 457: "[$CYP]\\ #,##0.00", + 458: "[$CZK]\\ #,##0.00", + 459: "[$DEM]\\ #,##0.00", + 460: "[$DJF]\\ #,##0.00", + 461: "[$DKK]\\ #,##0.00", + 462: "[$DOP]\\ #,##0.00", + 463: "[$DZD]\\ #,##0.00", + 464: "[$ECS]\\ #,##0.00", + 465: "[$ECV]\\ #,##0.00", + 466: "[$EEK]\\ #,##0.00", + 467: "[$EGP]\\ #,##0.00", + 468: "[$ERN]\\ #,##0.00", + 469: "[$ESP]\\ #,##0.00", + 470: "[$ETB]\\ #,##0.00", + 471: "[$EUR]\\ #,##0.00", + 472: "[$FIM]\\ #,##0.00", + 473: "[$FJD]\\ #,##0.00", + 474: "[$FKP]\\ #,##0.00", + 475: "[$FRF]\\ #,##0.00", + 476: "[$GBP]\\ #,##0.00", + 477: "[$GEL]\\ #,##0.00", + 478: "[$GHC]\\ #,##0.00", + 479: "[$GHS]\\ #,##0.00", + 480: "[$GIP]\\ #,##0.00", + 481: "[$GMD]\\ #,##0.00", + 482: "[$GNF]\\ #,##0.00", + 483: "[$GRD]\\ #,##0.00", + 484: "[$GTQ]\\ #,##0.00", + 485: "[$GYD]\\ #,##0.00", + 486: "[$HKD]\\ #,##0.00", + 487: "[$HNL]\\ #,##0.00", + 488: "[$HRK]\\ #,##0.00", + 489: "[$HTG]\\ #,##0.00", + 490: "[$HUF]\\ #,##0.00", + 491: "[$IDR]\\ #,##0.00", + 492: "[$IEP]\\ #,##0.00", + 493: "[$ILS]\\ #,##0.00", + 494: "[$INR]\\ #,##0.00", + 495: "[$IQD]\\ #,##0.00", + 496: "[$IRR]\\ #,##0.00", + 497: "[$ISK]\\ #,##0.00", + 498: "[$ITL]\\ #,##0.00", + 499: "[$JMD]\\ #,##0.00", + 500: "[$JOD]\\ #,##0.00", + 501: "[$JPY]\\ #,##0.00", + 502: "[$KAF]\\ #,##0.00", + 503: "[$KES]\\ #,##0.00", + 504: "[$KGS]\\ #,##0.00", + 505: "[$KHR]\\ #,##0.00", + 506: "[$KMF]\\ #,##0.00", + 507: "[$KPW]\\ #,##0.00", + 508: "[$KRW]\\ #,##0.00", + 509: "[$KWD]\\ #,##0.00", + 510: "[$KYD]\\ #,##0.00", + 511: "[$KZT]\\ #,##0.00", + 512: "[$LAK]\\ #,##0.00", + 513: "[$LBP]\\ #,##0.00", + 514: "[$LKR]\\ #,##0.00", + 515: "[$LRD]\\ #,##0.00", + 516: "[$LSL]\\ #,##0.00", + 517: "[$LTL]\\ #,##0.00", + 518: "[$LUF]\\ #,##0.00", + 519: "[$LVL]\\ #,##0.00", + 520: "[$LYD]\\ #,##0.00", + 521: "[$MAD]\\ #,##0.00", + 522: "[$MDL]\\ #,##0.00", + 523: "[$MGA]\\ #,##0.00", + 524: "[$MGF]\\ #,##0.00", + 525: "[$MKD]\\ #,##0.00", + 526: "[$MMK]\\ #,##0.00", + 527: "[$MNT]\\ #,##0.00", + 528: "[$MOP]\\ #,##0.00", + 529: "[$MRO]\\ #,##0.00", + 530: "[$MTL]\\ #,##0.00", + 531: "[$MUR]\\ #,##0.00", + 532: "[$MVR]\\ #,##0.00", + 533: "[$MWK]\\ #,##0.00", + 534: "[$MXN]\\ #,##0.00", + 535: "[$MXV]\\ #,##0.00", + 536: "[$MYR]\\ #,##0.00", + 537: "[$MZM]\\ #,##0.00", + 538: "[$MZN]\\ #,##0.00", + 539: "[$NAD]\\ #,##0.00", + 540: "[$NGN]\\ #,##0.00", + 541: "[$NIO]\\ #,##0.00", + 542: "[$NLG]\\ #,##0.00", + 543: "[$NOK]\\ #,##0.00", + 544: "[$NPR]\\ #,##0.00", + 545: "[$NTD]\\ #,##0.00", + 546: "[$NZD]\\ #,##0.00", + 547: "[$OMR]\\ #,##0.00", + 548: "[$PAB]\\ #,##0.00", + 549: "[$PEN]\\ #,##0.00", + 550: "[$PGK]\\ #,##0.00", + 551: "[$PHP]\\ #,##0.00", + 552: "[$PKR]\\ #,##0.00", + 553: "[$PLN]\\ #,##0.00", + 554: "[$PTE]\\ #,##0.00", + 555: "[$PYG]\\ #,##0.00", + 556: "[$QAR]\\ #,##0.00", + 557: "[$ROL]\\ #,##0.00", + 558: "[$RON]\\ #,##0.00", + 559: "[$RSD]\\ #,##0.00", + 560: "[$RUB]\\ #,##0.00", + 561: "[$RUR]\\ #,##0.00", + 562: "[$RWF]\\ #,##0.00", + 563: "[$SAR]\\ #,##0.00", + 564: "[$SBD]\\ #,##0.00", + 565: "[$SCR]\\ #,##0.00", + 566: "[$SDD]\\ #,##0.00", + 567: "[$SDG]\\ #,##0.00", + 568: "[$SDP]\\ #,##0.00", + 569: "[$SEK]\\ #,##0.00", + 570: "[$SGD]\\ #,##0.00", + 571: "[$SHP]\\ #,##0.00", + 572: "[$SIT]\\ #,##0.00", + 573: "[$SKK]\\ #,##0.00", + 574: "[$SLL]\\ #,##0.00", + 575: "[$SOS]\\ #,##0.00", + 576: "[$SPL]\\ #,##0.00", + 577: "[$SRD]\\ #,##0.00", + 578: "[$SRG]\\ #,##0.00", + 579: "[$STD]\\ #,##0.00", + 580: "[$SVC]\\ #,##0.00", + 581: "[$SYP]\\ #,##0.00", + 582: "[$SZL]\\ #,##0.00", + 583: "[$THB]\\ #,##0.00", + 584: "[$TJR]\\ #,##0.00", + 585: "[$TJS]\\ #,##0.00", + 586: "[$TMM]\\ #,##0.00", + 587: "[$TMT]\\ #,##0.00", + 588: "[$TND]\\ #,##0.00", + 589: "[$TOP]\\ #,##0.00", + 590: "[$TRL]\\ #,##0.00", + 591: "[$TRY]\\ #,##0.00", + 592: "[$TTD]\\ #,##0.00", + 593: "[$TWD]\\ #,##0.00", + 594: "[$TZS]\\ #,##0.00", + 595: "[$UAH]\\ #,##0.00", + 596: "[$UGX]\\ #,##0.00", + 597: "[$USD]\\ #,##0.00", + 598: "[$USN]\\ #,##0.00", + 599: "[$USS]\\ #,##0.00", + 600: "[$UYI]\\ #,##0.00", + 601: "[$UYU]\\ #,##0.00", + 602: "[$UZS]\\ #,##0.00", + 603: "[$VEB]\\ #,##0.00", + 604: "[$VEF]\\ #,##0.00", + 605: "[$VND]\\ #,##0.00", + 606: "[$VUV]\\ #,##0.00", + 607: "[$WST]\\ #,##0.00", + 608: "[$XAF]\\ #,##0.00", + 609: "[$XAG]\\ #,##0.00", + 610: "[$XAU]\\ #,##0.00", + 611: "[$XB5]\\ #,##0.00", + 612: "[$XBA]\\ #,##0.00", + 613: "[$XBB]\\ #,##0.00", + 614: "[$XBC]\\ #,##0.00", + 615: "[$XBD]\\ #,##0.00", + 616: "[$XCD]\\ #,##0.00", + 617: "[$XDR]\\ #,##0.00", + 618: "[$XFO]\\ #,##0.00", + 619: "[$XFU]\\ #,##0.00", + 620: "[$XOF]\\ #,##0.00", + 621: "[$XPD]\\ #,##0.00", + 622: "[$XPF]\\ #,##0.00", + 623: "[$XPT]\\ #,##0.00", + 624: "[$XTS]\\ #,##0.00", + 625: "[$XXX]\\ #,##0.00", + 626: "[$YER]\\ #,##0.00", + 627: "[$YUM]\\ #,##0.00", + 628: "[$ZAR]\\ #,##0.00", + 629: "[$ZMK]\\ #,##0.00", + 630: "[$ZMW]\\ #,##0.00", + 631: "[$ZWD]\\ #,##0.00", + 632: "[$ZWL]\\ #,##0.00", + 633: "[$ZWN]\\ #,##0.00", + 634: "[$ZWR]\\ #,##0.00", +} + +// builtInNumFmtFunc defined the format conversion functions map. Partial format +// code doesn't support currently and will return original string. +var builtInNumFmtFunc = map[int]func(v string, format string) string{ + 0: format, + 1: formatToInt, + 2: formatToFloat, + 3: formatToInt, + 4: formatToFloat, + 9: formatToC, + 10: formatToD, + 11: formatToE, + 12: format, // Doesn't support currently + 13: format, // Doesn't support currently + 14: format, + 15: format, + 16: format, + 17: format, + 18: format, + 19: format, + 20: format, + 21: format, + 22: format, + 37: formatToA, + 38: formatToA, + 39: formatToB, + 40: formatToB, + 41: format, // Doesn't support currently + 42: format, // Doesn't support currently + 43: format, // Doesn't support currently + 44: format, // Doesn't support currently + 45: format, + 46: format, + 47: format, + 48: formatToE, + 49: format, +} + +// validType defined the list of valid validation types. +var validType = map[string]string{ + "cell": "cellIs", + "date": "date", // Doesn't support currently + "time": "time", // Doesn't support currently + "average": "aboveAverage", + "duplicate": "duplicateValues", + "unique": "uniqueValues", + "top": "top10", + "bottom": "top10", + "text": "text", // Doesn't support currently + "time_period": "timePeriod", // Doesn't support currently + "blanks": "containsBlanks", // Doesn't support currently + "no_blanks": "notContainsBlanks", // Doesn't support currently + "errors": "containsErrors", // Doesn't support currently + "no_errors": "notContainsErrors", // Doesn't support currently + "2_color_scale": "2_color_scale", + "3_color_scale": "3_color_scale", + "data_bar": "dataBar", + "formula": "expression", +} + +// criteriaType defined the list of valid criteria types. +var criteriaType = map[string]string{ + "between": "between", + "not between": "notBetween", + "equal to": "equal", + "=": "equal", + "==": "equal", + "not equal to": "notEqual", + "!=": "notEqual", + "<>": "notEqual", + "greater than": "greaterThan", + ">": "greaterThan", + "less than": "lessThan", + "<": "lessThan", + "greater than or equal to": "greaterThanOrEqual", + ">=": "greaterThanOrEqual", + "less than or equal to": "lessThanOrEqual", + "<=": "lessThanOrEqual", + "containing": "containsText", + "not containing": "notContains", + "begins with": "beginsWith", + "ends with": "endsWith", + "yesterday": "yesterday", + "today": "today", + "last 7 days": "last7Days", + "last week": "lastWeek", + "this week": "thisWeek", + "continue week": "continueWeek", + "last month": "lastMonth", + "this month": "thisMonth", + "continue month": "continueMonth", +} + +// formatToInt provides a function to convert original string to integer +// format as string type by given built-in number formats code and cell +// string. +func formatToInt(v string, format string) string { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return v + } + return fmt.Sprintf("%d", int64(f)) +} + +// formatToFloat provides a function to convert original string to float +// format as string type by given built-in number formats code and cell +// string. +func formatToFloat(v string, format string) string { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return v + } + return fmt.Sprintf("%.2f", f) +} + +// formatToA provides a function to convert original string to special format +// as string type by given built-in number formats code and cell string. +func formatToA(v string, format string) string { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return v + } + if f < 0 { + t := int(math.Abs(f)) + return fmt.Sprintf("(%d)", t) + } + t := int(f) + return fmt.Sprintf("%d", t) +} + +// formatToB provides a function to convert original string to special format +// as string type by given built-in number formats code and cell string. +func formatToB(v string, format string) string { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return v + } + if f < 0 { + return fmt.Sprintf("(%.2f)", f) + } + return fmt.Sprintf("%.2f", f) +} + +// formatToC provides a function to convert original string to special format +// as string type by given built-in number formats code and cell string. +func formatToC(v string, format string) string { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return v + } + f = f * 100 + return fmt.Sprintf("%.f%%", f) +} + +// formatToD provides a function to convert original string to special format +// as string type by given built-in number formats code and cell string. +func formatToD(v string, format string) string { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return v + } + f = f * 100 + return fmt.Sprintf("%.2f%%", f) +} + +// formatToE provides a function to convert original string to special format +// as string type by given built-in number formats code and cell string. +func formatToE(v string, format string) string { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return v + } + return fmt.Sprintf("%.2E", f) +} + +// stylesReader provides a function to get the pointer to the structure after +// deserialization of xl/styles.xml. +func (f *File) stylesReader() *xlsxStyleSheet { + if f.Styles == nil { + f.Styles = new(xlsxStyleSheet) + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathStyles)))). + Decode(f.Styles); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + } + return f.Styles +} + +// styleSheetWriter provides a function to save xl/styles.xml after serialize +// structure. +func (f *File) styleSheetWriter() { + if f.Styles != nil { + output, _ := xml.Marshal(f.Styles) + f.saveFileList(defaultXMLPathStyles, f.replaceNameSpaceBytes(defaultXMLPathStyles, output)) + } +} + +// sharedStringsWriter provides a function to save xl/sharedStrings.xml after +// serialize structure. +func (f *File) sharedStringsWriter() { + if f.SharedStrings != nil { + output, _ := xml.Marshal(f.SharedStrings) + f.saveFileList(defaultXMLPathSharedStrings, f.replaceNameSpaceBytes(defaultXMLPathSharedStrings, output)) + } +} + +// parseFormatStyleSet provides a function to parse the format settings of the +// cells and conditional formats. +func parseFormatStyleSet(style interface{}) (*Style, error) { + fs := Style{} + var err error + switch v := style.(type) { + case string: + err = json.Unmarshal([]byte(v), &fs) + case *Style: + fs = *v + default: + err = ErrParameterInvalid + } + if fs.Font != nil { + if len(fs.Font.Family) > MaxFontFamilyLength { + return &fs, ErrFontLength + } + if fs.Font.Size > MaxFontSize { + return &fs, ErrFontSize + } + } + if fs.CustomNumFmt != nil && len(*fs.CustomNumFmt) == 0 { + err = ErrCustomNumFmt + } + return &fs, err +} + +// NewStyle provides a function to create the style for cells by given JSON or +// structure pointer. Note that the color field uses RGB color code. +// +// The following shows the border styles sorted by excelize index number: +// +// Index | Name | Weight | Style +// -------+---------------+--------+------------- +// 0 | None | 0 | +// 1 | Continuous | 1 | ----------- +// 2 | Continuous | 2 | ----------- +// 3 | Dash | 1 | - - - - - - +// 4 | Dot | 1 | . . . . . . +// 5 | Continuous | 3 | ----------- +// 6 | Double | 3 | =========== +// 7 | Continuous | 0 | ----------- +// 8 | Dash | 2 | - - - - - - +// 9 | Dash Dot | 1 | - . - . - . +// 10 | Dash Dot | 2 | - . - . - . +// 11 | Dash Dot Dot | 1 | - . . - . . +// 12 | Dash Dot Dot | 2 | - . . - . . +// 13 | SlantDash Dot | 2 | / - . / - . +// +// The following shows the borders in the order shown in the Excel dialog: +// +// Index | Style | Index | Style +// -------+-------------+-------+------------- +// 0 | None | 12 | - . . - . . +// 7 | ----------- | 13 | / - . / - . +// 4 | . . . . . . | 10 | - . - . - . +// 11 | - . . - . . | 8 | - - - - - - +// 9 | - . - . - . | 2 | ----------- +// 3 | - - - - - - | 5 | ----------- +// 1 | ----------- | 6 | =========== +// +// The following shows the shading styles sorted by excelize index number: +// +// Index | Style | Index | Style +// -------+-----------------+-------+----------------- +// 0 | Horizontal | 3 | Diagonal down +// 1 | Vertical | 4 | From corner +// 2 | Diagonal Up | 5 | From center +// +// The following shows the patterns styles sorted by excelize index number: +// +// Index | Style | Index | Style +// -------+-----------------+-------+----------------- +// 0 | None | 10 | darkTrellis +// 1 | solid | 11 | lightHorizontal +// 2 | mediumGray | 12 | lightVertical +// 3 | darkGray | 13 | lightDown +// 4 | lightGray | 14 | lightUp +// 5 | darkHorizontal | 15 | lightGrid +// 6 | darkVertical | 16 | lightTrellis +// 7 | darkDown | 17 | gray125 +// 8 | darkUp | 18 | gray0625 +// 9 | darkGrid | | +// +// The following the type of horizontal alignment in cells: +// +// Style +// ------------------ +// left +// center +// right +// fill +// justify +// centerContinuous +// distributed +// +// The following the type of vertical alignment in cells: +// +// Style +// ------------------ +// top +// center +// justify +// distributed +// +// The following the type of font underline style: +// +// Style +// ------------------ +// single +// double +// +// Excel's built-in all languages formats are shown in the following table: +// +// Index | Format String +// -------+---------------------------------------------------- +// 0 | General +// 1 | 0 +// 2 | 0.00 +// 3 | #,##0 +// 4 | #,##0.00 +// 5 | ($#,##0_);($#,##0) +// 6 | ($#,##0_);[Red]($#,##0) +// 7 | ($#,##0.00_);($#,##0.00) +// 8 | ($#,##0.00_);[Red]($#,##0.00) +// 9 | 0% +// 10 | 0.00% +// 11 | 0.00E+00 +// 12 | # ?/? +// 13 | # ??/?? +// 14 | m/d/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 | mm:ss.0 +// 48 | ##0.0E+0 +// 49 | @ +// +// Number format code in zh-tw language: +// +// Index | Symbol +// -------+------------------------------------------- +// 27 | [$-404]e/m/d +// 28 | [$-404]e"年"m"月"d"日" +// 29 | [$-404]e"年"m"月"d"日" +// 30 | m/d/yy +// 31 | yyyy"年"m"月"d"日" +// 32 | hh"時"mm"分" +// 33 | hh"時"mm"分"ss"秒" +// 34 | 上午/下午 hh"時"mm"分" +// 35 | 上午/下午 hh"時"mm"分"ss"秒" +// 36 | [$-404]e/m/d +// 50 | [$-404]e/m/d +// 51 | [$-404]e"年"m"月"d"日" +// 52 | 上午/下午 hh"時"mm"分" +// 53 | 上午/下午 hh"時"mm"分"ss"秒" +// 54 | [$-404]e"年"m"月"d"日" +// 55 | 上午/下午 hh"時"mm"分" +// 56 | 上午/下午 hh"時"mm"分"ss"秒" +// 57 | [$-404]e/m/d +// 58 | [$-404]e"年"m"月"d"日" +// +// Number format code in zh-cn language: +// +// Index | Symbol +// -------+------------------------------------------- +// 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"日" +// +// Number format code with unicode values provided for language glyphs where +// they occur in zh-tw language: +// +// Index | Symbol +// -------+------------------------------------------- +// 27 | [$-404]e/m/ +// 28 | [$-404]e"5E74"m"6708"d"65E5 +// 29 | [$-404]e"5E74"m"6708"d"65E5 +// 30 | m/d/y +// 31 | yyyy"5E74"m"6708"d"65E5 +// 32 | hh"6642"mm"5206 +// 33 | hh"6642"mm"5206"ss"79D2 +// 34 | 4E0A5348/4E0B5348hh"6642"mm"5206 +// 35 | 4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2 +// 36 | [$-404]e/m/ +// 50 | [$-404]e/m/ +// 51 | [$-404]e"5E74"m"6708"d"65E5 +// 52 | 4E0A5348/4E0B5348hh"6642"mm"5206 +// 53 | 4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2 +// 54 | [$-404]e"5E74"m"6708"d"65E5 +// 55 | 4E0A5348/4E0B5348hh"6642"mm"5206 +// 56 | 4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2 +// 57 | [$-404]e/m/ +// 58 | [$-404]e"5E74"m"6708"d"65E5" +// +// Number format code with unicode values provided for language glyphs where +// they occur in zh-cn language: +// +// Index | Symbol +// -------+------------------------------------------- +// 27 | yyyy"5E74"m"6708 +// 28 | m"6708"d"65E5 +// 29 | m"6708"d"65E5 +// 30 | m-d-y +// 31 | yyyy"5E74"m"6708"d"65E5 +// 32 | h"65F6"mm"5206 +// 33 | h"65F6"mm"5206"ss"79D2 +// 34 | 4E0A5348/4E0B5348h"65F6"mm"5206 +// 35 | 4E0A5348/4E0B5348h"65F6"mm"5206"ss"79D2 +// 36 | yyyy"5E74"m"6708 +// 50 | yyyy"5E74"m"6708 +// 51 | m"6708"d"65E5 +// 52 | yyyy"5E74"m"6708 +// 53 | m"6708"d"65E5 +// 54 | m"6708"d"65E5 +// 55 | 4E0A5348/4E0B5348h"65F6"mm"5206 +// 56 | 4E0A5348/4E0B5348h"65F6"mm"5206"ss"79D2 +// 57 | yyyy"5E74"m"6708 +// 58 | m"6708"d"65E5" +// +// Number format code in ja-jp language: +// +// Index | Symbol +// -------+------------------------------------------- +// 27 | [$-411]ge.m.d +// 28 | [$-411]ggge"年"m"月"d"日 +// 29 | [$-411]ggge"年"m"月"d"日 +// 30 | m/d/y +// 31 | yyyy"年"m"月"d"日 +// 32 | h"時"mm"分 +// 33 | h"時"mm"分"ss"秒 +// 34 | yyyy"年"m"月 +// 35 | m"月"d"日 +// 36 | [$-411]ge.m.d +// 50 | [$-411]ge.m.d +// 51 | [$-411]ggge"年"m"月"d"日 +// 52 | yyyy"年"m"月 +// 53 | m"月"d"日 +// 54 | [$-411]ggge"年"m"月"d"日 +// 55 | yyyy"年"m"月 +// 56 | m"月"d"日 +// 57 | [$-411]ge.m.d +// 58 | [$-411]ggge"年"m"月"d"日" +// +// Number format code in ko-kr language: +// +// Index | Symbol +// -------+------------------------------------------- +// 27 | yyyy"年" mm"月" dd"日 +// 28 | mm-d +// 29 | mm-d +// 30 | mm-dd-y +// 31 | yyyy"년" mm"월" dd"일 +// 32 | h"시" mm"분 +// 33 | h"시" mm"분" ss"초 +// 34 | yyyy-mm-d +// 35 | yyyy-mm-d +// 36 | yyyy"年" mm"月" dd"日 +// 50 | yyyy"年" mm"月" dd"日 +// 51 | mm-d +// 52 | yyyy-mm-d +// 53 | yyyy-mm-d +// 54 | mm-d +// 55 | yyyy-mm-d +// 56 | yyyy-mm-d +// 57 | yyyy"年" mm"月" dd"日 +// 58 | mm-dd +// +// Number format code with unicode values provided for language glyphs where +// they occur in ja-jp language: +// +// Index | Symbol +// -------+------------------------------------------- +// 27 | [$-411]ge.m.d +// 28 | [$-411]ggge"5E74"m"6708"d"65E5 +// 29 | [$-411]ggge"5E74"m"6708"d"65E5 +// 30 | m/d/y +// 31 | yyyy"5E74"m"6708"d"65E5 +// 32 | h"6642"mm"5206 +// 33 | h"6642"mm"5206"ss"79D2 +// 34 | yyyy"5E74"m"6708 +// 35 | m"6708"d"65E5 +// 36 | [$-411]ge.m.d +// 50 | [$-411]ge.m.d +// 51 | [$-411]ggge"5E74"m"6708"d"65E5 +// 52 | yyyy"5E74"m"6708 +// 53 | m"6708"d"65E5 +// 54 | [$-411]ggge"5E74"m"6708"d"65E5 +// 55 | yyyy"5E74"m"6708 +// 56 | m"6708"d"65E5 +// 57 | [$-411]ge.m.d +// 58 | [$-411]ggge"5E74"m"6708"d"65E5" +// +// Number format code with unicode values provided for language glyphs where +// they occur in ko-kr language: +// +// Index | Symbol +// -------+------------------------------------------- +// 27 | yyyy"5E74" mm"6708" dd"65E5 +// 28 | mm-d +// 29 | mm-d +// 30 | mm-dd-y +// 31 | yyyy"B144" mm"C6D4" dd"C77C +// 32 | h"C2DC" mm"BD84 +// 33 | h"C2DC" mm"BD84" ss"CD08 +// 34 | yyyy-mm-d +// 35 | yyyy-mm-d +// 36 | yyyy"5E74" mm"6708" dd"65E5 +// 50 | yyyy"5E74" mm"6708" dd"65E5 +// 51 | mm-d +// 52 | yyyy-mm-d +// 53 | yyyy-mm-d +// 54 | mm-d +// 55 | yyyy-mm-d +// 56 | yyyy-mm-d +// 57 | yyyy"5E74" mm"6708" dd"65E5 +// 58 | mm-dd +// +// Number format code in th-th language: +// +// Index | Symbol +// -------+------------------------------------------- +// 59 | t +// 60 | t0.0 +// 61 | t#,## +// 62 | t#,##0.0 +// 67 | t0 +// 68 | t0.00 +// 69 | t# ?/ +// 70 | t# ??/? +// 71 | ว/ด/ปปป +// 72 | ว-ดดด-ป +// 73 | ว-ดด +// 74 | ดดด-ป +// 75 | ช:น +// 76 | ช:นน:ท +// 77 | ว/ด/ปปปป ช:น +// 78 | นน:ท +// 79 | [ช]:นน:ท +// 80 | นน:ทท. +// 81 | d/m/bb +// +// Number format code with unicode values provided for language glyphs where +// they occur in th-th language: +// +// Index | Symbol +// -------+------------------------------------------- +// 59 | t +// 60 | t0.0 +// 61 | t#,## +// 62 | t#,##0.0 +// 67 | t0 +// 68 | t0.00 +// 69 | t# ?/ +// 70 | t# ??/? +// 71 | 0E27/0E14/0E1B0E1B0E1B0E1 +// 72 | 0E27-0E140E140E14-0E1B0E1 +// 73 | 0E27-0E140E140E1 +// 74 | 0E140E140E14-0E1B0E1 +// 75 | 0E0A:0E190E1 +// 76 | 0E0A:0E190E19:0E170E1 +// 77 | 0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E1 +// 78 | 0E190E19:0E170E1 +// 79 | [0E0A]:0E190E19:0E170E1 +// 80 | 0E190E19:0E170E17. +// 81 | d/m/bb +// +// Excelize built-in currency formats are shown in the following table, only +// support these types in the following table (Index number is used only for +// markup and is not used inside an Excel file and you can't get formatted value +// by the function GetCellValue) currently: +// +// Index | Symbol +// -------+--------------------------------------------------------------- +// 164 | CN¥ +// 165 | $ English (China) +// 166 | $ Cherokee (United States) +// 167 | $ Chinese (Singapore) +// 168 | $ Chinese (Taiwan) +// 169 | $ English (Australia) +// 170 | $ English (Belize) +// 171 | $ English (Canada) +// 172 | $ English (Jamaica) +// 173 | $ English (New Zealand) +// 174 | $ English (Singapore) +// 175 | $ English (Trinidad & Tobago) +// 176 | $ English (U.S. Virgin Islands) +// 177 | $ English (United States) +// 178 | $ French (Canada) +// 179 | $ Hawaiian (United States) +// 180 | $ Malay (Brunei) +// 181 | $ Quechua (Ecuador) +// 182 | $ Spanish (Chile) +// 183 | $ Spanish (Colombia) +// 184 | $ Spanish (Ecuador) +// 185 | $ Spanish (El Salvador) +// 186 | $ Spanish (Mexico) +// 187 | $ Spanish (Puerto Rico) +// 188 | $ Spanish (United States) +// 189 | $ Spanish (Uruguay) +// 190 | £ English (United Kingdom) +// 191 | £ Scottish Gaelic (United Kingdom) +// 192 | £ Welsh (United Kindom) +// 193 | ¥ Chinese (China) +// 194 | ¥ Japanese (Japan) +// 195 | ¥ Sichuan Yi (China) +// 196 | ¥ Tibetan (China) +// 197 | ¥ Uyghur (China) +// 198 | ֏ Armenian (Armenia) +// 199 | ؋ Pashto (Afghanistan) +// 200 | ؋ Persian (Afghanistan) +// 201 | ৳ Bengali (Bangladesh) +// 202 | ៛ Khmer (Cambodia) +// 203 | ₡ Spanish (Costa Rica) +// 204 | ₦ Hausa (Nigeria) +// 205 | ₦ Igbo (Nigeria) +// 206 | ₦ Yoruba (Nigeria) +// 207 | ₩ Korean (South Korea) +// 208 | ₪ Hebrew (Israel) +// 209 | ₫ Vietnamese (Vietnam) +// 210 | € Basque (Spain) +// 211 | € Breton (France) +// 212 | € Catalan (Spain) +// 213 | € Corsican (France) +// 214 | € Dutch (Belgium) +// 215 | € Dutch (Netherlands) +// 216 | € English (Ireland) +// 217 | € Estonian (Estonia) +// 218 | € Euro (€ 123) +// 219 | € Euro (123 €) +// 220 | € Finnish (Finland) +// 221 | € French (Belgium) +// 222 | € French (France) +// 223 | € French (Luxembourg) +// 224 | € French (Monaco) +// 225 | € French (Réunion) +// 226 | € Galician (Spain) +// 227 | € German (Austria) +// 228 | € German (Luxembourg) +// 229 | € Greek (Greece) +// 230 | € Inari Sami (Finland) +// 231 | € Irish (Ireland) +// 232 | € Italian (Italy) +// 233 | € Latin (Italy) +// 234 | € Latin, Serbian (Montenegro) +// 235 | € Larvian (Latvia) +// 236 | € Lithuanian (Lithuania) +// 237 | € Lower Sorbian (Germany) +// 238 | € Luxembourgish (Luxembourg) +// 239 | € Maltese (Malta) +// 240 | € Northern Sami (Finland) +// 241 | € Occitan (France) +// 242 | € Portuguese (Portugal) +// 243 | € Serbian (Montenegro) +// 244 | € Skolt Sami (Finland) +// 245 | € Slovak (Slovakia) +// 246 | € Slovenian (Slovenia) +// 247 | € Spanish (Spain) +// 248 | € Swedish (Finland) +// 249 | € Swiss German (France) +// 250 | € Upper Sorbian (Germany) +// 251 | € Western Frisian (Netherlands) +// 252 | ₭ Lao (Laos) +// 253 | ₮ Mongolian (Mongolia) +// 254 | ₮ Mongolian, Mongolian (Mongolia) +// 255 | ₱ English (Philippines) +// 256 | ₱ Filipino (Philippines) +// 257 | ₴ Ukrainian (Ukraine) +// 258 | ₸ Kazakh (Kazakhstan) +// 259 | ₹ Arabic, Kashmiri (India) +// 260 | ₹ English (India) +// 261 | ₹ Gujarati (India) +// 262 | ₹ Hindi (India) +// 263 | ₹ Kannada (India) +// 264 | ₹ Kashmiri (India) +// 265 | ₹ Konkani (India) +// 266 | ₹ Manipuri (India) +// 267 | ₹ Marathi (India) +// 268 | ₹ Nepali (India) +// 269 | ₹ Oriya (India) +// 270 | ₹ Punjabi (India) +// 271 | ₹ Sanskrit (India) +// 272 | ₹ Sindhi (India) +// 273 | ₹ Tamil (India) +// 274 | ₹ Urdu (India) +// 275 | ₺ Turkish (Turkey) +// 276 | ₼ Azerbaijani (Azerbaijan) +// 277 | ₼ Cyrillic, Azerbaijani (Azerbaijan) +// 278 | ₽ Russian (Russia) +// 279 | ₽ Sakha (Russia) +// 280 | ₾ Georgian (Georgia) +// 281 | B/. Spanish (Panama) +// 282 | Br Oromo (Ethiopia) +// 283 | Br Somali (Ethiopia) +// 284 | Br Tigrinya (Ethiopia) +// 285 | Bs Quechua (Bolivia) +// 286 | Bs Spanish (Bolivia) +// 287 | BS. Spanish (Venezuela) +// 288 | BWP Tswana (Botswana) +// 289 | C$ Spanish (Nicaragua) +// 290 | CA$ Latin, Inuktitut (Canada) +// 291 | CA$ Mohawk (Canada) +// 292 | CA$ Unified Canadian Aboriginal Syllabics, Inuktitut (Canada) +// 293 | CFA French (Mali) +// 294 | CFA French (Senegal) +// 295 | CFA Fulah (Senegal) +// 296 | CFA Wolof (Senegal) +// 297 | CHF French (Switzerland) +// 298 | CHF German (Liechtenstein) +// 299 | CHF German (Switzerland) +// 300 | CHF Italian (Switzerland) +// 301 | CHF Romansh (Switzerland) +// 302 | CLP Mapuche (Chile) +// 303 | CN¥ Mongolian, Mongolian (China) +// 304 | DZD Central Atlas Tamazight (Algeria) +// 305 | FCFA French (Cameroon) +// 306 | Ft Hungarian (Hungary) +// 307 | G French (Haiti) +// 308 | Gs. Spanish (Paraguay) +// 309 | GTQ K'iche' (Guatemala) +// 310 | HK$ Chinese (Hong Kong (China)) +// 311 | HK$ English (Hong Kong (China)) +// 312 | HRK Croatian (Croatia) +// 313 | IDR English (Indonesia) +// 314 | IQD Arbic, Central Kurdish (Iraq) +// 315 | ISK Icelandic (Iceland) +// 316 | K Burmese (Myanmar (Burma)) +// 317 | Kč Czech (Czech Republic) +// 318 | KM Bosnian (Bosnia & Herzegovina) +// 319 | KM Croatian (Bosnia & Herzegovina) +// 320 | KM Latin, Serbian (Bosnia & Herzegovina) +// 321 | kr Faroese (Faroe Islands) +// 322 | kr Northern Sami (Norway) +// 323 | kr Northern Sami (Sweden) +// 324 | kr Norwegian Bokmål (Norway) +// 325 | kr Norwegian Nynorsk (Norway) +// 326 | kr Swedish (Sweden) +// 327 | kr. Danish (Denmark) +// 328 | kr. Kalaallisut (Greenland) +// 329 | Ksh Swahili (kenya) +// 330 | L Romanian (Moldova) +// 331 | L Russian (Moldova) +// 332 | L Spanish (Honduras) +// 333 | Lekë Albanian (Albania) +// 334 | MAD Arabic, Central Atlas Tamazight (Morocco) +// 335 | MAD French (Morocco) +// 336 | MAD Tifinagh, Central Atlas Tamazight (Morocco) +// 337 | MOP$ Chinese (Macau (China)) +// 338 | MVR Divehi (Maldives) +// 339 | Nfk Tigrinya (Eritrea) +// 340 | NGN Bini (Nigeria) +// 341 | NGN Fulah (Nigeria) +// 342 | NGN Ibibio (Nigeria) +// 343 | NGN Kanuri (Nigeria) +// 344 | NOK Lule Sami (Norway) +// 345 | NOK Southern Sami (Norway) +// 346 | NZ$ Maori (New Zealand) +// 347 | PKR Sindhi (Pakistan) +// 348 | PYG Guarani (Paraguay) +// 349 | Q Spanish (Guatemala) +// 350 | R Afrikaans (South Africa) +// 351 | R English (South Africa) +// 352 | R Zulu (South Africa) +// 353 | R$ Portuguese (Brazil) +// 354 | RD$ Spanish (Dominican Republic) +// 355 | RF Kinyarwanda (Rwanda) +// 356 | RM English (Malaysia) +// 357 | RM Malay (Malaysia) +// 358 | RON Romanian (Romania) +// 359 | Rp Indonesoan (Indonesia) +// 360 | Rs Urdu (Pakistan) +// 361 | Rs. Tamil (Sri Lanka) +// 362 | RSD Latin, Serbian (Serbia) +// 363 | RSD Serbian (Serbia) +// 364 | RUB Bashkir (Russia) +// 365 | RUB Tatar (Russia) +// 366 | S/. Quechua (Peru) +// 367 | S/. Spanish (Peru) +// 368 | SEK Lule Sami (Sweden) +// 369 | SEK Southern Sami (Sweden) +// 370 | soʻm Latin, Uzbek (Uzbekistan) +// 371 | soʻm Uzbek (Uzbekistan) +// 372 | SYP Syriac (Syria) +// 373 | THB Thai (Thailand) +// 374 | TMT Turkmen (Turkmenistan) +// 375 | US$ English (Zimbabwe) +// 376 | ZAR Northern Sotho (South Africa) +// 377 | ZAR Southern Sotho (South Africa) +// 378 | ZAR Tsonga (South Africa) +// 379 | ZAR Tswana (south Africa) +// 380 | ZAR Venda (South Africa) +// 381 | ZAR Xhosa (South Africa) +// 382 | zł Polish (Poland) +// 383 | ден Macedonian (Macedonia) +// 384 | KM Cyrillic, Bosnian (Bosnia & Herzegovina) +// 385 | KM Serbian (Bosnia & Herzegovina) +// 386 | лв. Bulgarian (Bulgaria) +// 387 | p. Belarusian (Belarus) +// 388 | сом Kyrgyz (Kyrgyzstan) +// 389 | сом Tajik (Tajikistan) +// 390 | ج.م. Arabic (Egypt) +// 391 | د.أ. Arabic (Jordan) +// 392 | د.أ. Arabic (United Arab Emirates) +// 393 | د.ب. Arabic (Bahrain) +// 394 | د.ت. Arabic (Tunisia) +// 395 | د.ج. Arabic (Algeria) +// 396 | د.ع. Arabic (Iraq) +// 397 | د.ك. Arabic (Kuwait) +// 398 | د.ل. Arabic (Libya) +// 399 | د.م. Arabic (Morocco) +// 400 | ر Punjabi (Pakistan) +// 401 | ر.س. Arabic (Saudi Arabia) +// 402 | ر.ع. Arabic (Oman) +// 403 | ر.ق. Arabic (Qatar) +// 404 | ر.ي. Arabic (Yemen) +// 405 | ریال Persian (Iran) +// 406 | ل.س. Arabic (Syria) +// 407 | ل.ل. Arabic (Lebanon) +// 408 | ብር Amharic (Ethiopia) +// 409 | रू Nepaol (Nepal) +// 410 | රු. Sinhala (Sri Lanka) +// 411 | ADP +// 412 | AED +// 413 | AFA +// 414 | AFN +// 415 | ALL +// 416 | AMD +// 417 | ANG +// 418 | AOA +// 419 | ARS +// 420 | ATS +// 421 | AUD +// 422 | AWG +// 423 | AZM +// 424 | AZN +// 425 | BAM +// 426 | BBD +// 427 | BDT +// 428 | BEF +// 429 | BGL +// 430 | BGN +// 431 | BHD +// 432 | BIF +// 433 | BMD +// 434 | BND +// 435 | BOB +// 436 | BOV +// 437 | BRL +// 438 | BSD +// 439 | BTN +// 440 | BWP +// 441 | BYR +// 442 | BZD +// 443 | CAD +// 444 | CDF +// 445 | CHE +// 446 | CHF +// 447 | CHW +// 448 | CLF +// 449 | CLP +// 450 | CNY +// 451 | COP +// 452 | COU +// 453 | CRC +// 454 | CSD +// 455 | CUC +// 456 | CVE +// 457 | CYP +// 458 | CZK +// 459 | DEM +// 460 | DJF +// 461 | DKK +// 462 | DOP +// 463 | DZD +// 464 | ECS +// 465 | ECV +// 466 | EEK +// 467 | EGP +// 468 | ERN +// 469 | ESP +// 470 | ETB +// 471 | EUR +// 472 | FIM +// 473 | FJD +// 474 | FKP +// 475 | FRF +// 476 | GBP +// 477 | GEL +// 478 | GHC +// 479 | GHS +// 480 | GIP +// 481 | GMD +// 482 | GNF +// 483 | GRD +// 484 | GTQ +// 485 | GYD +// 486 | HKD +// 487 | HNL +// 488 | HRK +// 489 | HTG +// 490 | HUF +// 491 | IDR +// 492 | IEP +// 493 | ILS +// 494 | INR +// 495 | IQD +// 496 | IRR +// 497 | ISK +// 498 | ITL +// 499 | JMD +// 500 | JOD +// 501 | JPY +// 502 | KAF +// 503 | KES +// 504 | KGS +// 505 | KHR +// 506 | KMF +// 507 | KPW +// 508 | KRW +// 509 | KWD +// 510 | KYD +// 511 | KZT +// 512 | LAK +// 513 | LBP +// 514 | LKR +// 515 | LRD +// 516 | LSL +// 517 | LTL +// 518 | LUF +// 519 | LVL +// 520 | LYD +// 521 | MAD +// 522 | MDL +// 523 | MGA +// 524 | MGF +// 525 | MKD +// 526 | MMK +// 527 | MNT +// 528 | MOP +// 529 | MRO +// 530 | MTL +// 531 | MUR +// 532 | MVR +// 533 | MWK +// 534 | MXN +// 535 | MXV +// 536 | MYR +// 537 | MZM +// 538 | MZN +// 539 | NAD +// 540 | NGN +// 541 | NIO +// 542 | NLG +// 543 | NOK +// 544 | NPR +// 545 | NTD +// 546 | NZD +// 547 | OMR +// 548 | PAB +// 549 | PEN +// 550 | PGK +// 551 | PHP +// 552 | PKR +// 553 | PLN +// 554 | PTE +// 555 | PYG +// 556 | QAR +// 557 | ROL +// 558 | RON +// 559 | RSD +// 560 | RUB +// 561 | RUR +// 562 | RWF +// 563 | SAR +// 564 | SBD +// 565 | SCR +// 566 | SDD +// 567 | SDG +// 568 | SDP +// 569 | SEK +// 570 | SGD +// 571 | SHP +// 572 | SIT +// 573 | SKK +// 574 | SLL +// 575 | SOS +// 576 | SPL +// 577 | SRD +// 578 | SRG +// 579 | STD +// 580 | SVC +// 581 | SYP +// 582 | SZL +// 583 | THB +// 584 | TJR +// 585 | TJS +// 586 | TMM +// 587 | TMT +// 588 | TND +// 589 | TOP +// 590 | TRL +// 591 | TRY +// 592 | TTD +// 593 | TWD +// 594 | TZS +// 595 | UAH +// 596 | UGX +// 597 | USD +// 598 | USN +// 599 | USS +// 600 | UYI +// 601 | UYU +// 602 | UZS +// 603 | VEB +// 604 | VEF +// 605 | VND +// 606 | VUV +// 607 | WST +// 608 | XAF +// 609 | XAG +// 610 | XAU +// 611 | XB5 +// 612 | XBA +// 613 | XBB +// 614 | XBC +// 615 | XBD +// 616 | XCD +// 617 | XDR +// 618 | XFO +// 619 | XFU +// 620 | XOF +// 621 | XPD +// 622 | XPF +// 623 | XPT +// 624 | XTS +// 625 | XXX +// 626 | YER +// 627 | YUM +// 628 | ZAR +// 629 | ZMK +// 630 | ZMW +// 631 | ZWD +// 632 | ZWL +// 633 | ZWN +// 634 | ZWR +// +// Excelize support set custom number format for cell. For example, set number +// as date type in Uruguay (Spanish) format for Sheet1!A6: +// +// f := excelize.NewFile() +// f.SetCellValue("Sheet1", "A6", 42920.5) +// exp := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@" +// style, err := f.NewStyle(&excelize.Style{CustomNumFmt: &exp}) +// err = f.SetCellStyle("Sheet1", "A6", "A6", style) +// +// Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017 +// +func (f *File) NewStyle(style interface{}) (int, error) { + var fs *Style + var err error + var cellXfsID, fontID, borderID, fillID int + fs, err = parseFormatStyleSet(style) + if err != nil { + return cellXfsID, err + } + if fs.DecimalPlaces == 0 { + fs.DecimalPlaces = 2 + } + s := f.stylesReader() + s.Lock() + defer s.Unlock() + // check given style already exist. + if cellXfsID = f.getStyleID(s, fs); cellXfsID != -1 { + return cellXfsID, err + } + + numFmtID := newNumFmt(s, fs) + + if fs.Font != nil { + fontID = f.getFontID(s, fs) + if fontID == -1 { + s.Fonts.Count++ + s.Fonts.Font = append(s.Fonts.Font, f.newFont(fs)) + fontID = s.Fonts.Count - 1 + } + } + + borderID = getBorderID(s, fs) + if borderID == -1 { + if len(fs.Border) == 0 { + borderID = 0 + } else { + s.Borders.Count++ + s.Borders.Border = append(s.Borders.Border, newBorders(fs)) + borderID = s.Borders.Count - 1 + } + } + + if fillID = getFillID(s, fs); fillID == -1 { + if fill := newFills(fs, true); fill != nil { + s.Fills.Count++ + s.Fills.Fill = append(s.Fills.Fill, fill) + fillID = s.Fills.Count - 1 + } else { + fillID = 0 + } + } + + applyAlignment, alignment := fs.Alignment != nil, newAlignment(fs) + applyProtection, protection := fs.Protection != nil, newProtection(fs) + cellXfsID = setCellXfs(s, fontID, numFmtID, fillID, borderID, applyAlignment, applyProtection, alignment, protection) + return cellXfsID, nil +} + +var getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{ + "numFmt": func(numFmtID int, xf xlsxXf, style *Style) bool { + if style.CustomNumFmt == nil && numFmtID == -1 { + return xf.NumFmtID != nil && *xf.NumFmtID == 0 + } + if style.NegRed || style.Lang != "" || style.DecimalPlaces != 2 { + return false + } + return xf.NumFmtID != nil && *xf.NumFmtID == numFmtID + }, + "font": func(fontID int, xf xlsxXf, style *Style) bool { + if style.Font == nil { + return (xf.FontID == nil || *xf.FontID == 0) && (xf.ApplyFont == nil || !*xf.ApplyFont) + } + return xf.FontID != nil && *xf.FontID == fontID && xf.ApplyFont != nil && *xf.ApplyFont + }, + "fill": func(fillID int, xf xlsxXf, style *Style) bool { + if style.Fill.Type == "" { + return (xf.FillID == nil || *xf.FillID == 0) && (xf.ApplyFill == nil || !*xf.ApplyFill) + } + return xf.FillID != nil && *xf.FillID == fillID && xf.ApplyFill != nil && *xf.ApplyFill + }, + "border": func(borderID int, xf xlsxXf, style *Style) bool { + if len(style.Border) == 0 { + return (xf.BorderID == nil || *xf.BorderID == 0) && (xf.ApplyBorder == nil || !*xf.ApplyBorder) + } + return xf.BorderID != nil && *xf.BorderID == borderID && xf.ApplyBorder != nil && *xf.ApplyBorder + }, + "alignment": func(ID int, xf xlsxXf, style *Style) bool { + if style.Alignment == nil { + return xf.ApplyAlignment == nil || !*xf.ApplyAlignment + } + return reflect.DeepEqual(xf.Alignment, newAlignment(style)) + }, + "protection": func(ID int, xf xlsxXf, style *Style) bool { + if style.Protection == nil { + return xf.ApplyProtection == nil || !*xf.ApplyProtection + } + return reflect.DeepEqual(xf.Protection, newProtection(style)) && xf.ApplyProtection != nil && *xf.ApplyProtection + }, +} + +// getStyleID provides a function to get styleID by given style. If given +// style is not exist, will return -1. +func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) { + styleID = -1 + if ss.CellXfs == nil { + return + } + numFmtID, borderID, fillID, fontID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style), f.getFontID(ss, style) + if style.CustomNumFmt != nil { + numFmtID = getCustomNumFmtID(ss, style) + } + for xfID, xf := range ss.CellXfs.Xf { + if getXfIDFuncs["numFmt"](numFmtID, xf, style) && + getXfIDFuncs["font"](fontID, xf, style) && + getXfIDFuncs["fill"](fillID, xf, style) && + getXfIDFuncs["border"](borderID, xf, style) && + getXfIDFuncs["alignment"](0, xf, style) && + getXfIDFuncs["protection"](0, xf, style) { + styleID = xfID + return + } + } + return +} + +// NewConditionalStyle provides a function to create style for conditional +// format by given style format. The parameters are the same as function +// NewStyle(). Note that the color field uses RGB color code and only support +// to set font, fills, alignment and borders currently. +func (f *File) NewConditionalStyle(style string) (int, error) { + s := f.stylesReader() + fs, err := parseFormatStyleSet(style) + if err != nil { + return 0, err + } + dxf := dxf{ + Fill: newFills(fs, false), + } + if fs.Alignment != nil { + dxf.Alignment = newAlignment(fs) + } + if len(fs.Border) > 0 { + dxf.Border = newBorders(fs) + } + if fs.Font != nil { + dxf.Font = f.newFont(fs) + } + dxfStr, _ := xml.Marshal(dxf) + if s.Dxfs == nil { + s.Dxfs = &xlsxDxfs{} + } + s.Dxfs.Count++ + s.Dxfs.Dxfs = append(s.Dxfs.Dxfs, &xlsxDxf{ + Dxf: string(dxfStr[5 : len(dxfStr)-6]), + }) + return s.Dxfs.Count - 1, nil +} + +// GetDefaultFont provides the default font name currently set in the +// workbook. The spreadsheet generated by excelize default font is Calibri. +func (f *File) GetDefaultFont() string { + font := f.readDefaultFont() + return *font.Name.Val +} + +// SetDefaultFont changes the default font in the workbook. +func (f *File) SetDefaultFont(fontName string) { + font := f.readDefaultFont() + font.Name.Val = stringPtr(fontName) + s := f.stylesReader() + s.Fonts.Font[0] = font + custom := true + s.CellStyles.CellStyle[0].CustomBuiltIn = &custom +} + +// readDefaultFont provides an un-marshalled font value. +func (f *File) readDefaultFont() *xlsxFont { + s := f.stylesReader() + return s.Fonts.Font[0] +} + +// getFontID provides a function to get font ID. +// If given font is not exist, will return -1. +func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) (fontID int) { + fontID = -1 + if styleSheet.Fonts == nil || style.Font == nil { + return + } + for idx, fnt := range styleSheet.Fonts.Font { + if reflect.DeepEqual(*fnt, *f.newFont(style)) { + fontID = idx + return + } + } + return +} + +// newFont provides a function to add font style by given cell format +// settings. +func (f *File) newFont(style *Style) *xlsxFont { + fontUnderlineType := map[string]string{"single": "single", "double": "double"} + if style.Font.Size < 1 { + style.Font.Size = 11 + } + if style.Font.Color == "" { + style.Font.Color = "#000000" + } + fnt := xlsxFont{ + Sz: &attrValFloat{Val: float64Ptr(style.Font.Size)}, + Color: &xlsxColor{RGB: getPaletteColor(style.Font.Color)}, + Name: &attrValString{Val: stringPtr(style.Font.Family)}, + Family: &attrValInt{Val: intPtr(2)}, + } + if style.Font.Bold { + fnt.B = &attrValBool{Val: &style.Font.Bold} + } + if style.Font.Italic { + fnt.I = &attrValBool{Val: &style.Font.Italic} + } + if *fnt.Name.Val == "" { + *fnt.Name.Val = f.GetDefaultFont() + } + if style.Font.Strike { + fnt.Strike = &attrValBool{Val: &style.Font.Strike} + } + val, ok := fontUnderlineType[style.Font.Underline] + if ok { + fnt.U = &attrValString{Val: stringPtr(val)} + } + return &fnt +} + +// getNumFmtID provides a function to get number format code ID. +// If given number format code is not exist, will return -1. +func getNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (numFmtID int) { + numFmtID = -1 + if _, ok := builtInNumFmt[style.NumFmt]; ok { + return style.NumFmt + } + for lang, numFmt := range langNumFmt { + if _, ok := numFmt[style.NumFmt]; ok && lang == style.Lang { + numFmtID = style.NumFmt + return + } + } + if fmtCode, ok := currencyNumFmt[style.NumFmt]; ok { + numFmtID = style.NumFmt + if styleSheet.NumFmts != nil { + for _, numFmt := range styleSheet.NumFmts.NumFmt { + if numFmt.FormatCode == fmtCode { + numFmtID = numFmt.NumFmtID + return + } + } + } + } + return +} + +// newNumFmt provides a function to check if number format code in the range +// of built-in values. +func newNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { + dp := "0." + numFmtID := 164 // Default custom number format code from 164. + if style.DecimalPlaces < 0 || style.DecimalPlaces > 30 { + style.DecimalPlaces = 2 + } + for i := 0; i < style.DecimalPlaces; i++ { + dp += "0" + } + if style.CustomNumFmt != nil { + if customNumFmtID := getCustomNumFmtID(styleSheet, style); customNumFmtID != -1 { + return customNumFmtID + } + return setCustomNumFmt(styleSheet, style) + } + _, ok := builtInNumFmt[style.NumFmt] + if !ok { + fc, currency := currencyNumFmt[style.NumFmt] + if !currency { + return setLangNumFmt(styleSheet, style) + } + fc = strings.Replace(fc, "0.00", dp, -1) + if style.NegRed { + fc = fc + ";[Red]" + fc + } + if styleSheet.NumFmts != nil { + numFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 + nf := xlsxNumFmt{ + FormatCode: fc, + NumFmtID: numFmtID, + } + styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) + styleSheet.NumFmts.Count++ + } else { + nf := xlsxNumFmt{ + FormatCode: fc, + NumFmtID: numFmtID, + } + numFmts := xlsxNumFmts{ + NumFmt: []*xlsxNumFmt{&nf}, + Count: 1, + } + styleSheet.NumFmts = &numFmts + } + return numFmtID + } + return style.NumFmt +} + +// setCustomNumFmt provides a function to set custom number format code. +func setCustomNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { + nf := xlsxNumFmt{FormatCode: *style.CustomNumFmt} + + if styleSheet.NumFmts != nil { + nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 + styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) + styleSheet.NumFmts.Count++ + } else { + nf.NumFmtID = 164 + numFmts := xlsxNumFmts{ + NumFmt: []*xlsxNumFmt{&nf}, + Count: 1, + } + styleSheet.NumFmts = &numFmts + } + return nf.NumFmtID +} + +// getCustomNumFmtID provides a function to get custom number format code ID. +// If given custom number format code is not exist, will return -1. +func getCustomNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (customNumFmtID int) { + customNumFmtID = -1 + if styleSheet.NumFmts == nil { + return + } + for _, numFmt := range styleSheet.NumFmts.NumFmt { + if style.CustomNumFmt != nil && numFmt.FormatCode == *style.CustomNumFmt { + customNumFmtID = numFmt.NumFmtID + return + } + } + return +} + +// setLangNumFmt provides a function to set number format code with language. +func setLangNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { + numFmts, ok := langNumFmt[style.Lang] + if !ok { + return 0 + } + var fc string + fc, ok = numFmts[style.NumFmt] + if !ok { + return 0 + } + nf := xlsxNumFmt{FormatCode: fc} + if styleSheet.NumFmts != nil { + nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 + styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) + styleSheet.NumFmts.Count++ + } else { + nf.NumFmtID = style.NumFmt + numFmts := xlsxNumFmts{ + NumFmt: []*xlsxNumFmt{&nf}, + Count: 1, + } + styleSheet.NumFmts = &numFmts + } + return nf.NumFmtID +} + +// getFillID provides a function to get fill ID. If given fill is not +// exist, will return -1. +func getFillID(styleSheet *xlsxStyleSheet, style *Style) (fillID int) { + fillID = -1 + if styleSheet.Fills == nil || style.Fill.Type == "" { + return + } + fills := newFills(style, true) + if fills == nil { + return + } + for idx, fill := range styleSheet.Fills.Fill { + if reflect.DeepEqual(fill, fills) { + fillID = idx + return + } + } + return +} + +// newFills provides a function to add fill elements in the styles.xml by +// given cell format settings. +func newFills(style *Style, fg bool) *xlsxFill { + patterns := []string{ + "none", + "solid", + "mediumGray", + "darkGray", + "lightGray", + "darkHorizontal", + "darkVertical", + "darkDown", + "darkUp", + "darkGrid", + "darkTrellis", + "lightHorizontal", + "lightVertical", + "lightDown", + "lightUp", + "lightGrid", + "lightTrellis", + "gray125", + "gray0625", + } + + variants := []float64{ + 90, + 0, + 45, + 135, + } + + var fill xlsxFill + switch style.Fill.Type { + case "gradient": + if len(style.Fill.Color) != 2 { + break + } + var gradient xlsxGradientFill + switch style.Fill.Shading { + case 0, 1, 2, 3: + gradient.Degree = variants[style.Fill.Shading] + case 4: + gradient.Type = "path" + case 5: + gradient.Type = "path" + gradient.Bottom = 0.5 + gradient.Left = 0.5 + gradient.Right = 0.5 + gradient.Top = 0.5 + } + var stops []*xlsxGradientFillStop + for index, color := range style.Fill.Color { + var stop xlsxGradientFillStop + stop.Position = float64(index) + stop.Color.RGB = getPaletteColor(color) + stops = append(stops, &stop) + } + gradient.Stop = stops + fill.GradientFill = &gradient + case "pattern": + if style.Fill.Pattern > 18 || style.Fill.Pattern < 0 { + break + } + if len(style.Fill.Color) < 1 { + break + } + var pattern xlsxPatternFill + pattern.PatternType = patterns[style.Fill.Pattern] + if fg { + if pattern.FgColor == nil { + pattern.FgColor = new(xlsxColor) + } + pattern.FgColor.RGB = getPaletteColor(style.Fill.Color[0]) + } else { + if pattern.BgColor == nil { + pattern.BgColor = new(xlsxColor) + } + pattern.BgColor.RGB = getPaletteColor(style.Fill.Color[0]) + } + fill.PatternFill = &pattern + default: + return nil + } + return &fill +} + +// newAlignment provides a function to formatting information pertaining to +// text alignment in cells. There are a variety of choices for how text is +// aligned both horizontally and vertically, as well as indentation settings, +// and so on. +func newAlignment(style *Style) *xlsxAlignment { + var alignment xlsxAlignment + if style.Alignment != nil { + alignment.Horizontal = style.Alignment.Horizontal + alignment.Indent = style.Alignment.Indent + alignment.JustifyLastLine = style.Alignment.JustifyLastLine + alignment.ReadingOrder = style.Alignment.ReadingOrder + alignment.RelativeIndent = style.Alignment.RelativeIndent + alignment.ShrinkToFit = style.Alignment.ShrinkToFit + alignment.TextRotation = style.Alignment.TextRotation + alignment.Vertical = style.Alignment.Vertical + alignment.WrapText = style.Alignment.WrapText + } + return &alignment +} + +// newProtection provides a function to set protection properties associated +// with the cell. +func newProtection(style *Style) *xlsxProtection { + var protection xlsxProtection + if style.Protection != nil { + protection.Hidden = &style.Protection.Hidden + protection.Locked = &style.Protection.Locked + } + return &protection +} + +// getBorderID provides a function to get border ID. If given border is not +// exist, will return -1. +func getBorderID(styleSheet *xlsxStyleSheet, style *Style) (borderID int) { + borderID = -1 + if styleSheet.Borders == nil || len(style.Border) == 0 { + return + } + for idx, border := range styleSheet.Borders.Border { + if reflect.DeepEqual(*border, *newBorders(style)) { + borderID = idx + return + } + } + return +} + +// newBorders provides a function to add border elements in the styles.xml by +// given borders format settings. +func newBorders(style *Style) *xlsxBorder { + styles := []string{ + "none", + "thin", + "medium", + "dashed", + "dotted", + "thick", + "double", + "hair", + "mediumDashed", + "dashDot", + "mediumDashDot", + "dashDotDot", + "mediumDashDotDot", + "slantDashDot", + } + + var border xlsxBorder + for _, v := range style.Border { + if 0 <= v.Style && v.Style < 14 { + var color xlsxColor + color.RGB = getPaletteColor(v.Color) + switch v.Type { + case "left": + border.Left.Style = styles[v.Style] + border.Left.Color = &color + case "right": + border.Right.Style = styles[v.Style] + border.Right.Color = &color + case "top": + border.Top.Style = styles[v.Style] + border.Top.Color = &color + case "bottom": + border.Bottom.Style = styles[v.Style] + border.Bottom.Color = &color + case "diagonalUp": + border.Diagonal.Style = styles[v.Style] + border.Diagonal.Color = &color + border.DiagonalUp = true + case "diagonalDown": + border.Diagonal.Style = styles[v.Style] + border.Diagonal.Color = &color + border.DiagonalDown = true + } + } + } + return &border +} + +// setCellXfs provides a function to set describes all of the formatting for a +// cell. +func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) int { + var xf xlsxXf + xf.FontID = intPtr(fontID) + if fontID != 0 { + xf.ApplyFont = boolPtr(true) + } + xf.NumFmtID = intPtr(numFmtID) + if numFmtID != 0 { + xf.ApplyNumberFormat = boolPtr(true) + } + xf.FillID = intPtr(fillID) + if fillID != 0 { + xf.ApplyFill = boolPtr(true) + } + xf.BorderID = intPtr(borderID) + if borderID != 0 { + xf.ApplyBorder = boolPtr(true) + } + style.CellXfs.Count++ + xf.Alignment = alignment + if alignment != nil { + xf.ApplyAlignment = boolPtr(applyAlignment) + } + if applyProtection { + xf.ApplyProtection = boolPtr(applyProtection) + xf.Protection = protection + } + xfID := 0 + xf.XfID = &xfID + style.CellXfs.Xf = append(style.CellXfs.Xf, xf) + return style.CellXfs.Count - 1 +} + +// GetCellStyle provides a function to get cell style index by given worksheet +// name and cell coordinates. +func (f *File) GetCellStyle(sheet, axis string) (int, error) { + ws, err := f.workSheetReader(sheet) + if err != nil { + return 0, err + } + cellData, col, row, err := f.prepareCell(ws, axis) + if err != nil { + return 0, err + } + return f.prepareCellStyle(ws, col, row, cellData.S), err +} + +// SetCellStyle provides a function to add style attribute for cells by given +// worksheet name, coordinate area and style ID. Note that diagonalDown and +// diagonalUp type border should be use same color in the same coordinate +// area. SetCellStyle will overwrite the existing styles for the cell, it +// won't append or merge style with existing styles. +// +// For example create a borders of cell H9 on Sheet1: +// +// style, err := f.NewStyle(&excelize.Style{ +// Border: []excelize.Border{ +// {Type: "left", Color: "0000FF", Style: 3}, +// {Type: "top", Color: "00FF00", Style: 4}, +// {Type: "bottom", Color: "FFFF00", Style: 5}, +// {Type: "right", Color: "FF0000", Style: 6}, +// {Type: "diagonalDown", Color: "A020F0", Style: 7}, +// {Type: "diagonalUp", Color: "A020F0", Style: 8}, +// }, +// }) +// if err != nil { +// fmt.Println(err) +// } +// err = f.SetCellStyle("Sheet1", "H9", "H9", style) +// +// Set gradient fill with vertical variants shading styles for cell H9 on +// Sheet1: +// +// style, err := f.NewStyle(&excelize.Style{ +// Fill: excelize.Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 1}, +// }) +// if err != nil { +// fmt.Println(err) +// } +// err = f.SetCellStyle("Sheet1", "H9", "H9", style) +// +// Set solid style pattern fill for cell H9 on Sheet1: +// +// style, err := f.NewStyle(&excelize.Style{ +// Fill: excelize.Fill{Type: "pattern", Color: []string{"#E0EBF5"}, Pattern: 1}, +// }) +// if err != nil { +// fmt.Println(err) +// } +// err = f.SetCellStyle("Sheet1", "H9", "H9", style) +// +// Set alignment style for cell H9 on Sheet1: +// +// style, err := f.NewStyle(&excelize.Style{ +// Alignment: &excelize.Alignment{ +// Horizontal: "center", +// Indent: 1, +// JustifyLastLine: true, +// ReadingOrder: 0, +// RelativeIndent: 1, +// ShrinkToFit: true, +// TextRotation: 45, +// Vertical: "", +// WrapText: true, +// }, +// }) +// if err != nil { +// fmt.Println(err) +// } +// err = f.SetCellStyle("Sheet1", "H9", "H9", style) +// +// Dates and times in Excel are represented by real numbers, for example "Apr 7 +// 2017 12:00 PM" is represented by the number 42920.5. Set date and time format +// for cell H9 on Sheet1: +// +// f.SetCellValue("Sheet1", "H9", 42920.5) +// style, err := f.NewStyle(&excelize.Style{NumFmt: 22}) +// if err != nil { +// fmt.Println(err) +// } +// err = f.SetCellStyle("Sheet1", "H9", "H9", style) +// +// Set font style for cell H9 on Sheet1: +// +// style, err := f.NewStyle(&excelize.Style{ +// Font: &excelize.Font{ +// Bold: true, +// Italic: true, +// Family: "Times New Roman", +// Size: 36, +// Color: "#777777", +// }, +// }) +// if err != nil { +// fmt.Println(err) +// } +// err = f.SetCellStyle("Sheet1", "H9", "H9", style) +// +// Hide and lock for cell H9 on Sheet1: +// +// style, err := f.NewStyle(&excelize.Style{ +// Protection: &excelize.Protection{ +// Hidden: true, +// Locked: true, +// }, +// }) +// if err != nil { +// fmt.Println(err) +// } +// err = f.SetCellStyle("Sheet1", "H9", "H9", style) +// +func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { + hCol, hRow, err := CellNameToCoordinates(hCell) + if err != nil { + return err + } + + vCol, vRow, err := CellNameToCoordinates(vCell) + if err != nil { + return err + } + + // Normalize the coordinate area, such correct C1:B3 to B1:C3. + if vCol < hCol { + vCol, hCol = hCol, vCol + } + + if vRow < hRow { + vRow, hRow = hRow, vRow + } + + hColIdx := hCol - 1 + hRowIdx := hRow - 1 + + vColIdx := vCol - 1 + vRowIdx := vRow - 1 + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + prepareSheetXML(ws, vCol, vRow) + makeContiguousColumns(ws, hRow, vRow, vCol) + ws.Lock() + defer ws.Unlock() + for r := hRowIdx; r <= vRowIdx; r++ { + for k := hColIdx; k <= vColIdx; k++ { + ws.SheetData.Row[r].C[k].S = styleID + } + } + return err +} + +// SetConditionalFormat provides a function to create conditional formatting +// rule for cell value. Conditional formatting is a feature of Excel which +// allows you to apply a format to a cell or a range of cells based on certain +// criteria. +// +// The type option is a required parameter and it has no default value. +// Allowable type values and their associated parameters are: +// +// Type | Parameters +// ---------------+------------------------------------ +// cell | criteria +// | value +// | minimum +// | maximum +// date | criteria +// | value +// | minimum +// | maximum +// time_period | criteria +// text | criteria +// | value +// average | criteria +// duplicate | (none) +// unique | (none) +// top | criteria +// | value +// bottom | criteria +// | value +// blanks | (none) +// no_blanks | (none) +// errors | (none) +// no_errors | (none) +// 2_color_scale | min_type +// | max_type +// | min_value +// | max_value +// | min_color +// | max_color +// 3_color_scale | min_type +// | mid_type +// | max_type +// | min_value +// | mid_value +// | max_value +// | min_color +// | mid_color +// | max_color +// data_bar | min_type +// | max_type +// | min_value +// | max_value +// | bar_color +// formula | criteria +// +// The criteria parameter is used to set the criteria by which the cell data +// will be evaluated. It has no default value. The most common criteria as +// applied to {"type":"cell"} are: +// +// between | +// not between | +// equal to | == +// not equal to | != +// greater than | > +// less than | < +// greater than or equal to | >= +// less than or equal to | <= +// +// You can either use Excel's textual description strings, in the first column +// above, or the more common symbolic alternatives. +// +// Additional criteria which are specific to other conditional format types are +// shown in the relevant sections below. +// +// value: The value is generally used along with the criteria parameter to set +// the rule by which the cell data will be evaluated: +// +// f.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format)) +// +// The value property can also be an cell reference: +// +// f.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"$C$1"}]`, format)) +// +// type: format - The format parameter is used to specify the format that will +// be applied to the cell when the conditional formatting criterion is met. The +// format is created using the NewConditionalStyle() method in the same way as +// cell formats: +// +// format, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) +// if err != nil { +// fmt.Println(err) +// } +// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format)) +// +// Note: In Excel, a conditional format is superimposed over the existing cell +// format and not all cell format properties can be modified. Properties that +// cannot be modified in a conditional format are font name, font size, +// superscript and subscript, diagonal borders, all alignment properties and all +// protection properties. +// +// Excel specifies some default formats to be used with conditional formatting. +// These can be replicated using the following excelize formats: +// +// // Rose format for bad conditional. +// format1, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) +// +// // Light yellow format for neutral conditional. +// format2, err = f.NewConditionalStyle(`{"font":{"color":"#9B5713"},"fill":{"type":"pattern","color":["#FEEAA0"],"pattern":1}}`) +// +// // Light green format for good conditional. +// format3, err = f.NewConditionalStyle(`{"font":{"color":"#09600B"},"fill":{"type":"pattern","color":["#C7EECF"],"pattern":1}}`) +// +// type: minimum - The minimum parameter is used to set the lower limiting value +// when the criteria is either "between" or "not between". +// +// // Hightlight cells rules: between... +// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format)) +// +// type: maximum - The maximum parameter is used to set the upper limiting value +// when the criteria is either "between" or "not between". See the previous +// example. +// +// type: average - The average type is used to specify Excel's "Average" style +// conditional format: +// +// // Top/Bottom rules: Above Average... +// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format1)) +// +// // Top/Bottom rules: Below Average... +// f.SetConditionalFormat("Sheet1", "B1:B10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format2)) +// +// type: duplicate - The duplicate type is used to highlight duplicate cells in a range: +// +// // Hightlight cells rules: Duplicate Values... +// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format)) +// +// type: unique - The unique type is used to highlight unique cells in a range: +// +// // Hightlight cells rules: Not Equal To... +// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format)) +// +// type: top - The top type is used to specify the top n values by number or percentage in a range: +// +// // Top/Bottom rules: Top 10. +// f.SetConditionalFormat("Sheet1", "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6"}]`, format)) +// +// The criteria can be used to indicate that a percentage condition is required: +// +// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format)) +// +// type: 2_color_scale - The 2_color_scale type is used to specify Excel's "2 +// Color Scale" style conditional format: +// +// // Color scales: 2 color. +// f.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`) +// +// This conditional type can be modified with min_type, max_type, min_value, +// max_value, min_color and max_color, see below. +// +// type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3 +// Color Scale" style conditional format: +// +// // Color scales: 3 color. +// f.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`) +// +// This conditional type can be modified with min_type, mid_type, max_type, +// min_value, mid_value, max_value, min_color, mid_color and max_color, see +// below. +// +// type: data_bar - The data_bar type is used to specify Excel's "Data Bar" +// style conditional format. +// +// min_type - The min_type and max_type properties are available when the conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The mid_type is available for 3_color_scale. The properties are used as follows: +// +// // Data Bars: Gradient Fill. +// f.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`) +// +// The available min/mid/max types are: +// +// min (for min_type only) +// num +// percent +// percentile +// formula +// max (for max_type only) +// +// mid_type - Used for 3_color_scale. Same as min_type, see above. +// +// max_type - Same as min_type, see above. +// +// min_value - The min_value and max_value properties are available when the +// conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The +// mid_value is available for 3_color_scale. +// +// mid_value - Used for 3_color_scale. Same as min_value, see above. +// +// max_value - Same as min_value, see above. +// +// min_color - The min_color and max_color properties are available when the +// conditional formatting type is 2_color_scale, 3_color_scale or data_bar. +// The mid_color is available for 3_color_scale. The properties are used as +// follows: +// +// // Color scales: 3 color. +// f.SetConditionalFormat("Sheet1", "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`) +// +// mid_color - Used for 3_color_scale. Same as min_color, see above. +// +// max_color - Same as min_color, see above. +// +// bar_color - Used for data_bar. Same as min_color, see above. +// +func (f *File) SetConditionalFormat(sheet, area, formatSet string) error { + var format []*formatConditional + err := json.Unmarshal([]byte(formatSet), &format) + if err != nil { + return err + } + drawContFmtFunc := map[string]func(p int, ct string, fmtCond *formatConditional) *xlsxCfRule{ + "cellIs": drawCondFmtCellIs, + "top10": drawCondFmtTop10, + "aboveAverage": drawCondFmtAboveAverage, + "duplicateValues": drawCondFmtDuplicateUniqueValues, + "uniqueValues": drawCondFmtDuplicateUniqueValues, + "2_color_scale": drawCondFmtColorScale, + "3_color_scale": drawCondFmtColorScale, + "dataBar": drawCondFmtDataBar, + "expression": drawConfFmtExp, + } + + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + var cfRule []*xlsxCfRule + for p, v := range format { + var vt, ct string + var ok bool + // "type" is a required parameter, check for valid validation types. + vt, ok = validType[v.Type] + if ok { + // Check for valid criteria types. + ct, ok = criteriaType[v.Criteria] + if ok || vt == "expression" { + drawfunc, ok := drawContFmtFunc[vt] + if ok { + cfRule = append(cfRule, drawfunc(p, ct, v)) + } + } + } + } + + ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{ + SQRef: area, + CfRule: cfRule, + }) + return err +} + +// UnsetConditionalFormat provides a function to unset the conditional format +// by given worksheet name and range. +func (f *File) UnsetConditionalFormat(sheet, area string) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + for i, cf := range ws.ConditionalFormatting { + if cf.SQRef == area { + ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i], ws.ConditionalFormatting[i+1:]...) + return nil + } + } + return nil +} + +// drawCondFmtCellIs provides a function to create conditional formatting rule +// for cell value (include between, not between, equal, not equal, greater +// than and less than) by given priority, criteria type and format settings. +func drawCondFmtCellIs(p int, ct string, format *formatConditional) *xlsxCfRule { + c := &xlsxCfRule{ + Priority: p + 1, + Type: validType[format.Type], + Operator: ct, + DxfID: &format.Format, + } + // "between" and "not between" criteria require 2 values. + _, ok := map[string]bool{"between": true, "notBetween": true}[ct] + if ok { + c.Formula = append(c.Formula, format.Minimum) + c.Formula = append(c.Formula, format.Maximum) + } + _, ok = map[string]bool{"equal": true, "notEqual": true, "greaterThan": true, "lessThan": true, "greaterThanOrEqual": true, "lessThanOrEqual": true, "containsText": true, "notContains": true, "beginsWith": true, "endsWith": true}[ct] + if ok { + c.Formula = append(c.Formula, format.Value) + } + return c +} + +// drawCondFmtTop10 provides a function to create conditional formatting rule +// for top N (default is top 10) by given priority, criteria type and format +// settings. +func drawCondFmtTop10(p int, ct string, format *formatConditional) *xlsxCfRule { + c := &xlsxCfRule{ + Priority: p + 1, + Bottom: format.Type == "bottom", + Type: validType[format.Type], + Rank: 10, + DxfID: &format.Format, + Percent: format.Percent, + } + rank, err := strconv.Atoi(format.Value) + if err == nil { + c.Rank = rank + } + return c +} + +// drawCondFmtAboveAverage provides a function to create conditional +// formatting rule for above average and below average by given priority, +// criteria type and format settings. +func drawCondFmtAboveAverage(p int, ct string, format *formatConditional) *xlsxCfRule { + return &xlsxCfRule{ + Priority: p + 1, + Type: validType[format.Type], + AboveAverage: &format.AboveAverage, + DxfID: &format.Format, + } +} + +// drawCondFmtDuplicateUniqueValues provides a function to create conditional +// formatting rule for duplicate and unique values by given priority, criteria +// type and format settings. +func drawCondFmtDuplicateUniqueValues(p int, ct string, format *formatConditional) *xlsxCfRule { + return &xlsxCfRule{ + Priority: p + 1, + Type: validType[format.Type], + DxfID: &format.Format, + } +} + +// drawCondFmtColorScale provides a function to create conditional formatting +// rule for color scale (include 2 color scale and 3 color scale) by given +// priority, criteria type and format settings. +func drawCondFmtColorScale(p int, ct string, format *formatConditional) *xlsxCfRule { + minValue := format.MinValue + if minValue == "" { + minValue = "0" + } + maxValue := format.MaxValue + if maxValue == "" { + maxValue = "0" + } + midValue := format.MidValue + if midValue == "" { + midValue = "50" + } + + c := &xlsxCfRule{ + Priority: p + 1, + Type: "colorScale", + ColorScale: &xlsxColorScale{ + Cfvo: []*xlsxCfvo{ + {Type: format.MinType, Val: minValue}, + }, + Color: []*xlsxColor{ + {RGB: getPaletteColor(format.MinColor)}, + }, + }, + } + if validType[format.Type] == "3_color_scale" { + c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MidType, Val: midValue}) + c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MidColor)}) + } + c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MaxType, Val: maxValue}) + c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MaxColor)}) + return c +} + +// drawCondFmtDataBar provides a function to create conditional formatting +// rule for data bar by given priority, criteria type and format settings. +func drawCondFmtDataBar(p int, ct string, format *formatConditional) *xlsxCfRule { + return &xlsxCfRule{ + Priority: p + 1, + Type: validType[format.Type], + DataBar: &xlsxDataBar{ + Cfvo: []*xlsxCfvo{{Type: format.MinType}, {Type: format.MaxType}}, + Color: []*xlsxColor{{RGB: getPaletteColor(format.BarColor)}}, + }, + } +} + +// drawConfFmtExp provides a function to create conditional formatting rule +// for expression by given priority, criteria type and format settings. +func drawConfFmtExp(p int, ct string, format *formatConditional) *xlsxCfRule { + return &xlsxCfRule{ + Priority: p + 1, + Type: validType[format.Type], + Formula: []string{format.Criteria}, + DxfID: &format.Format, + } +} + +// getPaletteColor provides a function to convert the RBG color by given +// string. +func getPaletteColor(color string) string { + return "FF" + strings.Replace(strings.ToUpper(color), "#", "", -1) +} + +// themeReader provides a function to get the pointer to the xl/theme/theme1.xml +// structure after deserialization. +func (f *File) themeReader() *xlsxTheme { + var ( + err error + theme xlsxTheme + ) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")))). + Decode(&theme); err != nil && err != io.EOF { + log.Printf("xml decoder error: %s", err) + } + return &theme +} + +// ThemeColor applied the color with tint value. +func ThemeColor(baseColor string, tint float64) string { + if tint == 0 { + return "FF" + baseColor + } + r, _ := strconv.ParseUint(baseColor[:2], 16, 64) + g, _ := strconv.ParseUint(baseColor[2:4], 16, 64) + b, _ := strconv.ParseUint(baseColor[4:6], 16, 64) + var h, s, l float64 + if r <= math.MaxUint8 && g <= math.MaxUint8 && b <= math.MaxUint8 { + h, s, l = RGBToHSL(uint8(r), uint8(g), uint8(b)) + } + if tint < 0 { + l *= 1 + tint + } else { + l = l*(1-tint) + (1 - (1 - tint)) + } + br, bg, bb := HSLToRGB(h, s, l) + return fmt.Sprintf("FF%02X%02X%02X", br, bg, bb) +} diff --git a/vendor/github.com/xuri/excelize/v2/table.go b/vendor/github.com/xuri/excelize/v2/table.go new file mode 100644 index 00000000..0311a8ed --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/table.go @@ -0,0 +1,524 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "regexp" + "strconv" + "strings" +) + +// parseFormatTableSet provides a function to parse the format settings of the +// table with default value. +func parseFormatTableSet(formatSet string) (*formatTable, error) { + format := formatTable{ + TableStyle: "", + ShowRowStripes: true, + } + err := json.Unmarshal(parseFormatSet(formatSet), &format) + return &format, err +} + +// AddTable provides the method to add table in a worksheet by given worksheet +// name, coordinate area and format set. For example, create a table of A1:D5 +// on Sheet1: +// +// err := f.AddTable("Sheet1", "A1", "D5", "") +// +// Create a table of F2:H6 on Sheet2 with format set: +// +// err := f.AddTable("Sheet2", "F2", "H6", `{ +// "table_name": "table", +// "table_style": "TableStyleMedium2", +// "show_first_column": true, +// "show_last_column": true, +// "show_row_stripes": false, +// "show_column_stripes": true +// }`) +// +// Note that the table must be at least two lines including the header. The +// header cells must contain strings and must be unique, and must set the +// header row data of the table before calling the AddTable function. Multiple +// tables coordinate areas that can't have an intersection. +// +// table_name: The name of the table, in the same worksheet name of the table should be unique +// +// table_style: The built-in table style names +// +// TableStyleLight1 - TableStyleLight21 +// TableStyleMedium1 - TableStyleMedium28 +// TableStyleDark1 - TableStyleDark11 +// +func (f *File) AddTable(sheet, hCell, vCell, format string) error { + formatSet, err := parseFormatTableSet(format) + if err != nil { + return err + } + // Coordinate conversion, convert C1:B3 to 2,0,1,2. + hCol, hRow, err := CellNameToCoordinates(hCell) + if err != nil { + return err + } + vCol, vRow, err := CellNameToCoordinates(vCell) + if err != nil { + return err + } + + if vCol < hCol { + vCol, hCol = hCol, vCol + } + + if vRow < hRow { + vRow, hRow = hRow, vRow + } + + tableID := f.countTables() + 1 + sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml" + tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1) + // Add first table for given sheet. + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "") + if err = f.addSheetTable(sheet, rID); err != nil { + return err + } + f.addSheetNameSpace(sheet, SourceRelationship) + if err = f.addTable(sheet, tableXML, hCol, hRow, vCol, vRow, tableID, formatSet); err != nil { + return err + } + f.addContentTypePart(tableID, "table") + return err +} + +// countTables provides a function to get table files count storage in the +// folder xl/tables. +func (f *File) countTables() int { + count := 0 + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/tables/table") { + count++ + } + return true + }) + return count +} + +// addSheetTable provides a function to add tablePart element to +// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. +func (f *File) addSheetTable(sheet string, rID int) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + table := &xlsxTablePart{ + RID: "rId" + strconv.Itoa(rID), + } + if ws.TableParts == nil { + ws.TableParts = &xlsxTableParts{} + } + ws.TableParts.Count++ + ws.TableParts.TableParts = append(ws.TableParts.TableParts, table) + return err +} + +// addTable provides a function to add table by given worksheet name, +// coordinate area and format set. +func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet *formatTable) error { + // Correct the minimum number of rows, the table at least two lines. + if y1 == y2 { + y2++ + } + + // Correct table reference coordinate area, such correct C1:B3 to B1:C3. + ref, err := f.coordinatesToAreaRef([]int{x1, y1, x2, y2}) + if err != nil { + return err + } + + var tableColumn []*xlsxTableColumn + + idx := 0 + for i := x1; i <= x2; i++ { + idx++ + cell, err := CoordinatesToCellName(i, y1) + if err != nil { + return err + } + name, _ := f.GetCellValue(sheet, cell) + if _, err := strconv.Atoi(name); err == nil { + _ = f.SetCellStr(sheet, cell, name) + } + if name == "" { + name = "Column" + strconv.Itoa(idx) + _ = f.SetCellStr(sheet, cell, name) + } + tableColumn = append(tableColumn, &xlsxTableColumn{ + ID: idx, + Name: name, + }) + } + name := formatSet.TableName + if name == "" { + name = "Table" + strconv.Itoa(i) + } + t := xlsxTable{ + XMLNS: NameSpaceSpreadSheet.Value, + ID: i, + Name: name, + DisplayName: name, + Ref: ref, + AutoFilter: &xlsxAutoFilter{ + Ref: ref, + }, + TableColumns: &xlsxTableColumns{ + Count: idx, + TableColumn: tableColumn, + }, + TableStyleInfo: &xlsxTableStyleInfo{ + Name: formatSet.TableStyle, + ShowFirstColumn: formatSet.ShowFirstColumn, + ShowLastColumn: formatSet.ShowLastColumn, + ShowRowStripes: formatSet.ShowRowStripes, + ShowColumnStripes: formatSet.ShowColumnStripes, + }, + } + table, _ := xml.Marshal(t) + f.saveFileList(tableXML, table) + return nil +} + +// parseAutoFilterSet provides a function to parse the settings of the auto +// filter. +func parseAutoFilterSet(formatSet string) (*formatAutoFilter, error) { + format := formatAutoFilter{} + err := json.Unmarshal([]byte(formatSet), &format) + return &format, err +} + +// AutoFilter provides the method to add auto filter in a worksheet by given +// worksheet name, coordinate area and settings. An autofilter in Excel is a +// way of filtering a 2D range of data based on some simple criteria. For +// example applying an autofilter to a cell range A1:D4 in the Sheet1: +// +// err := f.AutoFilter("Sheet1", "A1", "D4", "") +// +// Filter data in an autofilter: +// +// err := f.AutoFilter("Sheet1", "A1", "D4", `{"column":"B","expression":"x != blanks"}`) +// +// column defines the filter columns in a autofilter range based on simple +// criteria +// +// It isn't sufficient to just specify the filter condition. You must also +// hide any rows that don't match the filter condition. Rows are hidden using +// the SetRowVisible() method. Excelize can't filter rows automatically since +// this isn't part of the file format. +// +// Setting a filter criteria for a column: +// +// expression defines the conditions, the following operators are available +// for setting the filter criteria: +// +// == +// != +// > +// < +// >= +// <= +// and +// or +// +// An expression can comprise a single statement or two statements separated +// by the 'and' and 'or' operators. For example: +// +// x < 2000 +// x > 2000 +// x == 2000 +// x > 2000 and x < 5000 +// x == 2000 or x == 5000 +// +// Filtering of blank or non-blank data can be achieved by using a value of +// Blanks or NonBlanks in the expression: +// +// x == Blanks +// x == NonBlanks +// +// Excel also allows some simple string matching operations: +// +// x == b* // begins with b +// x != b* // doesn't begin with b +// x == *b // ends with b +// x != *b // doesn't end with b +// x == *b* // contains b +// x != *b* // doesn't contains b +// +// You can also use '*' to match any character or number and '?' to match any +// single character or number. No other regular expression quantifier is +// supported by Excel's filters. Excel's regular expression characters can be +// escaped using '~'. +// +// The placeholder variable x in the above examples can be replaced by any +// simple string. The actual placeholder name is ignored internally so the +// following are all equivalent: +// +// x < 2000 +// col < 2000 +// Price < 2000 +// +func (f *File) AutoFilter(sheet, hCell, vCell, format string) error { + hCol, hRow, err := CellNameToCoordinates(hCell) + if err != nil { + return err + } + vCol, vRow, err := CellNameToCoordinates(vCell) + if err != nil { + return err + } + + if vCol < hCol { + vCol, hCol = hCol, vCol + } + + if vRow < hRow { + vRow, hRow = hRow, vRow + } + + formatSet, _ := parseAutoFilterSet(format) + cellStart, _ := CoordinatesToCellName(hCol, hRow, true) + cellEnd, _ := CoordinatesToCellName(vCol, vRow, true) + ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase" + wb := f.workbookReader() + sheetID := f.GetSheetIndex(sheet) + filterRange := fmt.Sprintf("'%s'!%s", sheet, ref) + d := xlsxDefinedName{ + Name: filterDB, + Hidden: true, + LocalSheetID: intPtr(sheetID), + Data: filterRange, + } + if wb.DefinedNames == nil { + wb.DefinedNames = &xlsxDefinedNames{ + DefinedName: []xlsxDefinedName{d}, + } + } else { + var definedNameExists bool + for idx := range wb.DefinedNames.DefinedName { + definedName := wb.DefinedNames.DefinedName[idx] + if definedName.Name == filterDB && *definedName.LocalSheetID == sheetID && definedName.Hidden { + wb.DefinedNames.DefinedName[idx].Data = filterRange + definedNameExists = true + } + } + if !definedNameExists { + wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d) + } + } + refRange := vCol - hCol + return f.autoFilter(sheet, ref, refRange, hCol, formatSet) +} + +// autoFilter provides a function to extract the tokens from the filter +// expression. The tokens are mainly non-whitespace groups. +func (f *File) autoFilter(sheet, ref string, refRange, col int, formatSet *formatAutoFilter) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + if ws.SheetPr != nil { + ws.SheetPr.FilterMode = true + } + ws.SheetPr = &xlsxSheetPr{FilterMode: true} + filter := &xlsxAutoFilter{ + Ref: ref, + } + ws.AutoFilter = filter + if formatSet.Column == "" || formatSet.Expression == "" { + return nil + } + + fsCol, err := ColumnNameToNumber(formatSet.Column) + if err != nil { + return err + } + offset := fsCol - col + if offset < 0 || offset > refRange { + return fmt.Errorf("incorrect index of column '%s'", formatSet.Column) + } + + filter.FilterColumn = append(filter.FilterColumn, &xlsxFilterColumn{ + ColID: offset, + }) + re := regexp.MustCompile(`"(?:[^"]|"")*"|\S+`) + token := re.FindAllString(formatSet.Expression, -1) + if len(token) != 3 && len(token) != 7 { + return fmt.Errorf("incorrect number of tokens in criteria '%s'", formatSet.Expression) + } + expressions, tokens, err := f.parseFilterExpression(formatSet.Expression, token) + if err != nil { + return err + } + f.writeAutoFilter(filter, expressions, tokens) + ws.AutoFilter = filter + return nil +} + +// writeAutoFilter provides a function to check for single or double custom +// filters as default filters and handle them accordingly. +func (f *File) writeAutoFilter(filter *xlsxAutoFilter, exp []int, tokens []string) { + if len(exp) == 1 && exp[0] == 2 { + // Single equality. + var filters []*xlsxFilter + filters = append(filters, &xlsxFilter{Val: tokens[0]}) + filter.FilterColumn[0].Filters = &xlsxFilters{Filter: filters} + } else if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 { + // Double equality with "or" operator. + var filters []*xlsxFilter + for _, v := range tokens { + filters = append(filters, &xlsxFilter{Val: v}) + } + filter.FilterColumn[0].Filters = &xlsxFilters{Filter: filters} + } else { + // Non default custom filter. + expRel := map[int]int{0: 0, 1: 2} + andRel := map[int]bool{0: true, 1: false} + for k, v := range tokens { + f.writeCustomFilter(filter, exp[expRel[k]], v) + if k == 1 { + filter.FilterColumn[0].CustomFilters.And = andRel[exp[k]] + } + } + } +} + +// writeCustomFilter provides a function to write the element. +func (f *File) writeCustomFilter(filter *xlsxAutoFilter, operator int, val string) { + operators := map[int]string{ + 1: "lessThan", + 2: "equal", + 3: "lessThanOrEqual", + 4: "greaterThan", + 5: "notEqual", + 6: "greaterThanOrEqual", + 22: "equal", + } + customFilter := xlsxCustomFilter{ + Operator: operators[operator], + Val: val, + } + if filter.FilterColumn[0].CustomFilters != nil { + filter.FilterColumn[0].CustomFilters.CustomFilter = append(filter.FilterColumn[0].CustomFilters.CustomFilter, &customFilter) + } else { + var customFilters []*xlsxCustomFilter + customFilters = append(customFilters, &customFilter) + filter.FilterColumn[0].CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters} + } +} + +// parseFilterExpression provides a function to converts the tokens of a +// possibly conditional expression into 1 or 2 sub expressions for further +// parsing. +// +// Examples: +// +// ('x', '==', 2000) -> exp1 +// ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2 +// +func (f *File) parseFilterExpression(expression string, tokens []string) ([]int, []string, error) { + var expressions []int + var t []string + if len(tokens) == 7 { + // The number of tokens will be either 3 (for 1 expression) or 7 (for 2 + // expressions). + conditional := 0 + c := tokens[3] + re, _ := regexp.Match(`(or|\|\|)`, []byte(c)) + if re { + conditional = 1 + } + expression1, token1, err := f.parseFilterTokens(expression, tokens[:3]) + if err != nil { + return expressions, t, err + } + expression2, token2, err := f.parseFilterTokens(expression, tokens[4:7]) + if err != nil { + return expressions, t, err + } + expressions = []int{expression1[0], conditional, expression2[0]} + t = []string{token1, token2} + } else { + exp, token, err := f.parseFilterTokens(expression, tokens) + if err != nil { + return expressions, t, err + } + expressions = exp + t = []string{token} + } + return expressions, t, nil +} + +// parseFilterTokens provides a function to parse the 3 tokens of a filter +// expression and return the operator and token. +func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, string, error) { + operators := map[string]int{ + "==": 2, + "=": 2, + "=~": 2, + "eq": 2, + "!=": 5, + "!~": 5, + "ne": 5, + "<>": 5, + "<": 1, + "<=": 3, + ">": 4, + ">=": 6, + } + operator, ok := operators[strings.ToLower(tokens[1])] + if !ok { + // Convert the operator from a number to a descriptive string. + return []int{}, "", fmt.Errorf("unknown operator: %s", tokens[1]) + } + token := tokens[2] + // Special handling for Blanks/NonBlanks. + re, _ := regexp.Match("blanks|nonblanks", []byte(strings.ToLower(token))) + if re { + // Only allow Equals or NotEqual in this context. + if operator != 2 && operator != 5 { + return []int{operator}, token, fmt.Errorf("the operator '%s' in expression '%s' is not valid in relation to Blanks/NonBlanks'", tokens[1], expression) + } + token = strings.ToLower(token) + // The operator should always be 2 (=) to flag a "simple" equality in + // the binary record. Therefore we convert <> to =. + if token == "blanks" { + if operator == 5 { + token = " " + } + } else { + if operator == 5 { + operator = 2 + token = "blanks" + } else { + operator = 5 + token = " " + } + } + } + // if the string token contains an Excel match character then change the + // operator type to indicate a non "simple" equality. + re, _ = regexp.Match("[*?]", []byte(token)) + if operator == 2 && re { + operator = 22 + } + return []int{operator}, token, nil +} diff --git a/vendor/github.com/xuri/excelize/v2/templates.go b/vendor/github.com/xuri/excelize/v2/templates.go new file mode 100644 index 00000000..1e46b561 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/templates.go @@ -0,0 +1,46 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. +// +// This file contains default templates for XML files we don't yet populated +// based on content. + +package excelize + +const ( + defaultXMLPathContentTypes = "[Content_Types].xml" + defaultXMLPathDocPropsApp = "docProps/app.xml" + defaultXMLPathDocPropsCore = "docProps/core.xml" + defaultXMLPathCalcChain = "xl/calcChain.xml" + defaultXMLPathSharedStrings = "xl/sharedStrings.xml" + defaultXMLPathStyles = "xl/styles.xml" + defaultXMLPathWorkbook = "xl/workbook.xml" + defaultTempFileSST = "sharedStrings" +) + +const templateDocpropsApp = `0Go Excelize` + +const templateContentTypes = `` + +const templateWorkbook = `` + +const templateStyles = `` + +const templateSheet = `` + +const templateWorkbookRels = `` + +const templateDocpropsCore = `xuri2006-09-16T00:00:00Z2006-09-16T00:00:00Z` + +const templateRels = `` + +const templateTheme = `` + +const templateNamespaceIDMap = ` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:ap="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:op="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:cdr="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing" xmlns:comp="http://schemas.openxmlformats.org/drawingml/2006/compatibility" xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram" xmlns:lc="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:xne="http://schemas.microsoft.com/office/excel/2006/main" xmlns:mso="http://schemas.microsoft.com/office/2006/01/customui" xmlns:ax="http://schemas.microsoft.com/office/2006/activeX" xmlns:cppr="http://schemas.microsoft.com/office/2006/coverPageProps" xmlns:cdip="http://schemas.microsoft.com/office/2006/customDocumentInformationPanel" xmlns:ct="http://schemas.microsoft.com/office/2006/metadata/contentType" xmlns:ntns="http://schemas.microsoft.com/office/2006/metadata/customXsn" xmlns:lp="http://schemas.microsoft.com/office/2006/metadata/longProperties" xmlns:ma="http://schemas.microsoft.com/office/2006/metadata/properties/metaAttributes" xmlns:msink="http://schemas.microsoft.com/ink/2010/main" xmlns:c14="http://schemas.microsoft.com/office/drawing/2007/8/2/chart" xmlns:cdr14="http://schemas.microsoft.com/office/drawing/2010/chartDrawing" xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" xmlns:pic14="http://schemas.microsoft.com/office/drawing/2010/picture" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:xdr14="http://schemas.microsoft.com/office/excel/2010/spreadsheetDrawing" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:dsp="http://schemas.microsoft.com/office/drawing/2008/diagram" xmlns:mso14="http://schemas.microsoft.com/office/2009/07/customui" xmlns:dgm14="http://schemas.microsoft.com/office/drawing/2010/diagram" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:x12ac="http://schemas.microsoft.com/office/spreadsheetml/2011/1/ac" xmlns:x15ac="http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:xr4="http://schemas.microsoft.com/office/spreadsheetml/2016/revision4" xmlns:xr5="http://schemas.microsoft.com/office/spreadsheetml/2016/revision5" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr7="http://schemas.microsoft.com/office/spreadsheetml/2016/revision7" xmlns:xr8="http://schemas.microsoft.com/office/spreadsheetml/2016/revision8" xmlns:xr9="http://schemas.microsoft.com/office/spreadsheetml/2016/revision9" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr11="http://schemas.microsoft.com/office/spreadsheetml/2016/revision11" xmlns:xr12="http://schemas.microsoft.com/office/spreadsheetml/2016/revision12" xmlns:xr13="http://schemas.microsoft.com/office/spreadsheetml/2016/revision13" xmlns:xr14="http://schemas.microsoft.com/office/spreadsheetml/2016/revision14" xmlns:xr15="http://schemas.microsoft.com/office/spreadsheetml/2016/revision15" xmlns:x16="http://schemas.microsoft.com/office/spreadsheetml/2014/11/main" xmlns:x16r2="http://schemas.microsoft.com/office/spreadsheetml/2015/02/main" mc:Ignorable="c14 cdr14 a14 pic14 x14 xdr14 x14ac dsp mso14 dgm14 x15 x12ac x15ac xr xr2 xr3 xr4 xr5 xr6 xr7 xr8 xr9 xr10 xr11 xr12 xr13 xr14 xr15 x15 x16 x16r2 mo mx mv o v" xmlns:mo="http://schemas.microsoft.com/office/mac/office/2008/main" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xr:uid="{00000000-0001-0000-0000-000000000000}">` diff --git a/vendor/github.com/xuri/excelize/v2/vmlDrawing.go b/vendor/github.com/xuri/excelize/v2/vmlDrawing.go new file mode 100644 index 00000000..f9de4991 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/vmlDrawing.go @@ -0,0 +1,146 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// vmlDrawing directly maps the root element in the file +// xl/drawings/vmlDrawing%d.vml. +type vmlDrawing struct { + XMLName xml.Name `xml:"xml"` + XMLNSv string `xml:"xmlns:v,attr"` + XMLNSo string `xml:"xmlns:o,attr"` + XMLNSx string `xml:"xmlns:x,attr"` + XMLNSmv string `xml:"xmlns:mv,attr"` + Shapelayout *xlsxShapelayout `xml:"o:shapelayout"` + Shapetype *xlsxShapetype `xml:"v:shapetype"` + Shape []xlsxShape `xml:"v:shape"` +} + +// xlsxShapelayout directly maps the shapelayout element. This element contains +// child elements that store information used in the editing and layout of +// shapes. +type xlsxShapelayout struct { + Ext string `xml:"v:ext,attr"` + IDmap *xlsxIDmap `xml:"o:idmap"` +} + +// xlsxIDmap directly maps the idmap element. +type xlsxIDmap struct { + Ext string `xml:"v:ext,attr"` + Data int `xml:"data,attr"` +} + +// xlsxShape directly maps the shape element. +type xlsxShape struct { + XMLName xml.Name `xml:"v:shape"` + ID string `xml:"id,attr"` + Type string `xml:"type,attr"` + Style string `xml:"style,attr"` + Fillcolor string `xml:"fillcolor,attr"` + Insetmode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"` + Strokecolor string `xml:"strokecolor,attr,omitempty"` + Val string `xml:",innerxml"` +} + +// xlsxShapetype directly maps the shapetype element. +type xlsxShapetype struct { + ID string `xml:"id,attr"` + Coordsize string `xml:"coordsize,attr"` + Spt int `xml:"o:spt,attr"` + Path string `xml:"path,attr"` + Stroke *xlsxStroke `xml:"v:stroke"` + VPath *vPath `xml:"v:path"` +} + +// xlsxStroke directly maps the stroke element. +type xlsxStroke struct { + Joinstyle string `xml:"joinstyle,attr"` +} + +// vPath directly maps the v:path element. +type vPath struct { + Gradientshapeok string `xml:"gradientshapeok,attr,omitempty"` + Connecttype string `xml:"o:connecttype,attr"` +} + +// vFill directly maps the v:fill element. This element must be defined within a +// Shape element. +type vFill struct { + Angle int `xml:"angle,attr,omitempty"` + Color2 string `xml:"color2,attr"` + Type string `xml:"type,attr,omitempty"` + Fill *oFill `xml:"o:fill"` +} + +// oFill directly maps the o:fill element. +type oFill struct { + Ext string `xml:"v:ext,attr"` + Type string `xml:"type,attr,omitempty"` +} + +// vShadow directly maps the v:shadow element. This element must be defined +// within a Shape element. In addition, the On attribute must be set to True. +type vShadow struct { + On string `xml:"on,attr"` + Color string `xml:"color,attr,omitempty"` + Obscured string `xml:"obscured,attr"` +} + +// vTextbox directly maps the v:textbox element. This element must be defined +// within a Shape element. +type vTextbox struct { + Style string `xml:"style,attr"` + Div *xlsxDiv `xml:"div"` +} + +// xlsxDiv directly maps the div element. +type xlsxDiv struct { + Style string `xml:"style,attr"` +} + +// xClientData (Attached Object Data) directly maps the x:ClientData element. +// This element specifies data associated with objects attached to a +// spreadsheet. While this element might contain any of the child elements +// below, only certain combinations are meaningful. The ObjectType attribute +// determines the kind of object the element represents and which subset of +// child elements is appropriate. Relevant groups are identified for each child +// element. +type xClientData struct { + ObjectType string `xml:"ObjectType,attr"` + MoveWithCells string `xml:"x:MoveWithCells,omitempty"` + SizeWithCells string `xml:"x:SizeWithCells,omitempty"` + Anchor string `xml:"x:Anchor"` + AutoFill string `xml:"x:AutoFill"` + Row int `xml:"x:Row"` + Column int `xml:"x:Column"` +} + +// decodeVmlDrawing defines the structure used to parse the file +// xl/drawings/vmlDrawing%d.vml. +type decodeVmlDrawing struct { + Shape []decodeShape `xml:"urn:schemas-microsoft-com:vml shape"` +} + +// decodeShape defines the structure used to parse the particular shape element. +type decodeShape struct { + Val string `xml:",innerxml"` +} + +// encodeShape defines the structure used to re-serialization shape element. +type encodeShape struct { + Fill *vFill `xml:"v:fill"` + Shadow *vShadow `xml:"v:shadow"` + Path *vPath `xml:"v:path"` + Textbox *vTextbox `xml:"v:textbox"` + ClientData *xClientData `xml:"x:ClientData"` +} diff --git a/vendor/github.com/xuri/excelize/v2/workbook.go b/vendor/github.com/xuri/excelize/v2/workbook.go new file mode 100644 index 00000000..c65397b6 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/workbook.go @@ -0,0 +1,176 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "bytes" + "encoding/xml" + "io" + "log" + "path/filepath" + "strconv" + "strings" +) + +// WorkbookPrOption is an option of a view of a workbook. See SetWorkbookPrOptions(). +type WorkbookPrOption interface { + setWorkbookPrOption(pr *xlsxWorkbookPr) +} + +// WorkbookPrOptionPtr is a writable WorkbookPrOption. See GetWorkbookPrOptions(). +type WorkbookPrOptionPtr interface { + WorkbookPrOption + getWorkbookPrOption(pr *xlsxWorkbookPr) +} + +type ( + // FilterPrivacy is an option used for WorkbookPrOption + FilterPrivacy bool +) + +// setWorkbook update workbook property of the spreadsheet. Maximum 31 +// characters are allowed in sheet title. +func (f *File) setWorkbook(name string, sheetID, rid int) { + content := f.workbookReader() + content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{ + Name: trimSheetName(name), + SheetID: sheetID, + ID: "rId" + strconv.Itoa(rid), + }) +} + +// getWorkbookPath provides a function to get the path of the workbook.xml in +// the spreadsheet. +func (f *File) getWorkbookPath() (path string) { + if rels := f.relsReader("_rels/.rels"); rels != nil { + rels.Lock() + defer rels.Unlock() + for _, rel := range rels.Relationships { + if rel.Type == SourceRelationshipOfficeDocument { + path = strings.TrimPrefix(rel.Target, "/") + return + } + } + } + return +} + +// getWorkbookRelsPath provides a function to get the path of the workbook.xml.rels +// in the spreadsheet. +func (f *File) getWorkbookRelsPath() (path string) { + wbPath := f.getWorkbookPath() + wbDir := filepath.Dir(wbPath) + if wbDir == "." { + path = "_rels/" + filepath.Base(wbPath) + ".rels" + return + } + path = strings.TrimPrefix(filepath.Dir(wbPath)+"/_rels/"+filepath.Base(wbPath)+".rels", "/") + return +} + +// workbookReader provides a function to get the pointer to the workbook.xml +// structure after deserialization. +func (f *File) workbookReader() *xlsxWorkbook { + var err error + if f.WorkBook == nil { + wbPath := f.getWorkbookPath() + f.WorkBook = new(xlsxWorkbook) + if _, ok := f.xmlAttr[wbPath]; !ok { + d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))) + f.xmlAttr[wbPath] = append(f.xmlAttr[wbPath], getRootElement(d)...) + f.addNameSpaces(wbPath, SourceRelationship) + } + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))). + Decode(f.WorkBook); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } + } + return f.WorkBook +} + +// workBookWriter provides a function to save workbook.xml after serialize +// structure. +func (f *File) workBookWriter() { + if f.WorkBook != nil { + if f.WorkBook.DecodeAlternateContent != nil { + f.WorkBook.AlternateContent = &xlsxAlternateContent{ + Content: f.WorkBook.DecodeAlternateContent.Content, + XMLNSMC: SourceRelationshipCompatibility.Value, + } + } + f.WorkBook.DecodeAlternateContent = nil + output, _ := xml.Marshal(f.WorkBook) + f.saveFileList(f.getWorkbookPath(), replaceRelationshipsBytes(f.replaceNameSpaceBytes(f.getWorkbookPath(), output))) + } +} + +// SetWorkbookPrOptions provides a function to sets workbook properties. +// +// Available options: +// FilterPrivacy(bool) +// CodeName(string) +func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error { + wb := f.workbookReader() + pr := wb.WorkbookPr + if pr == nil { + pr = new(xlsxWorkbookPr) + wb.WorkbookPr = pr + } + for _, opt := range opts { + opt.setWorkbookPrOption(pr) + } + return nil +} + +// setWorkbookPrOption implements the WorkbookPrOption interface. +func (o FilterPrivacy) setWorkbookPrOption(pr *xlsxWorkbookPr) { + pr.FilterPrivacy = bool(o) +} + +// setWorkbookPrOption implements the WorkbookPrOption interface. +func (o CodeName) setWorkbookPrOption(pr *xlsxWorkbookPr) { + pr.CodeName = string(o) +} + +// GetWorkbookPrOptions provides a function to gets workbook properties. +// +// Available options: +// FilterPrivacy(bool) +// CodeName(string) +func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error { + wb := f.workbookReader() + pr := wb.WorkbookPr + for _, opt := range opts { + opt.getWorkbookPrOption(pr) + } + return nil +} + +// getWorkbookPrOption implements the WorkbookPrOption interface and get the +// filter privacy of thw workbook. +func (o *FilterPrivacy) getWorkbookPrOption(pr *xlsxWorkbookPr) { + if pr == nil { + *o = false + return + } + *o = FilterPrivacy(pr.FilterPrivacy) +} + +// getWorkbookPrOption implements the WorkbookPrOption interface and get the +// code name of thw workbook. +func (o *CodeName) getWorkbookPrOption(pr *xlsxWorkbookPr) { + if pr == nil { + *o = "" + return + } + *o = CodeName(pr.CodeName) +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlApp.go b/vendor/github.com/xuri/excelize/v2/xmlApp.go new file mode 100644 index 00000000..abfd82b3 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlApp.go @@ -0,0 +1,75 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// AppProperties directly maps the document application properties. +type AppProperties struct { + Application string + ScaleCrop bool + DocSecurity int + Company string + LinksUpToDate bool + HyperlinksChanged bool + AppVersion string +} + +// xlsxProperties specifies to an OOXML document properties such as the +// template used, the number of pages and words, and the application name and +// version. +type xlsxProperties struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties Properties"` + Vt string `xml:"xmlns:vt,attr"` + Template string `xml:",omitempty"` + Manager string `xml:",omitempty"` + Company string `xml:",omitempty"` + Pages int `xml:",omitempty"` + Words int `xml:",omitempty"` + Characters int `xml:",omitempty"` + PresentationFormat string `xml:",omitempty"` + Lines int `xml:",omitempty"` + Paragraphs int `xml:",omitempty"` + Slides int `xml:",omitempty"` + Notes int `xml:",omitempty"` + TotalTime int `xml:",omitempty"` + HiddenSlides int `xml:",omitempty"` + MMClips int `xml:",omitempty"` + ScaleCrop bool `xml:",omitempty"` + HeadingPairs *xlsxVectorVariant + TitlesOfParts *xlsxVectorLpstr + LinksUpToDate bool `xml:",omitempty"` + CharactersWithSpaces int `xml:",omitempty"` + SharedDoc bool `xml:",omitempty"` + HyperlinkBase string `xml:",omitempty"` + HLinks *xlsxVectorVariant + HyperlinksChanged bool `xml:",omitempty"` + DigSig *xlsxDigSig + Application string `xml:",omitempty"` + AppVersion string `xml:",omitempty"` + DocSecurity int `xml:",omitempty"` +} + +// xlsxVectorVariant specifies the set of hyperlinks that were in this +// document when last saved. +type xlsxVectorVariant struct { + Content string `xml:",innerxml"` +} + +type xlsxVectorLpstr struct { + Content string `xml:",innerxml"` +} + +// xlsxDigSig contains the signature of a digitally signed document. +type xlsxDigSig struct { + Content string `xml:",innerxml"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlCalcChain.go b/vendor/github.com/xuri/excelize/v2/xmlCalcChain.go new file mode 100644 index 00000000..f5780339 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlCalcChain.go @@ -0,0 +1,85 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxCalcChain directly maps the calcChain element. This element represents the root of the calculation chain. +type xlsxCalcChain struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main calcChain"` + C []xlsxCalcChainC `xml:"c"` +} + +// xlsxCalcChainC directly maps the c element. +// +// Attributes | Attributes +// --------------------------+---------------------------------------------------------- +// a (Array) | A Boolean flag indicating whether the cell's formula +// | is an array formula. True if this cell's formula is +// | an array formula, false otherwise. If there is a +// | conflict between this attribute and the t attribute +// | of the f element (§18.3.1.40), the t attribute takes +// | precedence. The possible values for this attribute +// | are defined by the W3C XML Schema boolean datatype. +// | +// i (Sheet Id) | A sheet Id of a sheet the cell belongs to. If this is +// | omitted, it is assumed to be the same as the i value +// | of the previous cell.The possible values for this +// | attribute are defined by the W3C XML Schema int datatype. +// | +// l (New Dependency Level) | A Boolean flag indicating that the cell's formula +// | starts a new dependency level. True if the formula +// | starts a new dependency level, false otherwise. +// | Starting a new dependency level means that all +// | concurrent calculations, and child calculations, shall +// | be completed - and the cells have new values - before +// | the calc chain can continue. In other words, this +// | dependency level might depend on levels that came before +// | it, and any later dependency levels might depend on +// | this level; but not later dependency levels can have +// | any calculations started until this dependency level +// | completes.The possible values for this attribute are +// | defined by the W3C XML Schema boolean datatype. +// | +// r (Cell Reference) | An A-1 style reference to a cell.The possible values +// | for this attribute are defined by the ST_CellRef +// | simple type (§18.18.7). +// | +// s (Child Chain) | A Boolean flag indicating whether the cell's formula +// | is on a child chain. True if this cell is part of a +// | child chain, false otherwise. If this is omitted, it +// | is assumed to be the same as the s value of the +// | previous cell .A child chain is a list of calculations +// | that occur which depend on the parent to the chain. +// | There shall not be cross dependencies between child +// | chains. Child chains are not the same as dependency +// | levels - a child chain and its parent are all on the +// | same dependency level. Child chains are series of +// | calculations that can be independently farmed out to +// | other threads or processors.The possible values for +// | this attribute is defined by the W3C XML Schema +// | boolean datatype. +// | +// t (New Thread) | A Boolean flag indicating whether the cell's formula +// | starts a new thread. True if the cell's formula starts +// | a new thread, false otherwise.The possible values for +// | this attribute is defined by the W3C XML Schema +// | boolean datatype. +// +type xlsxCalcChainC struct { + R string `xml:"r,attr"` + I int `xml:"i,attr"` + L bool `xml:"l,attr,omitempty"` + S bool `xml:"s,attr,omitempty"` + T bool `xml:"t,attr,omitempty"` + A bool `xml:"a,attr,omitempty"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlChart.go b/vendor/github.com/xuri/excelize/v2/xmlChart.go new file mode 100644 index 00000000..b6ee3cd8 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlChart.go @@ -0,0 +1,656 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxChartSpace directly maps the chartSpace element. The chart namespace in +// DrawingML is for representing visualizations of numeric data with column +// charts, pie charts, scatter charts, or other types of charts. +type xlsxChartSpace struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/chart chartSpace"` + XMLNSa string `xml:"xmlns:a,attr"` + Date1904 *attrValBool `xml:"date1904"` + Lang *attrValString `xml:"lang"` + RoundedCorners *attrValBool `xml:"roundedCorners"` + Chart cChart `xml:"chart"` + SpPr *cSpPr `xml:"spPr"` + TxPr *cTxPr `xml:"txPr"` + PrintSettings *cPrintSettings `xml:"printSettings"` +} + +// cThicknessSpPr directly maps the element that specifies the thickness of +// the walls or floor as a percentage of the largest dimension of the plot +// volume and SpPr element. +type cThicknessSpPr struct { + Thickness *attrValInt `xml:"thickness"` + SpPr *cSpPr `xml:"spPr"` +} + +// cChart (Chart) directly maps the chart element. This element specifies a +// title. +type cChart struct { + Title *cTitle `xml:"title"` + AutoTitleDeleted *cAutoTitleDeleted `xml:"autoTitleDeleted"` + View3D *cView3D `xml:"view3D"` + Floor *cThicknessSpPr `xml:"floor"` + SideWall *cThicknessSpPr `xml:"sideWall"` + BackWall *cThicknessSpPr `xml:"backWall"` + PlotArea *cPlotArea `xml:"plotArea"` + Legend *cLegend `xml:"legend"` + PlotVisOnly *attrValBool `xml:"plotVisOnly"` + DispBlanksAs *attrValString `xml:"dispBlanksAs"` + ShowDLblsOverMax *attrValBool `xml:"showDLblsOverMax"` +} + +// cTitle (Title) directly maps the title element. This element specifies a +// title. +type cTitle struct { + Tx cTx `xml:"tx,omitempty"` + Layout string `xml:"layout,omitempty"` + Overlay *attrValBool `xml:"overlay"` + SpPr cSpPr `xml:"spPr,omitempty"` + TxPr cTxPr `xml:"txPr,omitempty"` +} + +// cTx (Chart Text) directly maps the tx element. This element specifies text +// to use on a chart, including rich text formatting. +type cTx struct { + StrRef *cStrRef `xml:"strRef"` + Rich *cRich `xml:"rich,omitempty"` +} + +// cRich (Rich Text) directly maps the rich element. This element contains a +// string with rich text formatting. +type cRich struct { + BodyPr aBodyPr `xml:"a:bodyPr,omitempty"` + LstStyle string `xml:"a:lstStyle,omitempty"` + P aP `xml:"a:p"` +} + +// aBodyPr (Body Properties) directly maps the a:bodyPr element. This element +// defines the body properties for the text body within a shape. +type aBodyPr struct { + Anchor string `xml:"anchor,attr,omitempty"` + AnchorCtr bool `xml:"anchorCtr,attr"` + Rot int `xml:"rot,attr"` + BIns float64 `xml:"bIns,attr,omitempty"` + CompatLnSpc bool `xml:"compatLnSpc,attr,omitempty"` + ForceAA bool `xml:"forceAA,attr,omitempty"` + FromWordArt bool `xml:"fromWordArt,attr,omitempty"` + HorzOverflow string `xml:"horzOverflow,attr,omitempty"` + LIns float64 `xml:"lIns,attr,omitempty"` + NumCol int `xml:"numCol,attr,omitempty"` + RIns float64 `xml:"rIns,attr,omitempty"` + RtlCol bool `xml:"rtlCol,attr,omitempty"` + SpcCol int `xml:"spcCol,attr,omitempty"` + SpcFirstLastPara bool `xml:"spcFirstLastPara,attr"` + TIns float64 `xml:"tIns,attr,omitempty"` + Upright bool `xml:"upright,attr,omitempty"` + Vert string `xml:"vert,attr,omitempty"` + VertOverflow string `xml:"vertOverflow,attr,omitempty"` + Wrap string `xml:"wrap,attr,omitempty"` +} + +// aP (Paragraph) directly maps the a:p element. This element specifies a +// paragraph of content in the document. +type aP struct { + PPr *aPPr `xml:"a:pPr"` + R *aR `xml:"a:r"` + EndParaRPr *aEndParaRPr `xml:"a:endParaRPr"` +} + +// aPPr (Paragraph Properties) directly maps the a:pPr element. This element +// specifies a set of paragraph properties which shall be applied to the +// contents of the parent paragraph after all style/numbering/table properties +// have been applied to the text. These properties are defined as direct +// formatting, since they are directly applied to the paragraph and supersede +// any formatting from styles. +type aPPr struct { + DefRPr aRPr `xml:"a:defRPr"` +} + +// aSolidFill (Solid Fill) directly maps the solidFill element. This element +// specifies a solid color fill. The shape is filled entirely with the specified +// color. +type aSolidFill struct { + SchemeClr *aSchemeClr `xml:"a:schemeClr"` + SrgbClr *attrValString `xml:"a:srgbClr"` +} + +// aSchemeClr (Scheme Color) directly maps the a:schemeClr element. This +// element specifies a color bound to a user's theme. As with all elements which +// define a color, it is possible to apply a list of color transforms to the +// base color defined. +type aSchemeClr struct { + Val string `xml:"val,attr,omitempty"` + LumMod *attrValInt `xml:"a:lumMod"` + LumOff *attrValInt `xml:"a:lumOff"` +} + +// attrValInt directly maps the val element with integer data type as an +// attribute. +type attrValInt struct { + Val *int `xml:"val,attr"` +} + +// attrValFloat directly maps the val element with float64 data type as an +// attribute. +type attrValFloat struct { + Val *float64 `xml:"val,attr"` +} + +// attrValBool directly maps the val element with boolean data type as an +// attribute. +type attrValBool struct { + Val *bool `xml:"val,attr"` +} + +// attrValString directly maps the val element with string data type as an +// attribute. +type attrValString struct { + Val *string `xml:"val,attr"` +} + +// aCs directly maps the a:cs element. +type aCs struct { + Typeface string `xml:"typeface,attr"` +} + +// aEa directly maps the a:ea element. +type aEa struct { + Typeface string `xml:"typeface,attr"` +} + +// aLatin (Latin Font) directly maps the a:latin element. This element +// specifies that a Latin font be used for a specific run of text. This font is +// specified with a typeface attribute much like the others but is specifically +// classified as a Latin font. +type aLatin struct { + Typeface string `xml:"typeface,attr"` +} + +// aR directly maps the a:r element. +type aR struct { + RPr aRPr `xml:"a:rPr,omitempty"` + T string `xml:"a:t,omitempty"` +} + +// aRPr (Run Properties) directly maps the rPr element. This element +// specifies a set of run properties which shall be applied to the contents of +// the parent run after all style formatting has been applied to the text. These +// properties are defined as direct formatting, since they are directly applied +// to the run and supersede any formatting from styles. +type aRPr struct { + AltLang string `xml:"altLang,attr,omitempty"` + B bool `xml:"b,attr"` + Baseline int `xml:"baseline,attr"` + Bmk string `xml:"bmk,attr,omitempty"` + Cap string `xml:"cap,attr,omitempty"` + Dirty bool `xml:"dirty,attr,omitempty"` + Err bool `xml:"err,attr,omitempty"` + I bool `xml:"i,attr"` + Kern int `xml:"kern,attr"` + Kumimoji bool `xml:"kumimoji,attr,omitempty"` + Lang string `xml:"lang,attr,omitempty"` + NoProof bool `xml:"noProof,attr,omitempty"` + NormalizeH bool `xml:"normalizeH,attr,omitempty"` + SmtClean bool `xml:"smtClean,attr,omitempty"` + SmtID uint64 `xml:"smtId,attr,omitempty"` + Spc int `xml:"spc,attr"` + Strike string `xml:"strike,attr,omitempty"` + Sz float64 `xml:"sz,attr,omitempty"` + U string `xml:"u,attr,omitempty"` + SolidFill *aSolidFill `xml:"a:solidFill"` + Latin *aLatin `xml:"a:latin"` + Ea *aEa `xml:"a:ea"` + Cs *aCs `xml:"a:cs"` +} + +// cSpPr (Shape Properties) directly maps the spPr element. This element +// specifies the visual shape properties that can be applied to a shape. These +// properties include the shape fill, outline, geometry, effects, and 3D +// orientation. +type cSpPr struct { + NoFill *string `xml:"a:noFill"` + SolidFill *aSolidFill `xml:"a:solidFill"` + Ln *aLn `xml:"a:ln"` + Sp3D *aSp3D `xml:"a:sp3d"` + EffectLst *string `xml:"a:effectLst"` +} + +// aSp3D (3-D Shape Properties) directly maps the a:sp3d element. This element +// defines the 3D properties associated with a particular shape in DrawingML. +// The 3D properties which can be applied to a shape are top and bottom bevels, +// a contour and an extrusion. +type aSp3D struct { + ContourW int `xml:"contourW,attr"` + ContourClr *aContourClr `xml:"a:contourClr"` +} + +// aContourClr (Contour Color) directly maps the a:contourClr element. This +// element defines the color for the contour on a shape. The contour of a shape +// is a solid filled line which surrounds the outer edges of the shape. +type aContourClr struct { + SchemeClr *aSchemeClr `xml:"a:schemeClr"` +} + +// aLn (Outline) directly maps the a:ln element. This element specifies an +// outline style that can be applied to a number of different objects such as +// shapes and text. The line allows for the specifying of many different types +// of outlines including even line dashes and bevels. +type aLn struct { + Algn string `xml:"algn,attr,omitempty"` + Cap string `xml:"cap,attr,omitempty"` + Cmpd string `xml:"cmpd,attr,omitempty"` + W int `xml:"w,attr,omitempty"` + NoFill string `xml:"a:noFill,omitempty"` + Round string `xml:"a:round,omitempty"` + SolidFill *aSolidFill `xml:"a:solidFill"` +} + +// cTxPr (Text Properties) directly maps the txPr element. This element +// specifies text formatting. The lstStyle element is not supported. +type cTxPr struct { + BodyPr aBodyPr `xml:"a:bodyPr,omitempty"` + LstStyle string `xml:"a:lstStyle,omitempty"` + P aP `xml:"a:p,omitempty"` +} + +// aEndParaRPr (End Paragraph Run Properties) directly maps the a:endParaRPr +// element. This element specifies the text run properties that are to be used +// if another run is inserted after the last run specified. This effectively +// saves the run property state so that it can be applied when the user enters +// additional text. If this element is omitted, then the application can +// determine which default properties to apply. It is recommended that this +// element be specified at the end of the list of text runs within the paragraph +// so that an orderly list is maintained. +type aEndParaRPr struct { + Lang string `xml:"lang,attr"` + AltLang string `xml:"altLang,attr,omitempty"` + Sz int `xml:"sz,attr,omitempty"` +} + +// cAutoTitleDeleted (Auto Title Is Deleted) directly maps the +// autoTitleDeleted element. This element specifies the title shall not be +// shown for this chart. +type cAutoTitleDeleted struct { + Val bool `xml:"val,attr"` +} + +// cView3D (View In 3D) directly maps the view3D element. This element +// specifies the 3-D view of the chart. +type cView3D struct { + RotX *attrValInt `xml:"rotX"` + RotY *attrValInt `xml:"rotY"` + RAngAx *attrValInt `xml:"rAngAx"` + DepthPercent *attrValInt `xml:"depthPercent"` + Perspective *attrValInt `xml:"perspective"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// cPlotArea directly maps the plotArea element. This element specifies the +// plot area of the chart. +type cPlotArea struct { + Layout *string `xml:"layout"` + AreaChart *cCharts `xml:"areaChart"` + Area3DChart *cCharts `xml:"area3DChart"` + BarChart *cCharts `xml:"barChart"` + Bar3DChart *cCharts `xml:"bar3DChart"` + BubbleChart *cCharts `xml:"bubbleChart"` + DoughnutChart *cCharts `xml:"doughnutChart"` + LineChart *cCharts `xml:"lineChart"` + PieChart *cCharts `xml:"pieChart"` + Pie3DChart *cCharts `xml:"pie3DChart"` + OfPieChart *cCharts `xml:"ofPieChart"` + RadarChart *cCharts `xml:"radarChart"` + ScatterChart *cCharts `xml:"scatterChart"` + Surface3DChart *cCharts `xml:"surface3DChart"` + SurfaceChart *cCharts `xml:"surfaceChart"` + CatAx []*cAxs `xml:"catAx"` + ValAx []*cAxs `xml:"valAx"` + SerAx []*cAxs `xml:"serAx"` + SpPr *cSpPr `xml:"spPr"` +} + +// cCharts specifies the common element of the chart. +type cCharts struct { + BarDir *attrValString `xml:"barDir"` + BubbleScale *attrValFloat `xml:"bubbleScale"` + Grouping *attrValString `xml:"grouping"` + RadarStyle *attrValString `xml:"radarStyle"` + ScatterStyle *attrValString `xml:"scatterStyle"` + OfPieType *attrValString `xml:"ofPieType"` + VaryColors *attrValBool `xml:"varyColors"` + Wireframe *attrValBool `xml:"wireframe"` + Ser *[]cSer `xml:"ser"` + SerLines *attrValString `xml:"serLines"` + DLbls *cDLbls `xml:"dLbls"` + Shape *attrValString `xml:"shape"` + HoleSize *attrValInt `xml:"holeSize"` + Smooth *attrValBool `xml:"smooth"` + Overlap *attrValInt `xml:"overlap"` + AxID []*attrValInt `xml:"axId"` +} + +// cAxs directly maps the catAx and valAx element. +type cAxs struct { + AxID *attrValInt `xml:"axId"` + Scaling *cScaling `xml:"scaling"` + Delete *attrValBool `xml:"delete"` + AxPos *attrValString `xml:"axPos"` + MajorGridlines *cChartLines `xml:"majorGridlines"` + MinorGridlines *cChartLines `xml:"minorGridlines"` + NumFmt *cNumFmt `xml:"numFmt"` + MajorTickMark *attrValString `xml:"majorTickMark"` + MinorTickMark *attrValString `xml:"minorTickMark"` + TickLblPos *attrValString `xml:"tickLblPos"` + SpPr *cSpPr `xml:"spPr"` + TxPr *cTxPr `xml:"txPr"` + CrossAx *attrValInt `xml:"crossAx"` + Crosses *attrValString `xml:"crosses"` + CrossBetween *attrValString `xml:"crossBetween"` + MajorUnit *attrValFloat `xml:"majorUnit"` + MinorUnit *attrValFloat `xml:"minorUnit"` + Auto *attrValBool `xml:"auto"` + LblAlgn *attrValString `xml:"lblAlgn"` + LblOffset *attrValInt `xml:"lblOffset"` + TickLblSkip *attrValInt `xml:"tickLblSkip"` + TickMarkSkip *attrValInt `xml:"tickMarkSkip"` + NoMultiLvlLbl *attrValBool `xml:"noMultiLvlLbl"` +} + +// cChartLines directly maps the chart lines content model. +type cChartLines struct { + SpPr *cSpPr `xml:"spPr"` +} + +// cScaling directly maps the scaling element. This element contains +// additional axis settings. +type cScaling struct { + LogBase *attrValFloat `xml:"logBase"` + Orientation *attrValString `xml:"orientation"` + Max *attrValFloat `xml:"max"` + Min *attrValFloat `xml:"min"` +} + +// cNumFmt (Numbering Format) directly maps the numFmt element. This element +// specifies number formatting for the parent element. +type cNumFmt struct { + FormatCode string `xml:"formatCode,attr"` + SourceLinked bool `xml:"sourceLinked,attr"` +} + +// cSer directly maps the ser element. This element specifies a series on a +// chart. +type cSer struct { + IDx *attrValInt `xml:"idx"` + Order *attrValInt `xml:"order"` + Tx *cTx `xml:"tx"` + SpPr *cSpPr `xml:"spPr"` + DPt []*cDPt `xml:"dPt"` + DLbls *cDLbls `xml:"dLbls"` + Marker *cMarker `xml:"marker"` + InvertIfNegative *attrValBool `xml:"invertIfNegative"` + Cat *cCat `xml:"cat"` + Val *cVal `xml:"val"` + XVal *cCat `xml:"xVal"` + YVal *cVal `xml:"yVal"` + Smooth *attrValBool `xml:"smooth"` + BubbleSize *cVal `xml:"bubbleSize"` + Bubble3D *attrValBool `xml:"bubble3D"` +} + +// cMarker (Marker) directly maps the marker element. This element specifies a +// data marker. +type cMarker struct { + Symbol *attrValString `xml:"symbol"` + Size *attrValInt `xml:"size"` + SpPr *cSpPr `xml:"spPr"` +} + +// cDPt (Data Point) directly maps the dPt element. This element specifies a +// single data point. +type cDPt struct { + IDx *attrValInt `xml:"idx"` + Bubble3D *attrValBool `xml:"bubble3D"` + SpPr *cSpPr `xml:"spPr"` +} + +// cCat (Category Axis Data) directly maps the cat element. This element +// specifies the data used for the category axis. +type cCat struct { + StrRef *cStrRef `xml:"strRef"` +} + +// cStrRef (String Reference) directly maps the strRef element. This element +// specifies a reference to data for a single data label or title with a cache +// of the last values used. +type cStrRef struct { + F string `xml:"f"` + StrCache *cStrCache `xml:"strCache"` +} + +// cStrCache (String Cache) directly maps the strCache element. This element +// specifies the last string data used for a chart. +type cStrCache struct { + Pt []*cPt `xml:"pt"` + PtCount *attrValInt `xml:"ptCount"` +} + +// cPt directly maps the pt element. This element specifies data for a +// particular data point. +type cPt struct { + IDx int `xml:"idx,attr"` + V *string `xml:"v"` +} + +// cVal directly maps the val element. This element specifies the data values +// which shall be used to define the location of data markers on a chart. +type cVal struct { + NumRef *cNumRef `xml:"numRef"` +} + +// cNumRef directly maps the numRef element. This element specifies a +// reference to numeric data with a cache of the last values used. +type cNumRef struct { + F string `xml:"f"` + NumCache *cNumCache `xml:"numCache"` +} + +// cNumCache directly maps the numCache element. This element specifies the +// last data shown on the chart for a series. +type cNumCache struct { + FormatCode string `xml:"formatCode"` + Pt []*cPt `xml:"pt"` + PtCount *attrValInt `xml:"ptCount"` +} + +// cDLbls (Data Lables) directly maps the dLbls element. This element serves +// as a root element that specifies the settings for the data labels for an +// entire series or the entire chart. It contains child elements that specify +// the specific formatting and positioning settings. +type cDLbls struct { + ShowLegendKey *attrValBool `xml:"showLegendKey"` + ShowVal *attrValBool `xml:"showVal"` + ShowCatName *attrValBool `xml:"showCatName"` + ShowSerName *attrValBool `xml:"showSerName"` + ShowPercent *attrValBool `xml:"showPercent"` + ShowBubbleSize *attrValBool `xml:"showBubbleSize"` + ShowLeaderLines *attrValBool `xml:"showLeaderLines"` +} + +// cLegend (Legend) directly maps the legend element. This element specifies +// the legend. +type cLegend struct { + Layout *string `xml:"layout"` + LegendPos *attrValString `xml:"legendPos"` + Overlay *attrValBool `xml:"overlay"` + SpPr *cSpPr `xml:"spPr"` + TxPr *cTxPr `xml:"txPr"` +} + +// cPrintSettings directly maps the printSettings element. This element +// specifies the print settings for the chart. +type cPrintSettings struct { + HeaderFooter *string `xml:"headerFooter"` + PageMargins *cPageMargins `xml:"pageMargins"` + PageSetup *string `xml:"pageSetup"` +} + +// cPageMargins directly maps the pageMargins element. This element specifies +// the page margins for a chart. +type cPageMargins struct { + B float64 `xml:"b,attr"` + Footer float64 `xml:"footer,attr"` + Header float64 `xml:"header,attr"` + L float64 `xml:"l,attr"` + R float64 `xml:"r,attr"` + T float64 `xml:"t,attr"` +} + +// formatChartAxis directly maps the format settings of the chart axis. +type formatChartAxis struct { + None bool `json:"none"` + Crossing string `json:"crossing"` + MajorGridlines bool `json:"major_grid_lines"` + MinorGridlines bool `json:"minor_grid_lines"` + MajorTickMark string `json:"major_tick_mark"` + MinorTickMark string `json:"minor_tick_mark"` + MinorUnitType string `json:"minor_unit_type"` + MajorUnit float64 `json:"major_unit"` + MajorUnitType string `json:"major_unit_type"` + TickLabelSkip int `json:"tick_label_skip"` + DisplayUnits string `json:"display_units"` + DisplayUnitsVisible bool `json:"display_units_visible"` + DateAxis bool `json:"date_axis"` + ReverseOrder bool `json:"reverse_order"` + Maximum *float64 `json:"maximum"` + Minimum *float64 `json:"minimum"` + NumFormat string `json:"num_format"` + NumFont struct { + Color string `json:"color"` + Bold bool `json:"bold"` + Italic bool `json:"italic"` + Underline bool `json:"underline"` + } `json:"num_font"` + LogBase float64 `json:"logbase"` + NameLayout formatLayout `json:"name_layout"` +} + +type formatChartDimension struct { + Width int `json:"width"` + Height int `json:"height"` +} + +// formatChart directly maps the format settings of the chart. +type formatChart struct { + Type string `json:"type"` + Series []formatChartSeries `json:"series"` + Format formatPicture `json:"format"` + Dimension formatChartDimension `json:"dimension"` + Legend formatChartLegend `json:"legend"` + Title formatChartTitle `json:"title"` + VaryColors bool `json:"vary_colors"` + XAxis formatChartAxis `json:"x_axis"` + YAxis formatChartAxis `json:"y_axis"` + Chartarea struct { + Border struct { + None bool `json:"none"` + } `json:"border"` + Fill struct { + Color string `json:"color"` + } `json:"fill"` + Pattern struct { + Pattern string `json:"pattern"` + FgColor string `json:"fg_color"` + BgColor string `json:"bg_color"` + } `json:"pattern"` + } `json:"chartarea"` + Plotarea struct { + ShowBubbleSize bool `json:"show_bubble_size"` + ShowCatName bool `json:"show_cat_name"` + ShowLeaderLines bool `json:"show_leader_lines"` + ShowPercent bool `json:"show_percent"` + ShowSerName bool `json:"show_series_name"` + ShowVal bool `json:"show_val"` + Gradient struct { + Colors []string `json:"colors"` + } `json:"gradient"` + Border struct { + Color string `json:"color"` + Width int `json:"width"` + DashType string `json:"dash_type"` + } `json:"border"` + Fill struct { + Color string `json:"color"` + } `json:"fill"` + Layout formatLayout `json:"layout"` + } `json:"plotarea"` + ShowBlanksAs string `json:"show_blanks_as"` + ShowHiddenData bool `json:"show_hidden_data"` + SetRotation int `json:"set_rotation"` + HoleSize int `json:"hole_size"` + order int +} + +// formatChartLegend directly maps the format settings of the chart legend. +type formatChartLegend struct { + None bool `json:"none"` + DeleteSeries []int `json:"delete_series"` + Font Font `json:"font"` + Layout formatLayout `json:"layout"` + Position string `json:"position"` + ShowLegendEntry bool `json:"show_legend_entry"` + ShowLegendKey bool `json:"show_legend_key"` +} + +// formatChartSeries directly maps the format settings of the chart series. +type formatChartSeries struct { + Name string `json:"name"` + Categories string `json:"categories"` + Values string `json:"values"` + Line struct { + None bool `json:"none"` + Color string `json:"color"` + Width float64 `json:"width"` + } `json:"line"` + Marker struct { + Symbol string `json:"symbol"` + Size int `json:"size"` + Width float64 `json:"width"` + Border struct { + Color string `json:"color"` + None bool `json:"none"` + } `json:"border"` + Fill struct { + Color string `json:"color"` + None bool `json:"none"` + } `json:"fill"` + } `json:"marker"` +} + +// formatChartTitle directly maps the format settings of the chart title. +type formatChartTitle struct { + None bool `json:"none"` + Name string `json:"name"` + Overlay bool `json:"overlay"` + Layout formatLayout `json:"layout"` +} + +// formatLayout directly maps the format settings of the element layout. +type formatLayout struct { + X float64 `json:"x"` + Y float64 `json:"y"` + Width float64 `json:"width"` + Height float64 `json:"height"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlChartSheet.go b/vendor/github.com/xuri/excelize/v2/xmlChartSheet.go new file mode 100644 index 00000000..f0f2f628 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlChartSheet.go @@ -0,0 +1,90 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// struct code generated by github.com/xuri/xgen +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxChartsheet directly maps the chartsheet element of Chartsheet Parts in +// a SpreadsheetML document. +type xlsxChartsheet struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main chartsheet"` + SheetPr *xlsxChartsheetPr `xml:"sheetPr"` + SheetViews *xlsxChartsheetViews `xml:"sheetViews"` + SheetProtection *xlsxChartsheetProtection `xml:"sheetProtection"` + CustomSheetViews *xlsxCustomChartsheetViews `xml:"customSheetViews"` + PageMargins *xlsxPageMargins `xml:"pageMargins"` + PageSetup *xlsxPageSetUp `xml:"pageSetup"` + HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"` + Drawing *xlsxDrawing `xml:"drawing"` + DrawingHF *xlsxDrawingHF `xml:"drawingHF"` + Picture *xlsxPicture `xml:"picture"` + WebPublishItems *xlsxInnerXML `xml:"webPublishItems"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxChartsheetPr specifies chart sheet properties. +type xlsxChartsheetPr struct { + XMLName xml.Name `xml:"sheetPr"` + PublishedAttr bool `xml:"published,attr,omitempty"` + CodeNameAttr string `xml:"codeName,attr,omitempty"` + TabColor *xlsxTabColor `xml:"tabColor"` +} + +// xlsxChartsheetViews specifies chart sheet views. +type xlsxChartsheetViews struct { + XMLName xml.Name `xml:"sheetViews"` + SheetView []*xlsxChartsheetView `xml:"sheetView"` + ExtLst []*xlsxExtLst `xml:"extLst"` +} + +// xlsxChartsheetView defines custom view properties for chart sheets. +type xlsxChartsheetView struct { + XMLName xml.Name `xml:"sheetView"` + TabSelectedAttr bool `xml:"tabSelected,attr,omitempty"` + ZoomScaleAttr uint32 `xml:"zoomScale,attr,omitempty"` + WorkbookViewIDAttr uint32 `xml:"workbookViewId,attr"` + ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"` + ExtLst []*xlsxExtLst `xml:"extLst"` +} + +// xlsxChartsheetProtection collection expresses the chart sheet protection +// options to enforce when the chart sheet is protected. +type xlsxChartsheetProtection struct { + XMLName xml.Name `xml:"sheetProtection"` + AlgorithmNameAttr string `xml:"algorithmName,attr,omitempty"` + HashValueAttr []byte `xml:"hashValue,attr,omitempty"` + SaltValueAttr []byte `xml:"saltValue,attr,omitempty"` + SpinCountAttr uint32 `xml:"spinCount,attr,omitempty"` + ContentAttr bool `xml:"content,attr,omitempty"` + ObjectsAttr bool `xml:"objects,attr,omitempty"` +} + +// xlsxCustomChartsheetViews collection of custom Chart Sheet View +// information. +type xlsxCustomChartsheetViews struct { + XMLName xml.Name `xml:"customSheetViews"` + CustomSheetView []*xlsxCustomChartsheetView `xml:"customSheetView"` +} + +// xlsxCustomChartsheetView defines custom view properties for chart sheets. +type xlsxCustomChartsheetView struct { + XMLName xml.Name `xml:"customSheetView"` + GUIDAttr string `xml:"guid,attr"` + ScaleAttr uint32 `xml:"scale,attr,omitempty"` + StateAttr string `xml:"state,attr,omitempty"` + ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"` + PageMargins []*xlsxPageMargins `xml:"pageMargins"` + PageSetup []*xlsxPageSetUp `xml:"pageSetup"` + HeaderFooter []*xlsxHeaderFooter `xml:"headerFooter"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlComments.go b/vendor/github.com/xuri/excelize/v2/xmlComments.go new file mode 100644 index 00000000..b4602fc9 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlComments.go @@ -0,0 +1,87 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxComments directly maps the comments element from the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main. A comment is a +// rich text note that is attached to and associated with a cell, separate from +// other cell content. Comment content is stored separate from the cell, and is +// displayed in a drawing object (like a text box) that is separate from, but +// associated with, a cell. Comments are used as reminders, such as noting how a +// complex formula works, or to provide feedback to other users. Comments can +// also be used to explain assumptions made in a formula or to call out +// something special about the cell. +type xlsxComments struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main comments"` + Authors xlsxAuthor `xml:"authors"` + CommentList xlsxCommentList `xml:"commentList"` +} + +// xlsxAuthor directly maps the author element. This element holds a string +// representing the name of a single author of comments. Every comment shall +// have an author. The maximum length of the author string is an implementation +// detail, but a good guideline is 255 chars. +type xlsxAuthor struct { + Author []string `xml:"author"` +} + +// xlsxCommentList (List of Comments) directly maps the xlsxCommentList element. +// This element is a container that holds a list of comments for the sheet. +type xlsxCommentList struct { + Comment []xlsxComment `xml:"comment"` +} + +// xlsxComment directly maps the comment element. This element represents a +// single user entered comment. Each comment shall have an author and can +// optionally contain richly formatted text. +type xlsxComment struct { + Ref string `xml:"ref,attr"` + AuthorID int `xml:"authorId,attr"` + Text xlsxText `xml:"text"` +} + +// xlsxText directly maps the text element. This element contains rich text +// which represents the text of a comment. The maximum length for this text is a +// spreadsheet application implementation detail. A recommended guideline is +// 32767 chars. +type xlsxText struct { + T *string `xml:"t"` + R []xlsxR `xml:"r"` + RPh *xlsxPhoneticRun `xml:"rPh"` + PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"` +} + +// xlsxPhoneticRun element represents a run of text which displays a phonetic +// hint for this String Item (si). Phonetic hints are used to give information +// about the pronunciation of an East Asian language. The hints are displayed +// as text within the spreadsheet cells across the top portion of the cell. +type xlsxPhoneticRun struct { + Sb uint32 `xml:"sb,attr"` + Eb uint32 `xml:"eb,attr"` + T string `xml:"t"` +} + +// formatComment directly maps the format settings of the comment. +type formatComment struct { + Author string `json:"author"` + Text string `json:"text"` +} + +// Comment directly maps the comment information. +type Comment struct { + Author string `json:"author"` + AuthorID int `json:"author_id"` + Ref string `json:"ref"` + Text string `json:"text"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlContentTypes.go b/vendor/github.com/xuri/excelize/v2/xmlContentTypes.go new file mode 100644 index 00000000..4b3cd642 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlContentTypes.go @@ -0,0 +1,41 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/xml" + "sync" +) + +// xlsxTypes directly maps the types' element of content types for relationship +// parts, it takes a Multipurpose Internet Mail Extension (MIME) media type as a +// value. +type xlsxTypes struct { + sync.Mutex + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/content-types Types"` + Overrides []xlsxOverride `xml:"Override"` + Defaults []xlsxDefault `xml:"Default"` +} + +// xlsxOverride directly maps the override element in the namespace +// http://schemas.openxmlformats.org/package/2006/content-types +type xlsxOverride struct { + PartName string `xml:",attr"` + ContentType string `xml:",attr"` +} + +// xlsxDefault directly maps the default element in the namespace +// http://schemas.openxmlformats.org/package/2006/content-types +type xlsxDefault struct { + Extension string `xml:",attr"` + ContentType string `xml:",attr"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlCore.go b/vendor/github.com/xuri/excelize/v2/xmlCore.go new file mode 100644 index 00000000..22ae6bc0 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlCore.go @@ -0,0 +1,91 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// DocProperties directly maps the document core properties. +type DocProperties struct { + Category string + ContentStatus string + Created string + Creator string + Description string + Identifier string + Keywords string + LastModifiedBy string + Modified string + Revision string + Subject string + Title string + Language string + Version string +} + +// decodeCoreProperties directly maps the root element for a part of this +// content type shall coreProperties. In order to solve the problem that the +// label structure is changed after serialization and deserialization, two +// different structures are defined. decodeCoreProperties just for +// deserialization. +type decodeCoreProperties struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"` + Title string `xml:"http://purl.org/dc/elements/1.1/ title,omitempty"` + Subject string `xml:"http://purl.org/dc/elements/1.1/ subject,omitempty"` + Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"` + Keywords string `xml:"keywords,omitempty"` + Description string `xml:"http://purl.org/dc/elements/1.1/ description,omitempty"` + LastModifiedBy string `xml:"lastModifiedBy"` + Language string `xml:"http://purl.org/dc/elements/1.1/ language,omitempty"` + Identifier string `xml:"http://purl.org/dc/elements/1.1/ identifier,omitempty"` + Revision string `xml:"revision,omitempty"` + Created struct { + Text string `xml:",chardata"` + Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"` + } `xml:"http://purl.org/dc/terms/ created"` + Modified struct { + Text string `xml:",chardata"` + Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"` + } `xml:"http://purl.org/dc/terms/ modified"` + ContentStatus string `xml:"contentStatus,omitempty"` + Category string `xml:"category,omitempty"` + Version string `xml:"version,omitempty"` +} + +// xlsxCoreProperties directly maps the root element for a part of this +// content type shall coreProperties. +type xlsxCoreProperties struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"` + Dc string `xml:"xmlns:dc,attr"` + Dcterms string `xml:"xmlns:dcterms,attr"` + Dcmitype string `xml:"xmlns:dcmitype,attr"` + XSI string `xml:"xmlns:xsi,attr"` + Title string `xml:"dc:title,omitempty"` + Subject string `xml:"dc:subject,omitempty"` + Creator string `xml:"dc:creator"` + Keywords string `xml:"keywords,omitempty"` + Description string `xml:"dc:description,omitempty"` + LastModifiedBy string `xml:"lastModifiedBy"` + Language string `xml:"dc:language,omitempty"` + Identifier string `xml:"dc:identifier,omitempty"` + Revision string `xml:"revision,omitempty"` + Created struct { + Text string `xml:",chardata"` + Type string `xml:"xsi:type,attr"` + } `xml:"dcterms:created"` + Modified struct { + Text string `xml:",chardata"` + Type string `xml:"xsi:type,attr"` + } `xml:"dcterms:modified"` + ContentStatus string `xml:"contentStatus,omitempty"` + Category string `xml:"category,omitempty"` + Version string `xml:"version,omitempty"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlDecodeDrawing.go b/vendor/github.com/xuri/excelize/v2/xmlDecodeDrawing.go new file mode 100644 index 00000000..fb920be1 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlDecodeDrawing.go @@ -0,0 +1,234 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// decodeCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape +// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element +// specifies a two cell anchor placeholder for a group, a shape, or a drawing +// element. It moves with cells and its extents are in EMU units. +type decodeCellAnchor struct { + EditAs string `xml:"editAs,attr,omitempty"` + From *decodeFrom `xml:"from"` + To *decodeTo `xml:"to"` + Sp *decodeSp `xml:"sp"` + ClientData *decodeClientData `xml:"clientData"` + Content string `xml:",innerxml"` +} + +// xdrSp (Shape) directly maps the sp element. This element specifies the +// existence of a single shape. A shape can either be a preset or a custom +// geometry, defined using the SpreadsheetDrawingML framework. In addition to +// a geometry each shape can have both visual and non-visual properties +// attached. Text and corresponding styling information can also be attached +// to a shape. This shape is specified along with all other shapes within +// either the shape tree or group shape elements. +type decodeSp struct { + NvSpPr *decodeNvSpPr `xml:"nvSpPr"` + SpPr *decodeSpPr `xml:"spPr"` +} + +// decodeSp (Non-Visual Properties for a Shape) directly maps the nvSpPr +// element. This element specifies all non-visual properties for a shape. This +// element is a container for the non-visual identification properties, shape +// properties and application properties that are to be associated with a +// shape. This allows for additional information that does not affect the +// appearance of the shape to be stored. +type decodeNvSpPr struct { + CNvPr *decodeCNvPr `xml:"cNvPr"` + ExtLst *decodeExt `xml:"extLst"` + CNvSpPr *decodeCNvSpPr `xml:"cNvSpPr"` +} + +// decodeCNvSpPr (Connection Non-Visual Shape Properties) directly maps the +// cNvSpPr element. This element specifies the set of non-visual properties +// for a connection shape. These properties specify all data about the +// connection shape which do not affect its display within a spreadsheet. +type decodeCNvSpPr struct { + TxBox bool `xml:"txBox,attr"` +} + +// decodeWsDr directly maps the root element for a part of this content type +// shall wsDr. In order to solve the problem that the label structure is +// changed after serialization and deserialization, two different structures +// are defined. decodeWsDr just for deserialization. +type decodeWsDr struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr,omitempty"` + A string `xml:"xmlns a,attr"` + Xdr string `xml:"xmlns xdr,attr"` + R string `xml:"xmlns r,attr"` + AlternateContent []*xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` + OneCellAnchor []*decodeCellAnchor `xml:"oneCellAnchor,omitempty"` + TwoCellAnchor []*decodeCellAnchor `xml:"twoCellAnchor,omitempty"` +} + +// decodeTwoCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape +// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element +// specifies a two cell anchor placeholder for a group, a shape, or a drawing +// element. It moves with cells and its extents are in EMU units. +type decodeTwoCellAnchor struct { + From *decodeFrom `xml:"from"` + To *decodeTo `xml:"to"` + Pic *decodePic `xml:"pic"` + ClientData *decodeClientData `xml:"clientData"` +} + +// decodeCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This +// element specifies non-visual canvas properties. This allows for additional +// information that does not affect the appearance of the picture to be +// stored. +type decodeCNvPr struct { + ID int `xml:"id,attr"` + Name string `xml:"name,attr"` + Descr string `xml:"descr,attr"` + Title string `xml:"title,attr,omitempty"` +} + +// decodePicLocks directly maps the picLocks (Picture Locks). This element +// specifies all locking properties for a graphic frame. These properties +// inform the generating application about specific properties that have been +// previously locked and thus should not be changed. +type decodePicLocks struct { + NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"` + NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"` + NoChangeAspect bool `xml:"noChangeAspect,attr"` + NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"` + NoCrop bool `xml:"noCrop,attr,omitempty"` + NoEditPoints bool `xml:"noEditPoints,attr,omitempty"` + NoGrp bool `xml:"noGrp,attr,omitempty"` + NoMove bool `xml:"noMove,attr,omitempty"` + NoResize bool `xml:"noResize,attr,omitempty"` + NoRot bool `xml:"noRot,attr,omitempty"` + NoSelect bool `xml:"noSelect,attr,omitempty"` +} + +// decodeBlip element specifies the existence of an image (binary large image +// or picture) and contains a reference to the image data. +type decodeBlip struct { + Embed string `xml:"embed,attr"` + Cstate string `xml:"cstate,attr,omitempty"` + R string `xml:"r,attr"` +} + +// decodeStretch directly maps the stretch element. This element specifies +// that a BLIP should be stretched to fill the target rectangle. The other +// option is a tile where a BLIP is tiled to fill the available area. +type decodeStretch struct { + FillRect string `xml:"fillRect"` +} + +// decodeOff directly maps the colOff and rowOff element. This element is used +// to specify the column offset within a cell. +type decodeOff struct { + X int `xml:"x,attr"` + Y int `xml:"y,attr"` +} + +// decodeExt directly maps the ext element. +type decodeExt struct { + Cx int `xml:"cx,attr"` + Cy int `xml:"cy,attr"` +} + +// decodePrstGeom directly maps the prstGeom (Preset geometry). This element +// specifies when a preset geometric shape should be used instead of a custom +// geometric shape. The generating application should be able to render all +// preset geometries enumerated in the ST_ShapeType list. +type decodePrstGeom struct { + Prst string `xml:"prst,attr"` +} + +// decodeXfrm directly maps the xfrm (2D Transform for Graphic Frame). This +// element specifies the transform to be applied to the corresponding graphic +// frame. This transformation is applied to the graphic frame just as it would +// be for a shape or group shape. +type decodeXfrm struct { + Off decodeOff `xml:"off"` + Ext decodeExt `xml:"ext"` +} + +// decodeCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing +// Properties). This element specifies the non-visual properties for the picture +// canvas. These properties are to be used by the generating application to +// determine how certain properties are to be changed for the picture object in +// question. +type decodeCNvPicPr struct { + PicLocks decodePicLocks `xml:"picLocks"` +} + +// directly maps the nvPicPr (Non-Visual Properties for a Picture). This +// element specifies all non-visual properties for a picture. This element is +// a container for the non-visual identification properties, shape properties +// and application properties that are to be associated with a picture. This +// allows for additional information that does not affect the appearance of +// the picture to be stored. +type decodeNvPicPr struct { + CNvPr decodeCNvPr `xml:"cNvPr"` + CNvPicPr decodeCNvPicPr `xml:"cNvPicPr"` +} + +// decodeBlipFill directly maps the blipFill (Picture Fill). This element +// specifies the kind of picture fill that the picture object has. Because a +// picture has a picture fill already by default, it is possible to have two +// fills specified for a picture object. +type decodeBlipFill struct { + Blip decodeBlip `xml:"blip"` + Stretch decodeStretch `xml:"stretch"` +} + +// decodeSpPr directly maps the spPr (Shape Properties). This element +// specifies the visual shape properties that can be applied to a picture. +// These are the same properties that are allowed to describe the visual +// properties of a shape but are used here to describe the visual appearance +// of a picture within a document. +type decodeSpPr struct { + Xfrm decodeXfrm `xml:"xfrm"` + PrstGeom decodePrstGeom `xml:"prstGeom"` +} + +// decodePic elements encompass the definition of pictures within the +// DrawingML framework. While pictures are in many ways very similar to shapes +// they have specific properties that are unique in order to optimize for +// picture- specific scenarios. +type decodePic struct { + NvPicPr decodeNvPicPr `xml:"nvPicPr"` + BlipFill decodeBlipFill `xml:"blipFill"` + SpPr decodeSpPr `xml:"spPr"` +} + +// decodeFrom specifies the starting anchor. +type decodeFrom struct { + Col int `xml:"col"` + ColOff int `xml:"colOff"` + Row int `xml:"row"` + RowOff int `xml:"rowOff"` +} + +// decodeTo directly specifies the ending anchor. +type decodeTo struct { + Col int `xml:"col"` + ColOff int `xml:"colOff"` + Row int `xml:"row"` + RowOff int `xml:"rowOff"` +} + +// decodeClientData directly maps the clientData element. An empty element +// which specifies (via attributes) certain properties related to printing and +// selection of the drawing object. The fLocksWithSheet attribute (either true +// or false) determines whether to disable selection when the sheet is +// protected, and fPrintsWithSheet attribute (either true or false) determines +// whether the object is printed when the sheet is printed. +type decodeClientData struct { + FLocksWithSheet bool `xml:"fLocksWithSheet,attr"` + FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlDrawing.go b/vendor/github.com/xuri/excelize/v2/xmlDrawing.go new file mode 100644 index 00000000..4bf43ec8 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlDrawing.go @@ -0,0 +1,506 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/xml" + "sync" +) + +// Source relationship and namespace list, associated prefixes and schema in which it was +// introduced. +var ( + SourceRelationship = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"} + SourceRelationshipCompatibility = xml.Attr{Name: xml.Name{Local: "mc", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/markup-compatibility/2006"} + SourceRelationshipChart20070802 = xml.Attr{Name: xml.Name{Local: "c14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"} + SourceRelationshipChart2014 = xml.Attr{Name: xml.Name{Local: "c16", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2014/chart"} + SourceRelationshipChart201506 = xml.Attr{Name: xml.Name{Local: "c16r2", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2015/06/chart"} + NameSpaceSpreadSheet = xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} + NameSpaceSpreadSheetX14 = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"} + NameSpaceDrawingML = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"} + NameSpaceDrawingMLChart = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"} + NameSpaceDrawingMLSpreadSheet = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"} + NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"} + NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"} + NameSpaceMacExcel2008Main = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"} + NameSpaceDocumentPropertiesVariantTypes = xml.Attr{Name: xml.Name{Local: "vt", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"} +) + +// Source relationship and namespace. +const ( + SourceRelationshipOfficeDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" + SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" + SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" + SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" + SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" + SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" + SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" + SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" + SourceRelationshipChartsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet" + SourceRelationshipDialogsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet" + SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable" + SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" + SourceRelationshipSharedStrings = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" + SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject" + NameSpaceXML = "http://www.w3.org/XML/1998/namespace" + NameSpaceXMLSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance" + StrictSourceRelationship = "http://purl.oclc.org/ooxml/officeDocument/relationships" + StrictSourceRelationshipOfficeDocument = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument" + StrictSourceRelationshipChart = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart" + StrictSourceRelationshipComments = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments" + StrictSourceRelationshipImage = "http://purl.oclc.org/ooxml/officeDocument/relationships/image" + StrictNameSpaceSpreadSheet = "http://purl.oclc.org/ooxml/spreadsheetml/main" + NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/" + NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/" + NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/" + ContentTypeDrawing = "application/vnd.openxmlformats-officedocument.drawing+xml" + ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" + ContentTypeSheetML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" + ContentTypeTemplate = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" + ContentTypeAddinMacro = "application/vnd.ms-excel.addin.macroEnabled.main+xml" + ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml" + ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml" + ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" + ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" + ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" + ContentTypeSpreadSheetMLPivotTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" + ContentTypeSpreadSheetMLSharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" + ContentTypeSpreadSheetMLTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml" + ContentTypeSpreadSheetMLWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" + ContentTypeVBA = "application/vnd.ms-office.vbaProject" + ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing" + // ExtURIConditionalFormattings is the extLst child element + // ([ISO/IEC29500-1:2016] section 18.2.10) of the worksheet element + // ([ISO/IEC29500-1:2016] section 18.3.1.99) is extended by the addition of + // new child ext elements ([ISO/IEC29500-1:2016] section 18.2.7) + ExtURIConditionalFormattings = "{78C0D931-6437-407D-A8EE-F0AAD7539E65}" + ExtURIDataValidations = "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}" + ExtURISparklineGroups = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}" + ExtURISlicerListX14 = "{A8765BA9-456A-4DAB-B4F3-ACF838C121DE}" + ExtURISlicerCachesListX14 = "{BBE1A952-AA13-448e-AADC-164F8A28A991}" + ExtURISlicerListX15 = "{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}" + ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}" + ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}" + ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}" + ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}" + ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}" + ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}" +) + +// Excel specifications and limits +const ( + UnzipSizeLimit = 1000 << 24 + StreamChunkSize = 1 << 24 + MaxFontFamilyLength = 31 + MaxFontSize = 409 + MaxFileNameLength = 207 + MaxFieldLength = 255 + MaxColumnWidth = 255 + MaxRowHeight = 409 + TotalRows = 1048576 + TotalColumns = 16384 + TotalSheetHyperlinks = 65529 + TotalCellChars = 32767 + // pivotTableVersion should be greater than 3. One or more of the + // PivotTables chosen are created in a version of Excel earlier than + // Excel 2007 or in compatibility mode. Slicer can only be used with + // PivotTables created in Excel 2007 or a newer version of Excel. + pivotTableVersion = 3 +) + +var supportImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff"} + +// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This +// element specifies non-visual canvas properties. This allows for additional +// information that does not affect the appearance of the picture to be stored. +type xlsxCNvPr struct { + ID int `xml:"id,attr"` + Name string `xml:"name,attr"` + Descr string `xml:"descr,attr"` + Title string `xml:"title,attr,omitempty"` + HlinkClick *xlsxHlinkClick `xml:"a:hlinkClick"` +} + +// xlsxHlinkClick (Click Hyperlink) Specifies the on-click hyperlink +// information to be applied to a run of text. When the hyperlink text is +// clicked the link is fetched. +type xlsxHlinkClick struct { + R string `xml:"xmlns:r,attr,omitempty"` + RID string `xml:"r:id,attr,omitempty"` + InvalidURL string `xml:"invalidUrl,attr,omitempty"` + Action string `xml:"action,attr,omitempty"` + TgtFrame string `xml:"tgtFrame,attr,omitempty"` + Tooltip string `xml:"tooltip,attr,omitempty"` + History bool `xml:"history,attr,omitempty"` + HighlightClick bool `xml:"highlightClick,attr,omitempty"` + EndSnd bool `xml:"endSnd,attr,omitempty"` +} + +// xlsxPicLocks directly maps the picLocks (Picture Locks). This element +// specifies all locking properties for a graphic frame. These properties inform +// the generating application about specific properties that have been +// previously locked and thus should not be changed. +type xlsxPicLocks struct { + NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"` + NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"` + NoChangeAspect bool `xml:"noChangeAspect,attr"` + NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"` + NoCrop bool `xml:"noCrop,attr,omitempty"` + NoEditPoints bool `xml:"noEditPoints,attr,omitempty"` + NoGrp bool `xml:"noGrp,attr,omitempty"` + NoMove bool `xml:"noMove,attr,omitempty"` + NoResize bool `xml:"noResize,attr,omitempty"` + NoRot bool `xml:"noRot,attr,omitempty"` + NoSelect bool `xml:"noSelect,attr,omitempty"` +} + +// xlsxBlip element specifies the existence of an image (binary large image or +// picture) and contains a reference to the image data. +type xlsxBlip struct { + Embed string `xml:"r:embed,attr"` + Cstate string `xml:"cstate,attr,omitempty"` + R string `xml:"xmlns:r,attr"` +} + +// xlsxStretch directly maps the stretch element. This element specifies that a +// BLIP should be stretched to fill the target rectangle. The other option is a +// tile where a BLIP is tiled to fill the available area. +type xlsxStretch struct { + FillRect string `xml:"a:fillRect"` +} + +// xlsxOff directly maps the colOff and rowOff element. This element is used to +// specify the column offset within a cell. +type xlsxOff struct { + X int `xml:"x,attr"` + Y int `xml:"y,attr"` +} + +// xlsxExt directly maps the ext element. +type xlsxExt struct { + Cx int `xml:"cx,attr"` + Cy int `xml:"cy,attr"` +} + +// xlsxPrstGeom directly maps the prstGeom (Preset geometry). This element +// specifies when a preset geometric shape should be used instead of a custom +// geometric shape. The generating application should be able to render all +// preset geometries enumerated in the ST_ShapeType list. +type xlsxPrstGeom struct { + Prst string `xml:"prst,attr"` +} + +// xlsxXfrm directly maps the xfrm (2D Transform for Graphic Frame). This +// element specifies the transform to be applied to the corresponding graphic +// frame. This transformation is applied to the graphic frame just as it would +// be for a shape or group shape. +type xlsxXfrm struct { + Off xlsxOff `xml:"a:off"` + Ext xlsxExt `xml:"a:ext"` +} + +// xlsxCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing +// Properties). This element specifies the non-visual properties for the picture +// canvas. These properties are to be used by the generating application to +// determine how certain properties are to be changed for the picture object in +// question. +type xlsxCNvPicPr struct { + PicLocks xlsxPicLocks `xml:"a:picLocks"` +} + +// directly maps the nvPicPr (Non-Visual Properties for a Picture). This element +// specifies all non-visual properties for a picture. This element is a +// container for the non-visual identification properties, shape properties and +// application properties that are to be associated with a picture. This allows +// for additional information that does not affect the appearance of the picture +// to be stored. +type xlsxNvPicPr struct { + CNvPr xlsxCNvPr `xml:"xdr:cNvPr"` + CNvPicPr xlsxCNvPicPr `xml:"xdr:cNvPicPr"` +} + +// xlsxBlipFill directly maps the blipFill (Picture Fill). This element +// specifies the kind of picture fill that the picture object has. Because a +// picture has a picture fill already by default, it is possible to have two +// fills specified for a picture object. +type xlsxBlipFill struct { + Blip xlsxBlip `xml:"a:blip"` + Stretch xlsxStretch `xml:"a:stretch"` +} + +// xlsxLineProperties specifies the width of a line in EMUs. This simple type +// has a minimum value of greater than or equal to 0. This simple type has a +// maximum value of less than or equal to 20116800. +type xlsxLineProperties struct { + W int `xml:"w,attr,omitempty"` +} + +// xlsxSpPr directly maps the spPr (Shape Properties). This element specifies +// the visual shape properties that can be applied to a picture. These are the +// same properties that are allowed to describe the visual properties of a shape +// but are used here to describe the visual appearance of a picture within a +// document. +type xlsxSpPr struct { + Xfrm xlsxXfrm `xml:"a:xfrm"` + PrstGeom xlsxPrstGeom `xml:"a:prstGeom"` + Ln xlsxLineProperties `xml:"a:ln"` +} + +// xlsxPic elements encompass the definition of pictures within the DrawingML +// framework. While pictures are in many ways very similar to shapes they have +// specific properties that are unique in order to optimize for picture- +// specific scenarios. +type xlsxPic struct { + NvPicPr xlsxNvPicPr `xml:"xdr:nvPicPr"` + BlipFill xlsxBlipFill `xml:"xdr:blipFill"` + SpPr xlsxSpPr `xml:"xdr:spPr"` +} + +// xlsxFrom specifies the starting anchor. +type xlsxFrom struct { + Col int `xml:"xdr:col"` + ColOff int `xml:"xdr:colOff"` + Row int `xml:"xdr:row"` + RowOff int `xml:"xdr:rowOff"` +} + +// xlsxTo directly specifies the ending anchor. +type xlsxTo struct { + Col int `xml:"xdr:col"` + ColOff int `xml:"xdr:colOff"` + Row int `xml:"xdr:row"` + RowOff int `xml:"xdr:rowOff"` +} + +// xdrClientData directly maps the clientData element. An empty element which +// specifies (via attributes) certain properties related to printing and +// selection of the drawing object. The fLocksWithSheet attribute (either true +// or false) determines whether to disable selection when the sheet is +// protected, and fPrintsWithSheet attribute (either true or false) determines +// whether the object is printed when the sheet is printed. +type xdrClientData struct { + FLocksWithSheet bool `xml:"fLocksWithSheet,attr"` + FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"` +} + +// xdrCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape Size) +// and twoCellAnchor (Two Cell Anchor Shape Size). This element specifies a two +// cell anchor placeholder for a group, a shape, or a drawing element. It moves +// with cells and its extents are in EMU units. +type xdrCellAnchor struct { + EditAs string `xml:"editAs,attr,omitempty"` + Pos *xlsxPoint2D `xml:"xdr:pos"` + From *xlsxFrom `xml:"xdr:from"` + To *xlsxTo `xml:"xdr:to"` + Ext *xlsxExt `xml:"xdr:ext"` + Sp *xdrSp `xml:"xdr:sp"` + Pic *xlsxPic `xml:"xdr:pic,omitempty"` + GraphicFrame string `xml:",innerxml"` + ClientData *xdrClientData `xml:"xdr:clientData"` +} + +// xlsxPoint2D describes the position of a drawing element within a spreadsheet. +type xlsxPoint2D struct { + XMLName xml.Name `xml:"xdr:pos"` + X int `xml:"x,attr"` + Y int `xml:"y,attr"` +} + +// xlsxWsDr directly maps the root element for a part of this content type shall +// wsDr. +type xlsxWsDr struct { + sync.Mutex + XMLName xml.Name `xml:"xdr:wsDr"` + A string `xml:"xmlns:a,attr,omitempty"` + Xdr string `xml:"xmlns:xdr,attr,omitempty"` + R string `xml:"xmlns:r,attr,omitempty"` + AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"` + AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"` + OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"` + TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"` +} + +// xlsxGraphicFrame (Graphic Frame) directly maps the xdr:graphicFrame element. +// This element specifies the existence of a graphics frame. This frame contains +// a graphic that was generated by an external source and needs a container in +// which to be displayed on the slide surface. +type xlsxGraphicFrame struct { + XMLName xml.Name `xml:"xdr:graphicFrame"` + Macro string `xml:"macro,attr"` + NvGraphicFramePr xlsxNvGraphicFramePr `xml:"xdr:nvGraphicFramePr"` + Xfrm xlsxXfrm `xml:"xdr:xfrm"` + Graphic *xlsxGraphic `xml:"a:graphic"` +} + +// xlsxNvGraphicFramePr (Non-Visual Properties for a Graphic Frame) directly +// maps the xdr:nvGraphicFramePr element. This element specifies all non-visual +// properties for a graphic frame. This element is a container for the non- +// visual identification properties, shape properties and application properties +// that are to be associated with a graphic frame. This allows for additional +// information that does not affect the appearance of the graphic frame to be +// stored. +type xlsxNvGraphicFramePr struct { + CNvPr *xlsxCNvPr `xml:"xdr:cNvPr"` + ChicNvGraphicFramePr string `xml:"xdr:cNvGraphicFramePr"` +} + +// xlsxGraphic (Graphic Object) directly maps the a:graphic element. This +// element specifies the existence of a single graphic object. Document authors +// should refer to this element when they wish to persist a graphical object of +// some kind. The specification for this graphical object is provided entirely +// by the document author and referenced within the graphicData child element. +type xlsxGraphic struct { + GraphicData *xlsxGraphicData `xml:"a:graphicData"` +} + +// xlsxGraphicData (Graphic Object Data) directly maps the a:graphicData +// element. This element specifies the reference to a graphic object within the +// document. This graphic object is provided entirely by the document authors +// who choose to persist this data within the document. +type xlsxGraphicData struct { + URI string `xml:"uri,attr"` + Chart *xlsxChart `xml:"c:chart,omitempty"` +} + +// xlsxChart (Chart) directly maps the c:chart element. +type xlsxChart struct { + C string `xml:"xmlns:c,attr"` + RID string `xml:"r:id,attr"` + R string `xml:"xmlns:r,attr"` +} + +// xdrSp (Shape) directly maps the xdr:sp element. This element specifies the +// existence of a single shape. A shape can either be a preset or a custom +// geometry, defined using the SpreadsheetDrawingML framework. In addition to a +// geometry each shape can have both visual and non-visual properties attached. +// Text and corresponding styling information can also be attached to a shape. +// This shape is specified along with all other shapes within either the shape +// tree or group shape elements. +type xdrSp struct { + Macro string `xml:"macro,attr"` + Textlink string `xml:"textlink,attr"` + NvSpPr *xdrNvSpPr `xml:"xdr:nvSpPr"` + SpPr *xlsxSpPr `xml:"xdr:spPr"` + Style *xdrStyle `xml:"xdr:style"` + TxBody *xdrTxBody `xml:"xdr:txBody"` +} + +// xdrNvSpPr (Non-Visual Properties for a Shape) directly maps the xdr:nvSpPr +// element. This element specifies all non-visual properties for a shape. This +// element is a container for the non-visual identification properties, shape +// properties and application properties that are to be associated with a shape. +// This allows for additional information that does not affect the appearance of +// the shape to be stored. +type xdrNvSpPr struct { + CNvPr *xlsxCNvPr `xml:"xdr:cNvPr"` + CNvSpPr *xdrCNvSpPr `xml:"xdr:cNvSpPr"` +} + +// xdrCNvSpPr (Connection Non-Visual Shape Properties) directly maps the +// xdr:cNvSpPr element. This element specifies the set of non-visual properties +// for a connection shape. These properties specify all data about the +// connection shape which do not affect its display within a spreadsheet. +type xdrCNvSpPr struct { + TxBox bool `xml:"txBox,attr"` +} + +// xdrStyle (Shape Style) directly maps the xdr:style element. The element +// specifies the style that is applied to a shape and the corresponding +// references for each of the style components such as lines and fills. +type xdrStyle struct { + LnRef *aRef `xml:"a:lnRef"` + FillRef *aRef `xml:"a:fillRef"` + EffectRef *aRef `xml:"a:effectRef"` + FontRef *aFontRef `xml:"a:fontRef"` +} + +// aRef directly maps the a:lnRef, a:fillRef and a:effectRef element. +type aRef struct { + Idx int `xml:"idx,attr"` + ScrgbClr *aScrgbClr `xml:"a:scrgbClr"` + SchemeClr *attrValString `xml:"a:schemeClr"` + SrgbClr *attrValString `xml:"a:srgbClr"` +} + +// aScrgbClr (RGB Color Model - Percentage Variant) directly maps the a:scrgbClr +// element. This element specifies a color using the red, green, blue RGB color +// model. Each component, red, green, and blue is expressed as a percentage from +// 0% to 100%. A linear gamma of 1.0 is assumed. +type aScrgbClr struct { + R float64 `xml:"r,attr"` + G float64 `xml:"g,attr"` + B float64 `xml:"b,attr"` +} + +// aFontRef (Font Reference) directly maps the a:fontRef element. This element +// represents a reference to a themed font. When used it specifies which themed +// font to use along with a choice of color. +type aFontRef struct { + Idx string `xml:"idx,attr"` + SchemeClr *attrValString `xml:"a:schemeClr"` +} + +// xdrTxBody (Shape Text Body) directly maps the xdr:txBody element. This +// element specifies the existence of text to be contained within the +// corresponding shape. All visible text and visible text related properties are +// contained within this element. There can be multiple paragraphs and within +// paragraphs multiple runs of text. +type xdrTxBody struct { + BodyPr *aBodyPr `xml:"a:bodyPr"` + P []*aP `xml:"a:p"` +} + +// formatPicture directly maps the format settings of the picture. +type formatPicture struct { + FPrintsWithSheet bool `json:"print_obj"` + FLocksWithSheet bool `json:"locked"` + NoChangeAspect bool `json:"lock_aspect_ratio"` + Autofit bool `json:"autofit"` + OffsetX int `json:"x_offset"` + OffsetY int `json:"y_offset"` + XScale float64 `json:"x_scale"` + YScale float64 `json:"y_scale"` + Hyperlink string `json:"hyperlink"` + HyperlinkType string `json:"hyperlink_type"` + Positioning string `json:"positioning"` +} + +// formatShape directly maps the format settings of the shape. +type formatShape struct { + Type string `json:"type"` + Width int `json:"width"` + Height int `json:"height"` + Format formatPicture `json:"format"` + Color formatShapeColor `json:"color"` + Line formatLine `json:"line"` + Paragraph []formatShapeParagraph `json:"paragraph"` +} + +// formatShapeParagraph directly maps the format settings of the paragraph in +// the shape. +type formatShapeParagraph struct { + Font Font `json:"font"` + Text string `json:"text"` +} + +// formatShapeColor directly maps the color settings of the shape. +type formatShapeColor struct { + Line string `json:"line"` + Fill string `json:"fill"` + Effect string `json:"effect"` +} + +// formatLine directly maps the line settings of the shape. +type formatLine struct { + Width float64 `json:"width"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlPivotCache.go b/vendor/github.com/xuri/excelize/v2/xmlPivotCache.go new file mode 100644 index 00000000..0af7c44d --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlPivotCache.go @@ -0,0 +1,228 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxPivotCacheDefinition represents the pivotCacheDefinition part. This part +// defines each field in the source data, including the name, the string +// resources of the instance data (for shared items), and information about +// the type of data that appears in the field. +type xlsxPivotCacheDefinition struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotCacheDefinition"` + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` + Invalid bool `xml:"invalid,attr,omitempty"` + SaveData bool `xml:"saveData,attr"` + RefreshOnLoad bool `xml:"refreshOnLoad,attr,omitempty"` + OptimizeMemory bool `xml:"optimizeMemory,attr,omitempty"` + EnableRefresh bool `xml:"enableRefresh,attr,omitempty"` + RefreshedBy string `xml:"refreshedBy,attr,omitempty"` + RefreshedDate float64 `xml:"refreshedDate,attr,omitempty"` + RefreshedDateIso float64 `xml:"refreshedDateIso,attr,omitempty"` + BackgroundQuery bool `xml:"backgroundQuery,attr"` + MissingItemsLimit int `xml:"missingItemsLimit,attr,omitempty"` + CreatedVersion int `xml:"createdVersion,attr,omitempty"` + RefreshedVersion int `xml:"refreshedVersion,attr,omitempty"` + MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"` + RecordCount int `xml:"recordCount,attr,omitempty"` + UpgradeOnRefresh bool `xml:"upgradeOnRefresh,attr,omitempty"` + TupleCacheAttr bool `xml:"tupleCache,attr,omitempty"` + SupportSubquery bool `xml:"supportSubquery,attr,omitempty"` + SupportAdvancedDrill bool `xml:"supportAdvancedDrill,attr,omitempty"` + CacheSource *xlsxCacheSource `xml:"cacheSource"` + CacheFields *xlsxCacheFields `xml:"cacheFields"` + CacheHierarchies *xlsxCacheHierarchies `xml:"cacheHierarchies"` + Kpis *xlsxKpis `xml:"kpis"` + TupleCache *xlsxTupleCache `xml:"tupleCache"` + CalculatedItems *xlsxCalculatedItems `xml:"calculatedItems"` + CalculatedMembers *xlsxCalculatedMembers `xml:"calculatedMembers"` + Dimensions *xlsxDimensions `xml:"dimensions"` + MeasureGroups *xlsxMeasureGroups `xml:"measureGroups"` + Maps *xlsxMaps `xml:"maps"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxCacheSource represents the description of data source whose data is +// stored in the pivot cache. The data source refers to the underlying rows or +// database records that provide the data for a PivotTable. You can create a +// PivotTable report from a SpreadsheetML table, an external database +// (including OLAP cubes), multiple SpreadsheetML worksheets, or another +// PivotTable. +type xlsxCacheSource struct { + Type string `xml:"type,attr"` + ConnectionID int `xml:"connectionId,attr,omitempty"` + WorksheetSource *xlsxWorksheetSource `xml:"worksheetSource"` + Consolidation *xlsxConsolidation `xml:"consolidation"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxWorksheetSource represents the location of the source of the data that +// is stored in the cache. +type xlsxWorksheetSource struct { + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` + Ref string `xml:"ref,attr,omitempty"` + Name string `xml:"name,attr,omitempty"` + Sheet string `xml:"sheet,attr,omitempty"` +} + +// xlsxConsolidation represents the description of the PivotCache source using +// multiple consolidation ranges. This element is used when the source of the +// PivotTable is a collection of ranges in the workbook. The ranges are +// specified in the rangeSets collection. The logic for how the application +// consolidates the data in the ranges is application- defined. +type xlsxConsolidation struct{} + +// xlsxCacheFields represents the collection of field definitions in the +// source data. +type xlsxCacheFields struct { + Count int `xml:"count,attr"` + CacheField []*xlsxCacheField `xml:"cacheField"` +} + +// xlsxCacheField represent a single field in the PivotCache. This definition +// contains information about the field, such as its source, data type, and +// location within a level or hierarchy. The sharedItems element stores +// additional information about the data in this field. If there are no shared +// items, then values are stored directly in the pivotCacheRecords part. +type xlsxCacheField struct { + Name string `xml:"name,attr"` + Caption string `xml:"caption,attr,omitempty"` + PropertyName string `xml:"propertyName,attr,omitempty"` + ServerField bool `xml:"serverField,attr,omitempty"` + UniqueList bool `xml:"uniqueList,attr,omitempty"` + NumFmtID int `xml:"numFmtId,attr"` + Formula string `xml:"formula,attr,omitempty"` + SQLType int `xml:"sqlType,attr,omitempty"` + Hierarchy int `xml:"hierarchy,attr,omitempty"` + Level int `xml:"level,attr,omitempty"` + DatabaseField bool `xml:"databaseField,attr,omitempty"` + MappingCount int `xml:"mappingCount,attr,omitempty"` + MemberPropertyField bool `xml:"memberPropertyField,attr,omitempty"` + SharedItems *xlsxSharedItems `xml:"sharedItems"` + FieldGroup *xlsxFieldGroup `xml:"fieldGroup"` + MpMap *xlsxX `xml:"mpMap"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxSharedItems represents the collection of unique items for a field in +// the PivotCacheDefinition. The sharedItems complex type stores data type and +// formatting information about the data in a field. Items in the +// PivotCacheDefinition can be shared in order to reduce the redundancy of +// those values that are referenced in multiple places across all the +// PivotTable parts. +type xlsxSharedItems struct { + ContainsSemiMixedTypes bool `xml:"containsSemiMixedTypes,attr,omitempty"` + ContainsNonDate bool `xml:"containsNonDate,attr,omitempty"` + ContainsDate bool `xml:"containsDate,attr,omitempty"` + ContainsString bool `xml:"containsString,attr,omitempty"` + ContainsBlank bool `xml:"containsBlank,attr,omitempty"` + ContainsMixedTypes bool `xml:"containsMixedTypes,attr,omitempty"` + ContainsNumber bool `xml:"containsNumber,attr,omitempty"` + ContainsInteger bool `xml:"containsInteger,attr,omitempty"` + MinValue float64 `xml:"minValue,attr,omitempty"` + MaxValue float64 `xml:"maxValue,attr,omitempty"` + MinDate string `xml:"minDate,attr,omitempty"` + MaxDate string `xml:"maxDate,attr,omitempty"` + Count int `xml:"count,attr"` + LongText bool `xml:"longText,attr,omitempty"` + M *xlsxMissing `xml:"m"` + N *xlsxNumber `xml:"n"` + B *xlsxBoolean `xml:"b"` + E *xlsxError `xml:"e"` + S *xlsxString `xml:"s"` + D *xlsxDateTime `xml:"d"` +} + +// xlsxMissing represents a value that was not specified. +type xlsxMissing struct{} + +// xlsxNumber represents a numeric value in the PivotTable. +type xlsxNumber struct { + V float64 `xml:"v,attr"` + U bool `xml:"u,attr,omitempty"` + F bool `xml:"f,attr,omitempty"` + C string `xml:"c,attr,omitempty"` + Cp int `xml:"cp,attr,omitempty"` + In int `xml:"in,attr,omitempty"` + Bc string `xml:"bc,attr,omitempty"` + Fc string `xml:"fc,attr,omitempty"` + I bool `xml:"i,attr,omitempty"` + Un bool `xml:"un,attr,omitempty"` + St bool `xml:"st,attr,omitempty"` + B bool `xml:"b,attr,omitempty"` + Tpls *xlsxTuples `xml:"tpls"` + X *attrValInt `xml:"x"` +} + +// xlsxTuples represents members for the OLAP sheet data entry, also known as +// a tuple. +type xlsxTuples struct{} + +// xlsxBoolean represents a boolean value for an item in the PivotTable. +type xlsxBoolean struct{} + +// xlsxError represents an error value. The use of this item indicates that an +// error value is present in the PivotTable source. The error is recorded in +// the value attribute. +type xlsxError struct{} + +// xlsxString represents a character value in a PivotTable. +type xlsxString struct { + V string `xml:"v,attr"` + U bool `xml:"u,attr,omitempty"` + F bool `xml:"f,attr,omitempty"` + C string `xml:"c,attr,omitempty"` + Cp int `xml:"cp,attr,omitempty"` + In int `xml:"in,attr,omitempty"` + Bc string `xml:"bc,attr,omitempty"` + Fc string `xml:"fc,attr,omitempty"` + I bool `xml:"i,attr,omitempty"` + Un bool `xml:"un,attr,omitempty"` + St bool `xml:"st,attr,omitempty"` + B bool `xml:"b,attr,omitempty"` + Tpls *xlsxTuples `xml:"tpls"` + X *attrValInt `xml:"x"` +} + +// xlsxDateTime represents a date-time value in the PivotTable. +type xlsxDateTime struct{} + +// xlsxFieldGroup represents the collection of properties for a field group. +type xlsxFieldGroup struct{} + +// xlsxCacheHierarchies represents the collection of OLAP hierarchies in the +// PivotCache. +type xlsxCacheHierarchies struct{} + +// xlsxKpis represents the collection of Key Performance Indicators (KPIs) +// defined on the OLAP server and stored in the PivotCache. +type xlsxKpis struct{} + +// xlsxTupleCache represents the cache of OLAP sheet data members, or tuples. +type xlsxTupleCache struct{} + +// xlsxCalculatedItems represents the collection of calculated items. +type xlsxCalculatedItems struct{} + +// xlsxCalculatedMembers represents the collection of calculated members in an +// OLAP PivotTable. +type xlsxCalculatedMembers struct{} + +// xlsxDimensions represents the collection of PivotTable OLAP dimensions. +type xlsxDimensions struct{} + +// xlsxMeasureGroups represents the collection of PivotTable OLAP measure +// groups. +type xlsxMeasureGroups struct{} + +// xlsxMaps represents the PivotTable OLAP measure group - Dimension maps. +type xlsxMaps struct{} diff --git a/vendor/github.com/xuri/excelize/v2/xmlPivotTable.go b/vendor/github.com/xuri/excelize/v2/xmlPivotTable.go new file mode 100644 index 00000000..897669ba --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlPivotTable.go @@ -0,0 +1,293 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxPivotTableDefinition represents the PivotTable root element for +// non-null PivotTables. There exists one pivotTableDefinition for each +// PivotTableDefinition part +type xlsxPivotTableDefinition struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotTableDefinition"` + Name string `xml:"name,attr"` + CacheID int `xml:"cacheId,attr"` + ApplyNumberFormats bool `xml:"applyNumberFormats,attr,omitempty"` + ApplyBorderFormats bool `xml:"applyBorderFormats,attr,omitempty"` + ApplyFontFormats bool `xml:"applyFontFormats,attr,omitempty"` + ApplyPatternFormats bool `xml:"applyPatternFormats,attr,omitempty"` + ApplyAlignmentFormats bool `xml:"applyAlignmentFormats,attr,omitempty"` + ApplyWidthHeightFormats bool `xml:"applyWidthHeightFormats,attr,omitempty"` + DataOnRows bool `xml:"dataOnRows,attr,omitempty"` + DataPosition int `xml:"dataPosition,attr,omitempty"` + DataCaption string `xml:"dataCaption,attr"` + GrandTotalCaption string `xml:"grandTotalCaption,attr,omitempty"` + ErrorCaption string `xml:"errorCaption,attr,omitempty"` + ShowError *bool `xml:"showError,attr"` + MissingCaption string `xml:"missingCaption,attr,omitempty"` + ShowMissing bool `xml:"showMissing,attr,omitempty"` + PageStyle string `xml:"pageStyle,attr,omitempty"` + PivotTableStyle string `xml:"pivotTableStyle,attr,omitempty"` + VacatedStyle string `xml:"vacatedStyle,attr,omitempty"` + Tag string `xml:"tag,attr,omitempty"` + UpdatedVersion int `xml:"updatedVersion,attr,omitempty"` + MinRefreshableVersion int `xml:"minRefreshableVersion,attr,omitempty"` + AsteriskTotals bool `xml:"asteriskTotals,attr,omitempty"` + ShowItems bool `xml:"showItems,attr,omitempty"` + EditData bool `xml:"editData,attr,omitempty"` + DisableFieldList bool `xml:"disableFieldList,attr,omitempty"` + ShowCalcMbrs bool `xml:"showCalcMbrs,attr,omitempty"` + VisualTotals bool `xml:"visualTotals,attr,omitempty"` + ShowMultipleLabel bool `xml:"showMultipleLabel,attr,omitempty"` + ShowDataDropDown bool `xml:"showDataDropDown,attr,omitempty"` + ShowDrill *bool `xml:"showDrill,attr"` + PrintDrill bool `xml:"printDrill,attr,omitempty"` + ShowMemberPropertyTips bool `xml:"showMemberPropertyTips,attr,omitempty"` + ShowDataTips bool `xml:"showDataTips,attr,omitempty"` + EnableWizard bool `xml:"enableWizard,attr,omitempty"` + EnableDrill bool `xml:"enableDrill,attr,omitempty"` + EnableFieldProperties bool `xml:"enableFieldProperties,attr,omitempty"` + PreserveFormatting bool `xml:"preserveFormatting,attr,omitempty"` + UseAutoFormatting *bool `xml:"useAutoFormatting,attr,omitempty"` + PageWrap int `xml:"pageWrap,attr,omitempty"` + PageOverThenDown *bool `xml:"pageOverThenDown,attr,omitempty"` + SubtotalHiddenItems bool `xml:"subtotalHiddenItems,attr,omitempty"` + RowGrandTotals *bool `xml:"rowGrandTotals,attr,omitempty"` + ColGrandTotals *bool `xml:"colGrandTotals,attr,omitempty"` + FieldPrintTitles bool `xml:"fieldPrintTitles,attr,omitempty"` + ItemPrintTitles bool `xml:"itemPrintTitles,attr,omitempty"` + MergeItem *bool `xml:"mergeItem,attr,omitempty"` + ShowDropZones bool `xml:"showDropZones,attr,omitempty"` + CreatedVersion int `xml:"createdVersion,attr,omitempty"` + Indent int `xml:"indent,attr,omitempty"` + ShowEmptyRow bool `xml:"showEmptyRow,attr,omitempty"` + ShowEmptyCol bool `xml:"showEmptyCol,attr,omitempty"` + ShowHeaders bool `xml:"showHeaders,attr,omitempty"` + Compact *bool `xml:"compact,attr"` + Outline *bool `xml:"outline,attr"` + OutlineData bool `xml:"outlineData,attr,omitempty"` + CompactData *bool `xml:"compactData,attr,omitempty"` + Published bool `xml:"published,attr,omitempty"` + GridDropZones bool `xml:"gridDropZones,attr,omitempty"` + Immersive bool `xml:"immersive,attr,omitempty"` + MultipleFieldFilters bool `xml:"multipleFieldFilters,attr,omitempty"` + ChartFormat int `xml:"chartFormat,attr,omitempty"` + RowHeaderCaption string `xml:"rowHeaderCaption,attr,omitempty"` + ColHeaderCaption string `xml:"colHeaderCaption,attr,omitempty"` + FieldListSortAscending bool `xml:"fieldListSortAscending,attr,omitempty"` + MdxSubqueries bool `xml:"mdxSubqueries,attr,omitempty"` + CustomListSort bool `xml:"customListSort,attr,omitempty"` + Location *xlsxLocation `xml:"location"` + PivotFields *xlsxPivotFields `xml:"pivotFields"` + RowFields *xlsxRowFields `xml:"rowFields"` + RowItems *xlsxRowItems `xml:"rowItems"` + ColFields *xlsxColFields `xml:"colFields"` + ColItems *xlsxColItems `xml:"colItems"` + PageFields *xlsxPageFields `xml:"pageFields"` + DataFields *xlsxDataFields `xml:"dataFields"` + ConditionalFormats *xlsxConditionalFormats `xml:"conditionalFormats"` + PivotTableStyleInfo *xlsxPivotTableStyleInfo `xml:"pivotTableStyleInfo"` +} + +// xlsxLocation represents location information for the PivotTable. +type xlsxLocation struct { + Ref string `xml:"ref,attr"` + FirstHeaderRow int `xml:"firstHeaderRow,attr"` + FirstDataRow int `xml:"firstDataRow,attr"` + FirstDataCol int `xml:"firstDataCol,attr"` + RowPageCount int `xml:"rowPageCount,attr,omitempty"` + ColPageCount int `xml:"colPageCount,attr,omitempty"` +} + +// xlsxPivotFields represents the collection of fields that appear on the +// PivotTable. +type xlsxPivotFields struct { + Count int `xml:"count,attr"` + PivotField []*xlsxPivotField `xml:"pivotField"` +} + +// xlsxPivotField represents a single field in the PivotTable. This element +// contains information about the field, including the collection of items in +// the field. +type xlsxPivotField struct { + Name string `xml:"name,attr,omitempty"` + Axis string `xml:"axis,attr,omitempty"` + DataField bool `xml:"dataField,attr,omitempty"` + SubtotalCaption string `xml:"subtotalCaption,attr,omitempty"` + ShowDropDowns bool `xml:"showDropDowns,attr,omitempty"` + HiddenLevel bool `xml:"hiddenLevel,attr,omitempty"` + UniqueMemberProperty string `xml:"uniqueMemberProperty,attr,omitempty"` + Compact *bool `xml:"compact,attr"` + AllDrilled bool `xml:"allDrilled,attr,omitempty"` + NumFmtID string `xml:"numFmtId,attr,omitempty"` + Outline *bool `xml:"outline,attr"` + SubtotalTop bool `xml:"subtotalTop,attr,omitempty"` + DragToRow bool `xml:"dragToRow,attr,omitempty"` + DragToCol bool `xml:"dragToCol,attr,omitempty"` + MultipleItemSelectionAllowed bool `xml:"multipleItemSelectionAllowed,attr,omitempty"` + DragToPage bool `xml:"dragToPage,attr,omitempty"` + DragToData bool `xml:"dragToData,attr,omitempty"` + DragOff bool `xml:"dragOff,attr,omitempty"` + ShowAll bool `xml:"showAll,attr"` + InsertBlankRow bool `xml:"insertBlankRow,attr,omitempty"` + ServerField bool `xml:"serverField,attr,omitempty"` + InsertPageBreak bool `xml:"insertPageBreak,attr,omitempty"` + AutoShow bool `xml:"autoShow,attr,omitempty"` + TopAutoShow bool `xml:"topAutoShow,attr,omitempty"` + HideNewItems bool `xml:"hideNewItems,attr,omitempty"` + MeasureFilter bool `xml:"measureFilter,attr,omitempty"` + IncludeNewItemsInFilter bool `xml:"includeNewItemsInFilter,attr,omitempty"` + ItemPageCount int `xml:"itemPageCount,attr,omitempty"` + SortType string `xml:"sortType,attr,omitempty"` + DataSourceSort bool `xml:"dataSourceSort,attr,omitempty"` + NonAutoSortDefault bool `xml:"nonAutoSortDefault,attr,omitempty"` + RankBy int `xml:"rankBy,attr,omitempty"` + DefaultSubtotal *bool `xml:"defaultSubtotal,attr,omitempty"` + SumSubtotal bool `xml:"sumSubtotal,attr,omitempty"` + CountASubtotal bool `xml:"countASubtotal,attr,omitempty"` + AvgSubtotal bool `xml:"avgSubtotal,attr,omitempty"` + MaxSubtotal bool `xml:"maxSubtotal,attr,omitempty"` + MinSubtotal bool `xml:"minSubtotal,attr,omitempty"` + ProductSubtotal bool `xml:"productSubtotal,attr,omitempty"` + CountSubtotal bool `xml:"countSubtotal,attr,omitempty"` + StdDevSubtotal bool `xml:"stdDevSubtotal,attr,omitempty"` + StdDevPSubtotal bool `xml:"stdDevPSubtotal,attr,omitempty"` + VarSubtotal bool `xml:"varSubtotal,attr,omitempty"` + VarPSubtotal bool `xml:"varPSubtotal,attr,omitempty"` + ShowPropCell bool `xml:"showPropCell,attr,omitempty"` + ShowPropTip bool `xml:"showPropTip,attr,omitempty"` + ShowPropAsCaption bool `xml:"showPropAsCaption,attr,omitempty"` + DefaultAttributeDrillState bool `xml:"defaultAttributeDrillState,attr,omitempty"` + Items *xlsxItems `xml:"items"` + AutoSortScope *xlsxAutoSortScope `xml:"autoSortScope"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxItems represents the collection of items in a PivotTable field. The +// items in the collection are ordered by index. Items represent the unique +// entries from the field in the source data. +type xlsxItems struct { + Count int `xml:"count,attr"` + Item []*xlsxItem `xml:"item"` +} + +// xlsxItem represents a single item in PivotTable field. +type xlsxItem struct { + N string `xml:"n,attr,omitempty"` + T string `xml:"t,attr,omitempty"` + H bool `xml:"h,attr,omitempty"` + S bool `xml:"s,attr,omitempty"` + SD bool `xml:"sd,attr,omitempty"` + F bool `xml:"f,attr,omitempty"` + M bool `xml:"m,attr,omitempty"` + C bool `xml:"c,attr,omitempty"` + X *int `xml:"x,attr,omitempty"` + D bool `xml:"d,attr,omitempty"` + E bool `xml:"e,attr,omitempty"` +} + +// xlsxAutoSortScope represents the sorting scope for the PivotTable. +type xlsxAutoSortScope struct{} + +// xlsxRowFields represents the collection of row fields for the PivotTable. +type xlsxRowFields struct { + Count int `xml:"count,attr"` + Field []*xlsxField `xml:"field"` +} + +// xlsxField represents a generic field that can appear either on the column +// or the row region of the PivotTable. There areas many elements as there +// are item values in any particular column or row. +type xlsxField struct { + X int `xml:"x,attr"` +} + +// xlsxRowItems represents the collection of items in row axis of the +// PivotTable. +type xlsxRowItems struct { + Count int `xml:"count,attr"` + I []*xlsxI `xml:"i"` +} + +// xlsxI represents the collection of items in the row region of the +// PivotTable. +type xlsxI struct { + X []*xlsxX `xml:"x"` +} + +// xlsxX represents an array of indexes to cached shared item values. +type xlsxX struct{} + +// xlsxColFields represents the collection of fields that are on the column +// axis of the PivotTable. +type xlsxColFields struct { + Count int `xml:"count,attr"` + Field []*xlsxField `xml:"field"` +} + +// xlsxColItems represents the collection of column items of the PivotTable. +type xlsxColItems struct { + Count int `xml:"count,attr"` + I []*xlsxI `xml:"i"` +} + +// xlsxPageFields represents the collection of items in the page or report +// filter region of the PivotTable. +type xlsxPageFields struct { + Count int `xml:"count,attr"` + PageField []*xlsxPageField `xml:"pageField"` +} + +// xlsxPageField represents a field on the page or report filter of the +// PivotTable. +type xlsxPageField struct { + Fld int `xml:"fld,attr"` + Item int `xml:"item,attr,omitempty"` + Hier int `xml:"hier,attr,omitempty"` + Name string `xml:"name,attr,omitempty"` + Cap string `xml:"cap,attr,omitempty"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxDataFields represents the collection of items in the data region of the +// PivotTable. +type xlsxDataFields struct { + Count int `xml:"count,attr"` + DataField []*xlsxDataField `xml:"dataField"` +} + +// xlsxDataField represents a field from a source list, table, or database +// that contains data that is summarized in a PivotTable. +type xlsxDataField struct { + Name string `xml:"name,attr,omitempty"` + Fld int `xml:"fld,attr"` + Subtotal string `xml:"subtotal,attr,omitempty"` + ShowDataAs string `xml:"showDataAs,attr,omitempty"` + BaseField int `xml:"baseField,attr,omitempty"` + BaseItem int64 `xml:"baseItem,attr,omitempty"` + NumFmtID string `xml:"numFmtId,attr,omitempty"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxConditionalFormats represents the collection of conditional formats +// applied to a PivotTable. +type xlsxConditionalFormats struct{} + +// xlsxPivotTableStyleInfo represent information on style applied to the +// PivotTable. +type xlsxPivotTableStyleInfo struct { + Name string `xml:"name,attr"` + ShowRowHeaders bool `xml:"showRowHeaders,attr"` + ShowColHeaders bool `xml:"showColHeaders,attr"` + ShowRowStripes bool `xml:"showRowStripes,attr,omitempty"` + ShowColStripes bool `xml:"showColStripes,attr,omitempty"` + ShowLastColumn bool `xml:"showLastColumn,attr,omitempty"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlSharedStrings.go b/vendor/github.com/xuri/excelize/v2/xmlSharedStrings.go new file mode 100644 index 00000000..683105e3 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlSharedStrings.go @@ -0,0 +1,87 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxSST directly maps the sst element from the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may +// be stored directly inside spreadsheet cell elements; however, storing the +// same value inside multiple cell elements can result in very large worksheet +// Parts, possibly resulting in performance degradation. The Shared String Table +// is an indexed list of string values, shared across the workbook, which allows +// implementations to store values only once. +type xlsxSST struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main sst"` + Count int `xml:"count,attr"` + UniqueCount int `xml:"uniqueCount,attr"` + SI []xlsxSI `xml:"si"` +} + +// xlsxSI (String Item) is the representation of an individual string in the +// Shared String table. If the string is just a simple string with formatting +// applied at the cell level, then the String Item (si) should contain a +// single text element used to express the string. However, if the string in +// the cell is more complex - i.e., has formatting applied at the character +// level - then the string item shall consist of multiple rich text runs which +// collectively are used to express the string. +type xlsxSI struct { + T *xlsxT `xml:"t,omitempty"` + R []xlsxR `xml:"r"` + RPh []*xlsxPhoneticRun `xml:"rPh"` + PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"` +} + +// xlsxR represents a run of rich text. A rich text run is a region of text +// that share a common set of properties, such as formatting properties. The +// properties are defined in the rPr element, and the text displayed to the +// user is defined in the Text (t) element. +type xlsxR struct { + RPr *xlsxRPr `xml:"rPr"` + T *xlsxT `xml:"t"` +} + +// xlsxT directly maps the t element in the run properties. +type xlsxT struct { + XMLName xml.Name `xml:"t"` + Space xml.Attr `xml:"space,attr,omitempty"` + Val string `xml:",chardata"` +} + +// xlsxRPr (Run Properties) specifies a set of run properties which shall be +// applied to the contents of the parent run after all style formatting has been +// applied to the text. These properties are defined as direct formatting, since +// they are directly applied to the run and supersede any formatting from +// styles. +type xlsxRPr struct { + RFont *attrValString `xml:"rFont"` + Charset *attrValInt `xml:"charset"` + Family *attrValInt `xml:"family"` + B *string `xml:"b"` + I *string `xml:"i"` + Strike *string `xml:"strike"` + Outline *string `xml:"outline"` + Shadow *string `xml:"shadow"` + Condense *string `xml:"condense"` + Extend *string `xml:"extend"` + Color *xlsxColor `xml:"color"` + Sz *attrValFloat `xml:"sz"` + U *attrValString `xml:"u"` + VertAlign *attrValString `xml:"vertAlign"` + Scheme *attrValString `xml:"scheme"` +} + +// RichTextRun directly maps the settings of the rich text run. +type RichTextRun struct { + Font *Font + Text string +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlStyles.go b/vendor/github.com/xuri/excelize/v2/xmlStyles.go new file mode 100644 index 00000000..71fe9a66 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlStyles.go @@ -0,0 +1,372 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/xml" + "sync" +) + +// xlsxStyleSheet is the root element of the Styles part. +type xlsxStyleSheet struct { + sync.Mutex + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main styleSheet"` + NumFmts *xlsxNumFmts `xml:"numFmts"` + Fonts *xlsxFonts `xml:"fonts"` + Fills *xlsxFills `xml:"fills"` + Borders *xlsxBorders `xml:"borders"` + CellStyleXfs *xlsxCellStyleXfs `xml:"cellStyleXfs"` + CellXfs *xlsxCellXfs `xml:"cellXfs"` + CellStyles *xlsxCellStyles `xml:"cellStyles"` + Dxfs *xlsxDxfs `xml:"dxfs"` + TableStyles *xlsxTableStyles `xml:"tableStyles"` + Colors *xlsxStyleColors `xml:"colors"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxAlignment formatting information pertaining to text alignment in cells. +// There are a variety of choices for how text is aligned both horizontally and +// vertically, as well as indentation settings, and so on. +type xlsxAlignment struct { + Horizontal string `xml:"horizontal,attr,omitempty"` + Indent int `xml:"indent,attr,omitempty"` + JustifyLastLine bool `xml:"justifyLastLine,attr,omitempty"` + ReadingOrder uint64 `xml:"readingOrder,attr,omitempty"` + RelativeIndent int `xml:"relativeIndent,attr,omitempty"` + ShrinkToFit bool `xml:"shrinkToFit,attr,omitempty"` + TextRotation int `xml:"textRotation,attr,omitempty"` + Vertical string `xml:"vertical,attr,omitempty"` + WrapText bool `xml:"wrapText,attr,omitempty"` +} + +// xlsxProtection (Protection Properties) contains protection properties +// associated with the cell. Each cell has protection properties that can be +// set. The cell protection properties do not take effect unless the sheet has +// been protected. +type xlsxProtection struct { + Hidden *bool `xml:"hidden,attr"` + Locked *bool `xml:"locked,attr"` +} + +// xlsxLine expresses a single set of cell border. +type xlsxLine struct { + Style string `xml:"style,attr,omitempty"` + Color *xlsxColor `xml:"color"` +} + +// xlsxColor is a common mapping used for both the fgColor and bgColor elements. +// Foreground color of the cell fill pattern. Cell fill patterns operate with +// two colors: a background color and a foreground color. These combine together +// to make a patterned cell fill. Background color of the cell fill pattern. +// Cell fill patterns operate with two colors: a background color and a +// foreground color. These combine together to make a patterned cell fill. +type xlsxColor struct { + Auto bool `xml:"auto,attr,omitempty"` + RGB string `xml:"rgb,attr,omitempty"` + Indexed int `xml:"indexed,attr,omitempty"` + Theme *int `xml:"theme,attr"` + Tint float64 `xml:"tint,attr,omitempty"` +} + +// xlsxFonts directly maps the font element. This element contains all font +// definitions for this workbook. +type xlsxFonts struct { + Count int `xml:"count,attr"` + Font []*xlsxFont `xml:"font"` +} + +// xlsxFont directly maps the font element. This element defines the +// properties for one of the fonts used in this workbook. +type xlsxFont struct { + B *attrValBool `xml:"b"` + I *attrValBool `xml:"i"` + Strike *attrValBool `xml:"strike"` + Outline *attrValBool `xml:"outline"` + Shadow *attrValBool `xml:"shadow"` + Condense *attrValBool `xml:"condense"` + Extend *attrValBool `xml:"extend"` + U *attrValString `xml:"u"` + Sz *attrValFloat `xml:"sz"` + Color *xlsxColor `xml:"color"` + Name *attrValString `xml:"name"` + Family *attrValInt `xml:"family"` + Charset *attrValInt `xml:"charset"` + Scheme *attrValString `xml:"scheme"` +} + +// xlsxFills directly maps the fills element. This element defines the cell +// fills portion of the Styles part, consisting of a sequence of fill records. A +// cell fill consists of a background color, foreground color, and pattern to be +// applied across the cell. +type xlsxFills struct { + Count int `xml:"count,attr"` + Fill []*xlsxFill `xml:"fill"` +} + +// xlsxFill directly maps the fill element. This element specifies fill +// formatting. +type xlsxFill struct { + PatternFill *xlsxPatternFill `xml:"patternFill"` + GradientFill *xlsxGradientFill `xml:"gradientFill"` +} + +// xlsxPatternFill is used to specify cell fill information for pattern and +// solid color cell fills. For solid cell fills (no pattern), fgColor is used. +// For cell fills with patterns specified, then the cell fill color is +// specified by the bgColor element. +type xlsxPatternFill struct { + PatternType string `xml:"patternType,attr,omitempty"` + FgColor *xlsxColor `xml:"fgColor"` + BgColor *xlsxColor `xml:"bgColor"` +} + +// xlsxGradientFill defines a gradient-style cell fill. Gradient cell fills can +// use one or two colors as the end points of color interpolation. +type xlsxGradientFill struct { + Bottom float64 `xml:"bottom,attr,omitempty"` + Degree float64 `xml:"degree,attr,omitempty"` + Left float64 `xml:"left,attr,omitempty"` + Right float64 `xml:"right,attr,omitempty"` + Top float64 `xml:"top,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + Stop []*xlsxGradientFillStop `xml:"stop"` +} + +// xlsxGradientFillStop directly maps the stop element. +type xlsxGradientFillStop struct { + Position float64 `xml:"position,attr"` + Color xlsxColor `xml:"color,omitempty"` +} + +// xlsxBorders directly maps the borders element. This element contains borders +// formatting information, specifying all border definitions for all cells in +// the workbook. +type xlsxBorders struct { + Count int `xml:"count,attr"` + Border []*xlsxBorder `xml:"border"` +} + +// xlsxBorder directly maps the border element. Expresses a single set of cell +// border formats (left, right, top, bottom, diagonal). Color is optional. When +// missing, 'automatic' is implied. +type xlsxBorder struct { + DiagonalDown bool `xml:"diagonalDown,attr,omitempty"` + DiagonalUp bool `xml:"diagonalUp,attr,omitempty"` + Outline bool `xml:"outline,attr,omitempty"` + Left xlsxLine `xml:"left,omitempty"` + Right xlsxLine `xml:"right,omitempty"` + Top xlsxLine `xml:"top,omitempty"` + Bottom xlsxLine `xml:"bottom,omitempty"` + Diagonal xlsxLine `xml:"diagonal,omitempty"` +} + +// xlsxCellStyles directly maps the cellStyles element. This element contains +// the named cell styles, consisting of a sequence of named style records. A +// named cell style is a collection of direct or themed formatting (e.g., cell +// border, cell fill, and font type/size/style) grouped together into a single +// named style, and can be applied to a cell. +type xlsxCellStyles struct { + XMLName xml.Name `xml:"cellStyles"` + Count int `xml:"count,attr"` + CellStyle []*xlsxCellStyle `xml:"cellStyle"` +} + +// xlsxCellStyle directly maps the cellStyle element. This element represents +// the name and related formatting records for a named cell style in this +// workbook. +type xlsxCellStyle struct { + XMLName xml.Name `xml:"cellStyle"` + Name string `xml:"name,attr"` + XfID int `xml:"xfId,attr"` + BuiltInID *int `xml:"builtinId,attr"` + ILevel *int `xml:"iLevel,attr"` + Hidden *bool `xml:"hidden,attr"` + CustomBuiltIn *bool `xml:"customBuiltin,attr"` +} + +// xlsxCellStyleXfs directly maps the cellStyleXfs element. This element +// contains the master formatting records (xf's) which define the formatting for +// all named cell styles in this workbook. Master formatting records reference +// individual elements of formatting (e.g., number format, font definitions, +// cell fills, etc.) by specifying a zero-based index into those collections. +// Master formatting records also specify whether to apply or ignore particular +// aspects of formatting. +type xlsxCellStyleXfs struct { + Count int `xml:"count,attr"` + Xf []xlsxXf `xml:"xf,omitempty"` +} + +// xlsxXf directly maps the xf element. A single xf element describes all of the +// formatting for a cell. +type xlsxXf struct { + NumFmtID *int `xml:"numFmtId,attr"` + FontID *int `xml:"fontId,attr"` + FillID *int `xml:"fillId,attr"` + BorderID *int `xml:"borderId,attr"` + XfID *int `xml:"xfId,attr"` + QuotePrefix *bool `xml:"quotePrefix,attr"` + PivotButton *bool `xml:"pivotButton,attr"` + ApplyNumberFormat *bool `xml:"applyNumberFormat,attr"` + ApplyFont *bool `xml:"applyFont,attr"` + ApplyFill *bool `xml:"applyFill,attr"` + ApplyBorder *bool `xml:"applyBorder,attr"` + ApplyAlignment *bool `xml:"applyAlignment,attr"` + ApplyProtection *bool `xml:"applyProtection,attr"` + Alignment *xlsxAlignment `xml:"alignment"` + Protection *xlsxProtection `xml:"protection"` +} + +// xlsxCellXfs directly maps the cellXfs element. This element contains the +// master formatting records (xf) which define the formatting applied to cells +// in this workbook. These records are the starting point for determining the +// formatting for a cell. Cells in the Sheet Part reference the xf records by +// zero-based index. +type xlsxCellXfs struct { + Count int `xml:"count,attr"` + Xf []xlsxXf `xml:"xf,omitempty"` +} + +// xlsxDxfs directly maps the dxfs element. This element contains the master +// differential formatting records (dxf's) which define formatting for all non- +// cell formatting in this workbook. Whereas xf records fully specify a +// particular aspect of formatting (e.g., cell borders) by referencing those +// formatting definitions elsewhere in the Styles part, dxf records specify +// incremental (or differential) aspects of formatting directly inline within +// the dxf element. The dxf formatting is to be applied on top of or in addition +// to any formatting already present on the object using the dxf record. +type xlsxDxfs struct { + Count int `xml:"count,attr"` + Dxfs []*xlsxDxf `xml:"dxf"` +} + +// xlsxDxf directly maps the dxf element. A single dxf record, expressing +// incremental formatting to be applied. +type xlsxDxf struct { + Dxf string `xml:",innerxml"` +} + +// dxf directly maps the dxf element. +type dxf struct { + Font *xlsxFont `xml:"font"` + NumFmt *xlsxNumFmt `xml:"numFmt"` + Fill *xlsxFill `xml:"fill"` + Alignment *xlsxAlignment `xml:"alignment"` + Border *xlsxBorder `xml:"border"` + Protection *xlsxProtection `xml:"protection"` + ExtLst *xlsxExt `xml:"extLst"` +} + +// xlsxTableStyles directly maps the tableStyles element. This element +// represents a collection of Table style definitions for Table styles and +// PivotTable styles used in this workbook. It consists of a sequence of +// tableStyle records, each defining a single Table style. +type xlsxTableStyles struct { + Count int `xml:"count,attr"` + DefaultPivotStyle string `xml:"defaultPivotStyle,attr"` + DefaultTableStyle string `xml:"defaultTableStyle,attr"` + TableStyles []*xlsxTableStyle `xml:"tableStyle"` +} + +// xlsxTableStyle directly maps the tableStyle element. This element represents +// a single table style definition that indicates how a spreadsheet application +// should format and display a table. +type xlsxTableStyle struct { + Name string `xml:"name,attr,omitempty"` + Pivot int `xml:"pivot,attr"` + Count int `xml:"count,attr,omitempty"` + Table bool `xml:"table,attr,omitempty"` + TableStyleElement string `xml:",innerxml"` +} + +// xlsxNumFmts directly maps the numFmts element. This element defines the +// number formats in this workbook, consisting of a sequence of numFmt records, +// where each numFmt record defines a particular number format, indicating how +// to format and render the numeric value of a cell. +type xlsxNumFmts struct { + Count int `xml:"count,attr"` + NumFmt []*xlsxNumFmt `xml:"numFmt"` +} + +// xlsxNumFmt directly maps the numFmt element. This element specifies number +// format properties which indicate how to format and render the numeric value +// of a cell. +type xlsxNumFmt struct { + NumFmtID int `xml:"numFmtId,attr"` + FormatCode string `xml:"formatCode,attr,omitempty"` +} + +// xlsxStyleColors directly maps the colors element. Color information +// associated with this stylesheet. This collection is written whenever the +// legacy color palette has been modified (backwards compatibility settings) or +// a custom color has been selected while using this workbook. +type xlsxStyleColors struct { + Color string `xml:",innerxml"` +} + +// Alignment directly maps the alignment settings of the cells. +type Alignment struct { + Horizontal string `json:"horizontal"` + Indent int `json:"indent"` + JustifyLastLine bool `json:"justify_last_line"` + ReadingOrder uint64 `json:"reading_order"` + RelativeIndent int `json:"relative_indent"` + ShrinkToFit bool `json:"shrink_to_fit"` + TextRotation int `json:"text_rotation"` + Vertical string `json:"vertical"` + WrapText bool `json:"wrap_text"` +} + +// Border directly maps the border settings of the cells. +type Border struct { + Type string `json:"type"` + Color string `json:"color"` + Style int `json:"style"` +} + +// Font directly maps the font settings of the fonts. +type Font struct { + Bold bool `json:"bold"` + Italic bool `json:"italic"` + Underline string `json:"underline"` + Family string `json:"family"` + Size float64 `json:"size"` + Strike bool `json:"strike"` + Color string `json:"color"` +} + +// Fill directly maps the fill settings of the cells. +type Fill struct { + Type string `json:"type"` + Pattern int `json:"pattern"` + Color []string `json:"color"` + Shading int `json:"shading"` +} + +// Protection directly maps the protection settings of the cells. +type Protection struct { + Hidden bool `json:"hidden"` + Locked bool `json:"locked"` +} + +// Style directly maps the style settings of the cells. +type Style struct { + Border []Border `json:"border"` + Fill Fill `json:"fill"` + Font *Font `json:"font"` + Alignment *Alignment `json:"alignment"` + Protection *Protection `json:"protection"` + NumFmt int `json:"number_format"` + DecimalPlaces int `json:"decimal_places"` + CustomNumFmt *string `json:"custom_number_format"` + Lang string `json:"lang"` + NegRed bool `json:"negred"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlTable.go b/vendor/github.com/xuri/excelize/v2/xmlTable.go new file mode 100644 index 00000000..5a56a833 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlTable.go @@ -0,0 +1,217 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxTable directly maps the table element. A table helps organize and provide +// structure to list of information in a worksheet. Tables have clearly labeled +// columns, rows, and data regions. Tables make it easier for users to sort, +// analyze, format, manage, add, and delete information. This element is the +// root element for a table that is not a single cell XML table. +type xlsxTable struct { + XMLName xml.Name `xml:"table"` + XMLNS string `xml:"xmlns,attr"` + DataCellStyle string `xml:"dataCellStyle,attr,omitempty"` + DataDxfID int `xml:"dataDxfId,attr,omitempty"` + DisplayName string `xml:"displayName,attr,omitempty"` + HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"` + HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"` + HeaderRowCount int `xml:"headerRowCount,attr,omitempty"` + HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"` + ID int `xml:"id,attr"` + InsertRow bool `xml:"insertRow,attr,omitempty"` + InsertRowShift bool `xml:"insertRowShift,attr,omitempty"` + Name string `xml:"name,attr"` + Published bool `xml:"published,attr,omitempty"` + Ref string `xml:"ref,attr"` + TotalsRowCount int `xml:"totalsRowCount,attr,omitempty"` + TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"` + TotalsRowShown bool `xml:"totalsRowShown,attr"` + AutoFilter *xlsxAutoFilter `xml:"autoFilter"` + TableColumns *xlsxTableColumns `xml:"tableColumns"` + TableStyleInfo *xlsxTableStyleInfo `xml:"tableStyleInfo"` +} + +// xlsxAutoFilter temporarily hides rows based on a filter criteria, which is +// applied column by column to a table of data in the worksheet. This collection +// expresses AutoFilter settings. +type xlsxAutoFilter struct { + XMLName xml.Name `xml:"autoFilter"` + Ref string `xml:"ref,attr"` + FilterColumn []*xlsxFilterColumn `xml:"filterColumn"` +} + +// xlsxFilterColumn directly maps the filterColumn element. The filterColumn +// collection identifies a particular column in the AutoFilter range and +// specifies filter information that has been applied to this column. If a +// column in the AutoFilter range has no criteria specified, then there is no +// corresponding filterColumn collection expressed for that column. +type xlsxFilterColumn struct { + ColID int `xml:"colId,attr"` + HiddenButton bool `xml:"hiddenButton,attr,omitempty"` + ShowButton bool `xml:"showButton,attr,omitempty"` + CustomFilters *xlsxCustomFilters `xml:"customFilters"` + Filters *xlsxFilters `xml:"filters"` + ColorFilter *xlsxColorFilter `xml:"colorFilter"` + DynamicFilter *xlsxDynamicFilter `xml:"dynamicFilter"` + IconFilter *xlsxIconFilter `xml:"iconFilter"` + Top10 *xlsxTop10 `xml:"top10"` +} + +// xlsxCustomFilters directly maps the customFilters element. When there is more +// than one custom filter criteria to apply (an 'and' or 'or' joining two +// criteria), then this element groups the customFilter elements together. +type xlsxCustomFilters struct { + And bool `xml:"and,attr,omitempty"` + CustomFilter []*xlsxCustomFilter `xml:"customFilter"` +} + +// xlsxCustomFilter directly maps the customFilter element. A custom AutoFilter +// specifies an operator and a value. There can be at most two customFilters +// specified, and in that case the parent element specifies whether the two +// conditions are joined by 'and' or 'or'. For any cells whose values do not +// meet the specified criteria, the corresponding rows shall be hidden from view +// when the filter is applied. +type xlsxCustomFilter struct { + Operator string `xml:"operator,attr,omitempty"` + Val string `xml:"val,attr,omitempty"` +} + +// xlsxFilters directly maps the filters (Filter Criteria) element. When +// multiple values are chosen to filter by, or when a group of date values are +// chosen to filter by, this element groups those criteria together. +type xlsxFilters struct { + Blank bool `xml:"blank,attr,omitempty"` + CalendarType string `xml:"calendarType,attr,omitempty"` + Filter []*xlsxFilter `xml:"filter"` + DateGroupItem []*xlsxDateGroupItem `xml:"dateGroupItem"` +} + +// xlsxFilter directly maps the filter element. This element expresses a filter +// criteria value. +type xlsxFilter struct { + Val string `xml:"val,attr,omitempty"` +} + +// xlsxColorFilter directly maps the colorFilter element. This element specifies +// the color to filter by and whether to use the cell's fill or font color in +// the filter criteria. If the cell's font or fill color does not match the +// color specified in the criteria, the rows corresponding to those cells are +// hidden from view. +type xlsxColorFilter struct { + CellColor bool `xml:"cellColor,attr"` + DxfID int `xml:"dxfId,attr"` +} + +// xlsxDynamicFilter directly maps the dynamicFilter element. This collection +// specifies dynamic filter criteria. These criteria are considered dynamic +// because they can change, either with the data itself (e.g., "above average") +// or with the current system date (e.g., show values for "today"). For any +// cells whose values do not meet the specified criteria, the corresponding rows +// shall be hidden from view when the filter is applied. +type xlsxDynamicFilter struct { + MaxValISO string `xml:"maxValIso,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + Val float64 `xml:"val,attr,omitempty"` + ValISO string `xml:"valIso,attr,omitempty"` +} + +// xlsxIconFilter directly maps the iconFilter element. This element specifies +// the icon set and particular icon within that set to filter by. For any cells +// whose icon does not match the specified criteria, the corresponding rows +// shall be hidden from view when the filter is applied. +type xlsxIconFilter struct { + IconID int `xml:"iconId,attr"` + IconSet string `xml:"iconSet,attr,omitempty"` +} + +// xlsxTop10 directly maps the top10 element. This element specifies the top N +// (percent or number of items) to filter by. +type xlsxTop10 struct { + FilterVal float64 `xml:"filterVal,attr,omitempty"` + Percent bool `xml:"percent,attr,omitempty"` + Top bool `xml:"top,attr"` + Val float64 `xml:"val,attr,omitempty"` +} + +// xlsxDateGroupItem directly maps the dateGroupItem element. This collection is +// used to express a group of dates or times which are used in an AutoFilter +// criteria. [Note: See parent element for an example. end note] Values are +// always written in the calendar type of the first date encountered in the +// filter range, so that all subsequent dates, even when formatted or +// represented by other calendar types, can be correctly compared for the +// purposes of filtering. +type xlsxDateGroupItem struct { + DateTimeGrouping string `xml:"dateTimeGrouping,attr,omitempty"` + Day int `xml:"day,attr,omitempty"` + Hour int `xml:"hour,attr,omitempty"` + Minute int `xml:"minute,attr,omitempty"` + Month int `xml:"month,attr,omitempty"` + Second int `xml:"second,attr,omitempty"` + Year int `xml:"year,attr,omitempty"` +} + +// xlsxTableColumns directly maps the element representing the collection of all +// table columns for this table. +type xlsxTableColumns struct { + Count int `xml:"count,attr"` + TableColumn []*xlsxTableColumn `xml:"tableColumn"` +} + +// xlsxTableColumn directly maps the element representing a single column for +// this table. +type xlsxTableColumn struct { + DataCellStyle string `xml:"dataCellStyle,attr,omitempty"` + DataDxfID int `xml:"dataDxfId,attr,omitempty"` + HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"` + HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"` + ID int `xml:"id,attr"` + Name string `xml:"name,attr"` + QueryTableFieldID int `xml:"queryTableFieldId,attr,omitempty"` + TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"` + TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"` + TotalsRowFunction string `xml:"totalsRowFunction,attr,omitempty"` + TotalsRowLabel string `xml:"totalsRowLabel,attr,omitempty"` + UniqueName string `xml:"uniqueName,attr,omitempty"` +} + +// xlsxTableStyleInfo directly maps the tableStyleInfo element. This element +// describes which style is used to display this table, and specifies which +// portions of the table have the style applied. +type xlsxTableStyleInfo struct { + Name string `xml:"name,attr,omitempty"` + ShowFirstColumn bool `xml:"showFirstColumn,attr"` + ShowLastColumn bool `xml:"showLastColumn,attr"` + ShowRowStripes bool `xml:"showRowStripes,attr"` + ShowColumnStripes bool `xml:"showColumnStripes,attr"` +} + +// formatTable directly maps the format settings of the table. +type formatTable struct { + TableName string `json:"table_name"` + TableStyle string `json:"table_style"` + ShowFirstColumn bool `json:"show_first_column"` + ShowLastColumn bool `json:"show_last_column"` + ShowRowStripes bool `json:"show_row_stripes"` + ShowColumnStripes bool `json:"show_column_stripes"` +} + +// formatAutoFilter directly maps the auto filter settings. +type formatAutoFilter struct { + Column string `json:"column"` + Expression string `json:"expression"` + FilterList []struct { + Column string `json:"column"` + Value []int `json:"value"` + } `json:"filter_list"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlTheme.go b/vendor/github.com/xuri/excelize/v2/xmlTheme.go new file mode 100644 index 00000000..6b9e207c --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlTheme.go @@ -0,0 +1,151 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import "encoding/xml" + +// xlsxTheme directly maps the theme element in the namespace +// http://schemas.openxmlformats.org/drawingml/2006/main +type xlsxTheme struct { + ThemeElements xlsxThemeElements `xml:"themeElements"` + ObjectDefaults xlsxObjectDefaults `xml:"objectDefaults"` + ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"extraClrSchemeLst"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// objectDefaults element allows for the definition of default shape, line, +// and textbox formatting properties. An application can use this information +// to format a shape (or text) initially on insertion into a document. +type xlsxObjectDefaults struct { + ObjectDefaults string `xml:",innerxml"` +} + +// xlsxExtraClrSchemeLst element is a container for the list of extra color +// schemes present in a document. +type xlsxExtraClrSchemeLst struct { + ExtraClrSchemeLst string `xml:",innerxml"` +} + +// xlsxThemeElements directly maps the element defines the theme formatting +// options for the theme and is the workhorse of the theme. This is where the +// bulk of the shared theme information is contained and used by a document. +// This element contains the color scheme, font scheme, and format scheme +// elements which define the different formatting aspects of what a theme +// defines. +type xlsxThemeElements struct { + ClrScheme xlsxClrScheme `xml:"clrScheme"` + FontScheme xlsxFontScheme `xml:"fontScheme"` + FmtScheme xlsxFmtScheme `xml:"fmtScheme"` +} + +// xlsxClrScheme element specifies the theme color, stored in the document's +// Theme part to which the value of this theme color shall be mapped. This +// mapping enables multiple theme colors to be chained together. +type xlsxClrScheme struct { + Name string `xml:"name,attr"` + Children []xlsxClrSchemeEl `xml:",any"` +} + +// xlsxFontScheme element defines the font scheme within the theme. The font +// scheme consists of a pair of major and minor fonts for which to use in a +// document. The major font corresponds well with the heading areas of a +// document, and the minor font corresponds well with the normal text or +// paragraph areas. +type xlsxFontScheme struct { + Name string `xml:"name,attr"` + MajorFont xlsxMajorFont `xml:"majorFont"` + MinorFont xlsxMinorFont `xml:"minorFont"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxMajorFont element defines the set of major fonts which are to be used +// under different languages or locals. +type xlsxMajorFont struct { + Children []xlsxFontSchemeEl `xml:",any"` +} + +// xlsxMinorFont element defines the set of minor fonts that are to be used +// under different languages or locals. +type xlsxMinorFont struct { + Children []xlsxFontSchemeEl `xml:",any"` +} + +// xlsxFmtScheme element contains the background fill styles, effect styles, +// fill styles, and line styles which define the style matrix for a theme. The +// style matrix consists of subtle, moderate, and intense fills, lines, and +// effects. The background fills are not generally thought of to directly be +// associated with the matrix, but do play a role in the style of the overall +// document. Usually, a given object chooses a single line style, a single +// fill style, and a single effect style in order to define the overall final +// look of the object. +type xlsxFmtScheme struct { + Name string `xml:"name,attr"` + FillStyleLst xlsxFillStyleLst `xml:"fillStyleLst"` + LnStyleLst xlsxLnStyleLst `xml:"lnStyleLst"` + EffectStyleLst xlsxEffectStyleLst `xml:"effectStyleLst"` + BgFillStyleLst xlsxBgFillStyleLst `xml:"bgFillStyleLst"` +} + +// xlsxFillStyleLst element defines a set of three fill styles that are used +// within a theme. The three fill styles are arranged in order from subtle to +// moderate to intense. +type xlsxFillStyleLst struct { + FillStyleLst string `xml:",innerxml"` +} + +// xlsxLnStyleLst element defines a list of three line styles for use within a +// theme. The three line styles are arranged in order from subtle to moderate +// to intense versions of lines. This list makes up part of the style matrix. +type xlsxLnStyleLst struct { + LnStyleLst string `xml:",innerxml"` +} + +// xlsxEffectStyleLst element defines a set of three effect styles that create +// the effect style list for a theme. The effect styles are arranged in order +// of subtle to moderate to intense. +type xlsxEffectStyleLst struct { + EffectStyleLst string `xml:",innerxml"` +} + +// xlsxBgFillStyleLst element defines a list of background fills that are +// used within a theme. The background fills consist of three fills, arranged +// in order from subtle to moderate to intense. +type xlsxBgFillStyleLst struct { + BgFillStyleLst string `xml:",innerxml"` +} + +// xlsxClrScheme specifies the theme color, stored in the document's Theme +// part to which the value of this theme color shall be mapped. This mapping +// enables multiple theme colors to be chained together. +type xlsxClrSchemeEl struct { + XMLName xml.Name + SysClr *xlsxSysClr `xml:"sysClr"` + SrgbClr *attrValString `xml:"srgbClr"` +} + +// xlsxFontSchemeEl directly maps the major and minor font of the style's font +// scheme. +type xlsxFontSchemeEl struct { + XMLName xml.Name + Script string `xml:"script,attr,omitempty"` + Typeface string `xml:"typeface,attr"` + Panose string `xml:"panose,attr,omitempty"` + PitchFamily string `xml:"pitchFamily,attr,omitempty"` + Charset string `xml:"charset,attr,omitempty"` +} + +// xlsxSysClr element specifies a color bound to predefined operating system +// elements. +type xlsxSysClr struct { + Val string `xml:"val,attr"` + LastClr string `xml:"lastClr,attr"` +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlWorkbook.go b/vendor/github.com/xuri/excelize/v2/xmlWorkbook.go new file mode 100644 index 00000000..a0fce15f --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlWorkbook.go @@ -0,0 +1,315 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/xml" + "sync" +) + +// xlsxRelationships describe references from parts to other internal resources in the package or to external resources. +type xlsxRelationships struct { + sync.Mutex + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"` + Relationships []xlsxRelationship `xml:"Relationship"` +} + +// xlsxRelationship contains relations which maps id and XML. +type xlsxRelationship struct { + ID string `xml:"Id,attr"` + Target string `xml:",attr"` + Type string `xml:",attr"` + TargetMode string `xml:",attr,omitempty"` +} + +// xlsxWorkbook contains elements and attributes that encompass the data +// content of the workbook. The workbook's child elements each have their own +// subclause references. +type xlsxWorkbook struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"` + Conformance string `xml:"conformance,attr,omitempty"` + FileVersion *xlsxFileVersion `xml:"fileVersion"` + FileSharing *xlsxExtLst `xml:"fileSharing"` + WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"` + AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"` + DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` + WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"` + BookViews *xlsxBookViews `xml:"bookViews"` + Sheets xlsxSheets `xml:"sheets"` + FunctionGroups *xlsxExtLst `xml:"functionGroups"` + ExternalReferences *xlsxExternalReferences `xml:"externalReferences"` + DefinedNames *xlsxDefinedNames `xml:"definedNames"` + CalcPr *xlsxCalcPr `xml:"calcPr"` + OleSize *xlsxExtLst `xml:"oleSize"` + CustomWorkbookViews *xlsxCustomWorkbookViews `xml:"customWorkbookViews"` + PivotCaches *xlsxPivotCaches `xml:"pivotCaches"` + SmartTagPr *xlsxExtLst `xml:"smartTagPr"` + SmartTagTypes *xlsxExtLst `xml:"smartTagTypes"` + WebPublishing *xlsxExtLst `xml:"webPublishing"` + FileRecoveryPr *xlsxFileRecoveryPr `xml:"fileRecoveryPr"` + WebPublishObjects *xlsxExtLst `xml:"webPublishObjects"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxFileRecoveryPr maps sheet recovery information. This element defines +// properties that track the state of the workbook file, such as whether the +// file was saved during a crash, or whether it should be opened in auto-recover +// mode. +type xlsxFileRecoveryPr struct { + AutoRecover bool `xml:"autoRecover,attr,omitempty"` + CrashSave bool `xml:"crashSave,attr,omitempty"` + DataExtractLoad bool `xml:"dataExtractLoad,attr,omitempty"` + RepairLoad bool `xml:"repairLoad,attr,omitempty"` +} + +// xlsxWorkbookProtection directly maps the workbookProtection element. This +// element specifies options for protecting data in the workbook. Applications +// might use workbook protection to prevent anyone from accidentally changing, +// moving, or deleting important data. This protection can be ignored by +// applications which choose not to support this optional protection mechanism. +// When a password is to be hashed and stored in this element, it shall be +// hashed as defined below, starting from a UTF-16LE encoded string value. If +// there is a leading BOM character (U+FEFF) in the encoded password it is +// removed before hash calculation. +type xlsxWorkbookProtection struct { + LockRevision bool `xml:"lockRevision,attr,omitempty"` + LockStructure bool `xml:"lockStructure,attr,omitempty"` + LockWindows bool `xml:"lockWindows,attr,omitempty"` + RevisionsAlgorithmName string `xml:"revisionsAlgorithmName,attr,omitempty"` + RevisionsHashValue string `xml:"revisionsHashValue,attr,omitempty"` + RevisionsSaltValue string `xml:"revisionsSaltValue,attr,omitempty"` + RevisionsSpinCount int `xml:"revisionsSpinCount,attr,omitempty"` + WorkbookAlgorithmName string `xml:"workbookAlgorithmName,attr,omitempty"` + WorkbookHashValue string `xml:"workbookHashValue,attr,omitempty"` + WorkbookSaltValue string `xml:"workbookSaltValue,attr,omitempty"` + WorkbookSpinCount int `xml:"workbookSpinCount,attr,omitempty"` +} + +// xlsxFileVersion directly maps the fileVersion element. This element defines +// properties that track which version of the application accessed the data and +// source code contained in the file. +type xlsxFileVersion struct { + AppName string `xml:"appName,attr,omitempty"` + CodeName string `xml:"codeName,attr,omitempty"` + LastEdited string `xml:"lastEdited,attr,omitempty"` + LowestEdited string `xml:"lowestEdited,attr,omitempty"` + RupBuild string `xml:"rupBuild,attr,omitempty"` +} + +// xlsxWorkbookPr directly maps the workbookPr element from the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element +// defines a collection of workbook properties. +type xlsxWorkbookPr struct { + Date1904 bool `xml:"date1904,attr,omitempty"` + ShowObjects string `xml:"showObjects,attr,omitempty"` + ShowBorderUnselectedTables *bool `xml:"showBorderUnselectedTables,attr"` + FilterPrivacy bool `xml:"filterPrivacy,attr,omitempty"` + PromptedSolutions bool `xml:"promptedSolutions,attr,omitempty"` + ShowInkAnnotation *bool `xml:"showInkAnnotation,attr"` + BackupFile bool `xml:"backupFile,attr,omitempty"` + SaveExternalLinkValues *bool `xml:"saveExternalLinkValues,attr"` + UpdateLinks string `xml:"updateLinks,attr,omitempty"` + CodeName string `xml:"codeName,attr,omitempty"` + HidePivotFieldList bool `xml:"hidePivotFieldList,attr,omitempty"` + ShowPivotChartFilter bool `xml:"showPivotChartFilter,attr,omitempty"` + AllowRefreshQuery bool `xml:"allowRefreshQuery,attr,omitempty"` + PublishItems bool `xml:"publishItems,attr,omitempty"` + CheckCompatibility bool `xml:"checkCompatibility,attr,omitempty"` + AutoCompressPictures *bool `xml:"autoCompressPictures,attr"` + RefreshAllConnections bool `xml:"refreshAllConnections,attr,omitempty"` + DefaultThemeVersion string `xml:"defaultThemeVersion,attr,omitempty"` +} + +// xlsxBookViews directly maps the bookViews element. This element specifies the +// collection of workbook views of the enclosing workbook. Each view can specify +// a window position, filter options, and other configurations. There is no +// limit on the number of workbook views that can be defined for a workbook. +type xlsxBookViews struct { + WorkBookView []xlsxWorkBookView `xml:"workbookView"` +} + +// xlsxWorkBookView directly maps the workbookView element from the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element +// specifies a single Workbook view. +type xlsxWorkBookView struct { + Visibility string `xml:"visibility,attr,omitempty"` + Minimized bool `xml:"minimized,attr,omitempty"` + ShowHorizontalScroll *bool `xml:"showHorizontalScroll,attr"` + ShowVerticalScroll *bool `xml:"showVerticalScroll,attr"` + ShowSheetTabs *bool `xml:"showSheetTabs,attr"` + XWindow string `xml:"xWindow,attr,omitempty"` + YWindow string `xml:"yWindow,attr,omitempty"` + WindowWidth int `xml:"windowWidth,attr,omitempty"` + WindowHeight int `xml:"windowHeight,attr,omitempty"` + TabRatio int `xml:"tabRatio,attr,omitempty"` + FirstSheet int `xml:"firstSheet,attr,omitempty"` + ActiveTab int `xml:"activeTab,attr,omitempty"` + AutoFilterDateGrouping *bool `xml:"autoFilterDateGrouping,attr"` +} + +// xlsxSheets directly maps the sheets element from the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main. +type xlsxSheets struct { + Sheet []xlsxSheet `xml:"sheet"` +} + +// xlsxSheet defines a sheet in this workbook. Sheet data is stored in a +// separate part. +type xlsxSheet struct { + Name string `xml:"name,attr,omitempty"` + SheetID int `xml:"sheetId,attr,omitempty"` + ID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr"` + State string `xml:"state,attr,omitempty"` +} + +// xlsxExternalReferences directly maps the externalReferences element of the +// external workbook references part. +type xlsxExternalReferences struct { + ExternalReference []xlsxExternalReference `xml:"externalReference"` +} + +// xlsxExternalReference directly maps the externalReference element of the +// external workbook references part. +type xlsxExternalReference struct { + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` +} + +// xlsxPivotCaches element enumerates pivot cache definition parts used by pivot +// tables and formulas in this workbook. +type xlsxPivotCaches struct { + PivotCache []xlsxPivotCache `xml:"pivotCache"` +} + +// xlsxPivotCache directly maps the pivotCache element. +type xlsxPivotCache struct { + CacheID int `xml:"cacheId,attr"` + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` +} + +// extLst element provides a convention for extending spreadsheetML in +// predefined locations. The locations shall be denoted with the extLst element, +// and are called extension lists. Extension list locations within the markup +// document are specified in the markup specification and can be used to store +// extensions to the markup specification, whether those are future version +// extensions of the markup specification or are private extensions implemented +// independently from the markup specification. Markup within an extension might +// not be understood by a consumer. +type xlsxExtLst struct { + Ext string `xml:",innerxml"` +} + +// xlsxDefinedNames directly maps the definedNames element. This element defines +// the collection of defined names for this workbook. Defined names are +// descriptive names to represent cells, ranges of cells, formulas, or constant +// values. Defined names can be used to represent a range on any worksheet. +type xlsxDefinedNames struct { + DefinedName []xlsxDefinedName `xml:"definedName"` +} + +// xlsxDefinedName directly maps the definedName element from the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element +// defines a defined name within this workbook. A defined name is descriptive +// text that is used to represents a cell, range of cells, formula, or constant +// value. For a descriptions of the attributes see https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname +type xlsxDefinedName struct { + Comment string `xml:"comment,attr,omitempty"` + CustomMenu string `xml:"customMenu,attr,omitempty"` + Description string `xml:"description,attr,omitempty"` + Function bool `xml:"function,attr,omitempty"` + FunctionGroupID int `xml:"functionGroupId,attr,omitempty"` + Help string `xml:"help,attr,omitempty"` + Hidden bool `xml:"hidden,attr,omitempty"` + LocalSheetID *int `xml:"localSheetId,attr"` + Name string `xml:"name,attr,omitempty"` + PublishToServer bool `xml:"publishToServer,attr,omitempty"` + ShortcutKey string `xml:"shortcutKey,attr,omitempty"` + StatusBar string `xml:"statusBar,attr,omitempty"` + VbProcedure bool `xml:"vbProcedure,attr,omitempty"` + WorkbookParameter bool `xml:"workbookParameter,attr,omitempty"` + Xlm bool `xml:"xml,attr,omitempty"` + Data string `xml:",chardata"` +} + +// xlsxCalcPr directly maps the calcPr element. This element defines the +// collection of properties the application uses to record calculation status +// and details. Calculation is the process of computing formulas and then +// displaying the results as values in the cells that contain the formulas. +type xlsxCalcPr struct { + CalcCompleted bool `xml:"calcCompleted,attr,omitempty"` + CalcID string `xml:"calcId,attr,omitempty"` + CalcMode string `xml:"calcMode,attr,omitempty"` + CalcOnSave bool `xml:"calcOnSave,attr,omitempty"` + ConcurrentCalc *bool `xml:"concurrentCalc,attr"` + ConcurrentManualCount int `xml:"concurrentManualCount,attr,omitempty"` + ForceFullCalc bool `xml:"forceFullCalc,attr,omitempty"` + FullCalcOnLoad bool `xml:"fullCalcOnLoad,attr,omitempty"` + FullPrecision bool `xml:"fullPrecision,attr,omitempty"` + Iterate bool `xml:"iterate,attr,omitempty"` + IterateCount int `xml:"iterateCount,attr,omitempty"` + IterateDelta float64 `xml:"iterateDelta,attr,omitempty"` + RefMode string `xml:"refMode,attr,omitempty"` +} + +// xlsxCustomWorkbookViews defines the collection of custom workbook views that +// are defined for this workbook. A customWorkbookView is similar in concept to +// a workbookView in that its attributes contain settings related to the way +// that the workbook should be displayed on a screen by a spreadsheet +// application. +type xlsxCustomWorkbookViews struct { + CustomWorkbookView []xlsxCustomWorkbookView `xml:"customWorkbookView"` +} + +// xlsxCustomWorkbookView directly maps the customWorkbookView element. This +// element specifies a single custom workbook view. A custom workbook view +// consists of a set of display and print settings that you can name and apply +// to a workbook. You can create more than one custom workbook view of the same +// workbook. Custom Workbook Views are not required in order to construct a +// valid SpreadsheetML document, and are not necessary if the document is never +// displayed by a spreadsheet application, or if the spreadsheet application has +// a fixed display for workbooks. However, if a spreadsheet application chooses +// to implement configurable display modes, the customWorkbookView element +// should be used to persist the settings for those display modes. +type xlsxCustomWorkbookView struct { + ActiveSheetID *int `xml:"activeSheetId,attr"` + AutoUpdate *bool `xml:"autoUpdate,attr"` + ChangesSavedWin *bool `xml:"changesSavedWin,attr"` + GUID *string `xml:"guid,attr"` + IncludeHiddenRowCol *bool `xml:"includeHiddenRowCol,attr"` + IncludePrintSettings *bool `xml:"includePrintSettings,attr"` + Maximized *bool `xml:"maximized,attr"` + MergeInterval int `xml:"mergeInterval,attr"` + Minimized *bool `xml:"minimized,attr"` + Name *string `xml:"name,attr"` + OnlySync *bool `xml:"onlySync,attr"` + PersonalView *bool `xml:"personalView,attr"` + ShowComments *string `xml:"showComments,attr"` + ShowFormulaBar *bool `xml:"showFormulaBar,attr"` + ShowHorizontalScroll *bool `xml:"showHorizontalScroll,attr"` + ShowObjects *string `xml:"showObjects,attr"` + ShowSheetTabs *bool `xml:"showSheetTabs,attr"` + ShowStatusbar *bool `xml:"showStatusbar,attr"` + ShowVerticalScroll *bool `xml:"showVerticalScroll,attr"` + TabRatio *int `xml:"tabRatio,attr"` + WindowHeight *int `xml:"windowHeight,attr"` + WindowWidth *int `xml:"windowWidth,attr"` + XWindow *int `xml:"xWindow,attr"` + YWindow *int `xml:"yWindow,attr"` +} + +// DefinedName directly maps the name for a cell or cell range on a +// worksheet. +type DefinedName struct { + Name string + Comment string + RefersTo string + Scope string +} diff --git a/vendor/github.com/xuri/excelize/v2/xmlWorksheet.go b/vendor/github.com/xuri/excelize/v2/xmlWorksheet.go new file mode 100644 index 00000000..eb855c53 --- /dev/null +++ b/vendor/github.com/xuri/excelize/v2/xmlWorksheet.go @@ -0,0 +1,895 @@ +// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.15 or later. + +package excelize + +import ( + "encoding/xml" + "sync" +) + +// xlsxWorksheet directly maps the worksheet element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main. +type xlsxWorksheet struct { + sync.Mutex + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"` + SheetPr *xlsxSheetPr `xml:"sheetPr"` + Dimension *xlsxDimension `xml:"dimension"` + SheetViews *xlsxSheetViews `xml:"sheetViews"` + SheetFormatPr *xlsxSheetFormatPr `xml:"sheetFormatPr"` + Cols *xlsxCols `xml:"cols"` + SheetData xlsxSheetData `xml:"sheetData"` + SheetCalcPr *xlsxInnerXML `xml:"sheetCalcPr"` + SheetProtection *xlsxSheetProtection `xml:"sheetProtection"` + ProtectedRanges *xlsxInnerXML `xml:"protectedRanges"` + Scenarios *xlsxInnerXML `xml:"scenarios"` + AutoFilter *xlsxAutoFilter `xml:"autoFilter"` + SortState *xlsxSortState `xml:"sortState"` + DataConsolidate *xlsxInnerXML `xml:"dataConsolidate"` + CustomSheetViews *xlsxCustomSheetViews `xml:"customSheetViews"` + MergeCells *xlsxMergeCells `xml:"mergeCells"` + PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"` + ConditionalFormatting []*xlsxConditionalFormatting `xml:"conditionalFormatting"` + DataValidations *xlsxDataValidations `xml:"dataValidations"` + Hyperlinks *xlsxHyperlinks `xml:"hyperlinks"` + PrintOptions *xlsxPrintOptions `xml:"printOptions"` + PageMargins *xlsxPageMargins `xml:"pageMargins"` + PageSetUp *xlsxPageSetUp `xml:"pageSetup"` + HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"` + RowBreaks *xlsxBreaks `xml:"rowBreaks"` + ColBreaks *xlsxBreaks `xml:"colBreaks"` + CustomProperties *xlsxInnerXML `xml:"customProperties"` + CellWatches *xlsxInnerXML `xml:"cellWatches"` + IgnoredErrors *xlsxInnerXML `xml:"ignoredErrors"` + SmartTags *xlsxInnerXML `xml:"smartTags"` + Drawing *xlsxDrawing `xml:"drawing"` + LegacyDrawing *xlsxLegacyDrawing `xml:"legacyDrawing"` + LegacyDrawingHF *xlsxLegacyDrawingHF `xml:"legacyDrawingHF"` + DrawingHF *xlsxDrawingHF `xml:"drawingHF"` + Picture *xlsxPicture `xml:"picture"` + OleObjects *xlsxInnerXML `xml:"oleObjects"` + Controls *xlsxInnerXML `xml:"controls"` + WebPublishItems *xlsxInnerXML `xml:"webPublishItems"` + AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"` + TableParts *xlsxTableParts `xml:"tableParts"` + ExtLst *xlsxExtLst `xml:"extLst"` + DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` +} + +// xlsxDrawing change r:id to rid in the namespace. +type xlsxDrawing struct { + XMLName xml.Name `xml:"drawing"` + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` +} + +// xlsxHeaderFooter directly maps the headerFooter element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - When printed or +// viewed in page layout view (§18.18.69), each page of a worksheet can have a +// page header, a page footer, or both. The headers and footers on odd-numbered +// pages can differ from those on even-numbered pages, and the headers and +// footers on the first page can differ from those on odd- and even-numbered +// pages. In the latter case, the first page is not considered an odd page. +type xlsxHeaderFooter struct { + XMLName xml.Name `xml:"headerFooter"` + AlignWithMargins bool `xml:"alignWithMargins,attr,omitempty"` + DifferentFirst bool `xml:"differentFirst,attr,omitempty"` + DifferentOddEven bool `xml:"differentOddEven,attr,omitempty"` + ScaleWithDoc bool `xml:"scaleWithDoc,attr,omitempty"` + OddHeader string `xml:"oddHeader,omitempty"` + OddFooter string `xml:"oddFooter,omitempty"` + EvenHeader string `xml:"evenHeader,omitempty"` + EvenFooter string `xml:"evenFooter,omitempty"` + FirstFooter string `xml:"firstFooter,omitempty"` + FirstHeader string `xml:"firstHeader,omitempty"` + DrawingHF *xlsxDrawingHF `xml:"drawingHF"` +} + +// xlsxDrawingHF (Drawing Reference in Header Footer) specifies the usage of +// drawing objects to be rendered in the headers and footers of the sheet. It +// specifies an explicit relationship to the part containing the DrawingML +// shapes used in the headers and footers. It also indicates where in the +// headers and footers each shape belongs. One drawing object can appear in +// each of the left section, center section and right section of a header and +// a footer. +type xlsxDrawingHF struct { + Content string `xml:",innerxml"` +} + +// xlsxPageSetUp directly maps the pageSetup element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page setup +// settings for the worksheet. +type xlsxPageSetUp struct { + XMLName xml.Name `xml:"pageSetup"` + BlackAndWhite bool `xml:"blackAndWhite,attr,omitempty"` + CellComments string `xml:"cellComments,attr,omitempty"` + Copies int `xml:"copies,attr,omitempty"` + Draft bool `xml:"draft,attr,omitempty"` + Errors string `xml:"errors,attr,omitempty"` + FirstPageNumber string `xml:"firstPageNumber,attr,omitempty"` + FitToHeight *int `xml:"fitToHeight,attr"` + FitToWidth *int `xml:"fitToWidth,attr"` + HorizontalDPI string `xml:"horizontalDpi,attr,omitempty"` + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` + Orientation string `xml:"orientation,attr,omitempty"` + PageOrder string `xml:"pageOrder,attr,omitempty"` + PaperHeight string `xml:"paperHeight,attr,omitempty"` + PaperSize *int `xml:"paperSize,attr"` + PaperWidth string `xml:"paperWidth,attr,omitempty"` + Scale int `xml:"scale,attr,omitempty"` + UseFirstPageNumber bool `xml:"useFirstPageNumber,attr,omitempty"` + UsePrinterDefaults bool `xml:"usePrinterDefaults,attr,omitempty"` + VerticalDPI string `xml:"verticalDpi,attr,omitempty"` +} + +// xlsxPrintOptions directly maps the printOptions element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Print options for +// the sheet. Printer-specific settings are stored separately in the Printer +// Settings part. +type xlsxPrintOptions struct { + XMLName xml.Name `xml:"printOptions"` + GridLines bool `xml:"gridLines,attr,omitempty"` + GridLinesSet bool `xml:"gridLinesSet,attr,omitempty"` + Headings bool `xml:"headings,attr,omitempty"` + HorizontalCentered bool `xml:"horizontalCentered,attr,omitempty"` + VerticalCentered bool `xml:"verticalCentered,attr,omitempty"` +} + +// xlsxPageMargins directly maps the pageMargins element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page margins for +// a sheet or a custom sheet view. +type xlsxPageMargins struct { + XMLName xml.Name `xml:"pageMargins"` + Bottom float64 `xml:"bottom,attr"` + Footer float64 `xml:"footer,attr"` + Header float64 `xml:"header,attr"` + Left float64 `xml:"left,attr"` + Right float64 `xml:"right,attr"` + Top float64 `xml:"top,attr"` +} + +// xlsxSheetFormatPr directly maps the sheetFormatPr element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main. This element +// specifies the sheet formatting properties. +type xlsxSheetFormatPr struct { + XMLName xml.Name `xml:"sheetFormatPr"` + BaseColWidth uint8 `xml:"baseColWidth,attr,omitempty"` + DefaultColWidth float64 `xml:"defaultColWidth,attr,omitempty"` + DefaultRowHeight float64 `xml:"defaultRowHeight,attr"` + CustomHeight bool `xml:"customHeight,attr,omitempty"` + ZeroHeight bool `xml:"zeroHeight,attr,omitempty"` + ThickTop bool `xml:"thickTop,attr,omitempty"` + ThickBottom bool `xml:"thickBottom,attr,omitempty"` + OutlineLevelRow uint8 `xml:"outlineLevelRow,attr,omitempty"` + OutlineLevelCol uint8 `xml:"outlineLevelCol,attr,omitempty"` +} + +// xlsxSheetViews represents worksheet views collection. +type xlsxSheetViews struct { + XMLName xml.Name `xml:"sheetViews"` + SheetView []xlsxSheetView `xml:"sheetView"` +} + +// xlsxSheetView represents a single sheet view definition. When more than one +// sheet view is defined in the file, it means that when opening the workbook, +// each sheet view corresponds to a separate window within the spreadsheet +// application, where each window is showing the particular sheet containing +// the same workbookViewId value, the last sheetView definition is loaded, and +// the others are discarded. When multiple windows are viewing the same sheet, +// multiple sheetView elements (with corresponding workbookView entries) are +// saved. +type xlsxSheetView struct { + WindowProtection bool `xml:"windowProtection,attr,omitempty"` + ShowFormulas bool `xml:"showFormulas,attr,omitempty"` + ShowGridLines *bool `xml:"showGridLines,attr"` + ShowRowColHeaders *bool `xml:"showRowColHeaders,attr"` + ShowZeros *bool `xml:"showZeros,attr,omitempty"` + RightToLeft bool `xml:"rightToLeft,attr,omitempty"` + TabSelected bool `xml:"tabSelected,attr,omitempty"` + ShowRuler *bool `xml:"showRuler,attr,omitempty"` + ShowWhiteSpace *bool `xml:"showWhiteSpace,attr"` + ShowOutlineSymbols bool `xml:"showOutlineSymbols,attr,omitempty"` + DefaultGridColor *bool `xml:"defaultGridColor,attr"` + View string `xml:"view,attr,omitempty"` + TopLeftCell string `xml:"topLeftCell,attr,omitempty"` + ColorID int `xml:"colorId,attr,omitempty"` + ZoomScale float64 `xml:"zoomScale,attr,omitempty"` + ZoomScaleNormal float64 `xml:"zoomScaleNormal,attr,omitempty"` + ZoomScalePageLayoutView float64 `xml:"zoomScalePageLayoutView,attr,omitempty"` + ZoomScaleSheetLayoutView float64 `xml:"zoomScaleSheetLayoutView,attr,omitempty"` + WorkbookViewID int `xml:"workbookViewId,attr"` + Pane *xlsxPane `xml:"pane,omitempty"` + Selection []*xlsxSelection `xml:"selection"` +} + +// xlsxSelection directly maps the selection element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Worksheet view +// selection. +type xlsxSelection struct { + ActiveCell string `xml:"activeCell,attr,omitempty"` + ActiveCellID *int `xml:"activeCellId,attr"` + Pane string `xml:"pane,attr,omitempty"` + SQRef string `xml:"sqref,attr,omitempty"` +} + +// xlsxSelection directly maps the selection element. Worksheet view pane. +type xlsxPane struct { + ActivePane string `xml:"activePane,attr,omitempty"` + State string `xml:"state,attr,omitempty"` // Either "split" or "frozen" + TopLeftCell string `xml:"topLeftCell,attr,omitempty"` + XSplit float64 `xml:"xSplit,attr,omitempty"` + YSplit float64 `xml:"ySplit,attr,omitempty"` +} + +// xlsxSheetPr directly maps the sheetPr element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Sheet-level +// properties. +type xlsxSheetPr struct { + XMLName xml.Name `xml:"sheetPr"` + SyncHorizontal bool `xml:"syncHorizontal,attr,omitempty"` + SyncVertical bool `xml:"syncVertical,attr,omitempty"` + SyncRef string `xml:"syncRef,attr,omitempty"` + TransitionEvaluation bool `xml:"transitionEvaluation,attr,omitempty"` + TransitionEntry bool `xml:"transitionEntry,attr,omitempty"` + Published *bool `xml:"published,attr"` + CodeName string `xml:"codeName,attr,omitempty"` + FilterMode bool `xml:"filterMode,attr,omitempty"` + EnableFormatConditionsCalculation *bool `xml:"enableFormatConditionsCalculation,attr"` + TabColor *xlsxTabColor `xml:"tabColor"` + OutlinePr *xlsxOutlinePr `xml:"outlinePr"` + PageSetUpPr *xlsxPageSetUpPr `xml:"pageSetUpPr"` +} + +// xlsxOutlinePr maps to the outlinePr element. SummaryBelow allows you to +// adjust the direction of grouper controls. +type xlsxOutlinePr struct { + ApplyStyles *bool `xml:"applyStyles,attr"` + SummaryBelow bool `xml:"summaryBelow,attr"` + SummaryRight bool `xml:"summaryRight,attr"` + ShowOutlineSymbols bool `xml:"showOutlineSymbols,attr"` +} + +// xlsxPageSetUpPr expresses page setup properties of the worksheet. +type xlsxPageSetUpPr struct { + AutoPageBreaks bool `xml:"autoPageBreaks,attr,omitempty"` + FitToPage bool `xml:"fitToPage,attr,omitempty"` +} + +// xlsxTabColor represents background color of the sheet tab. +type xlsxTabColor struct { + Auto bool `xml:"auto,attr,omitempty"` + Indexed int `xml:"indexed,attr,omitempty"` + RGB string `xml:"rgb,attr,omitempty"` + Theme int `xml:"theme,attr,omitempty"` + Tint float64 `xml:"tint,attr,omitempty"` +} + +// xlsxCols defines column width and column formatting for one or more columns +// of the worksheet. +type xlsxCols struct { + XMLName xml.Name `xml:"cols"` + Col []xlsxCol `xml:"col"` +} + +// xlsxCol directly maps the col (Column Width & Formatting). Defines column +// width and column formatting for one or more columns of the worksheet. +type xlsxCol struct { + BestFit bool `xml:"bestFit,attr,omitempty"` + Collapsed bool `xml:"collapsed,attr,omitempty"` + CustomWidth bool `xml:"customWidth,attr,omitempty"` + Hidden bool `xml:"hidden,attr,omitempty"` + Max int `xml:"max,attr"` + Min int `xml:"min,attr"` + OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"` + Phonetic bool `xml:"phonetic,attr,omitempty"` + Style int `xml:"style,attr,omitempty"` + Width float64 `xml:"width,attr,omitempty"` +} + +// xlsxDimension directly maps the dimension element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - This element +// specifies the used range of the worksheet. It specifies the row and column +// bounds of used cells in the worksheet. This is optional and is not +// required. Used cells include cells with formulas, text content, and cell +// formatting. When an entire column is formatted, only the first cell in that +// column is considered used. +type xlsxDimension struct { + XMLName xml.Name `xml:"dimension"` + Ref string `xml:"ref,attr"` +} + +// xlsxSheetData collection represents the cell table itself. This collection +// expresses information about each cell, grouped together by rows in the +// worksheet. +type xlsxSheetData struct { + XMLName xml.Name `xml:"sheetData"` + Row []xlsxRow `xml:"row"` +} + +// xlsxRow directly maps the row element. The element expresses information +// about an entire row of a worksheet, and contains all cell definitions for a +// particular row in the worksheet. +type xlsxRow struct { + C []xlsxC `xml:"c"` + R int `xml:"r,attr,omitempty"` + Spans string `xml:"spans,attr,omitempty"` + S int `xml:"s,attr,omitempty"` + CustomFormat bool `xml:"customFormat,attr,omitempty"` + Ht float64 `xml:"ht,attr,omitempty"` + Hidden bool `xml:"hidden,attr,omitempty"` + CustomHeight bool `xml:"customHeight,attr,omitempty"` + OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"` + Collapsed bool `xml:"collapsed,attr,omitempty"` + ThickTop bool `xml:"thickTop,attr,omitempty"` + ThickBot bool `xml:"thickBot,attr,omitempty"` + Ph bool `xml:"ph,attr,omitempty"` +} + +// xlsxSortState directly maps the sortState element. This collection +// preserves the AutoFilter sort state. +type xlsxSortState struct { + ColumnSort bool `xml:"columnSort,attr,omitempty"` + CaseSensitive bool `xml:"caseSensitive,attr,omitempty"` + SortMethod string `xml:"sortMethod,attr,omitempty"` + Ref string `xml:"ref,attr"` + Content string `xml:",innerxml"` +} + +// xlsxCustomSheetViews directly maps the customSheetViews element. This is a +// collection of custom sheet views. +type xlsxCustomSheetViews struct { + XMLName xml.Name `xml:"customSheetViews"` + CustomSheetView []*xlsxCustomSheetView `xml:"customSheetView"` +} + +// xlsxBrk directly maps the row or column break to use when paginating a +// worksheet. +type xlsxBrk struct { + ID int `xml:"id,attr,omitempty"` + Min int `xml:"min,attr,omitempty"` + Max int `xml:"max,attr,omitempty"` + Man bool `xml:"man,attr,omitempty"` + Pt bool `xml:"pt,attr,omitempty"` +} + +// xlsxBreaks directly maps a collection of the row or column breaks. +type xlsxBreaks struct { + Brk []*xlsxBrk `xml:"brk"` + Count int `xml:"count,attr,omitempty"` + ManualBreakCount int `xml:"manualBreakCount,attr,omitempty"` +} + +// xlsxCustomSheetView directly maps the customSheetView element. +type xlsxCustomSheetView struct { + Pane *xlsxPane `xml:"pane"` + Selection *xlsxSelection `xml:"selection"` + RowBreaks *xlsxBreaks `xml:"rowBreaks"` + ColBreaks *xlsxBreaks `xml:"colBreaks"` + PageMargins *xlsxPageMargins `xml:"pageMargins"` + PrintOptions *xlsxPrintOptions `xml:"printOptions"` + PageSetup *xlsxPageSetUp `xml:"pageSetup"` + HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"` + AutoFilter *xlsxAutoFilter `xml:"autoFilter"` + ExtLst *xlsxExtLst `xml:"extLst"` + GUID string `xml:"guid,attr"` + Scale int `xml:"scale,attr,omitempty"` + ColorID int `xml:"colorId,attr,omitempty"` + ShowPageBreaks bool `xml:"showPageBreaks,attr,omitempty"` + ShowFormulas bool `xml:"showFormulas,attr,omitempty"` + ShowGridLines bool `xml:"showGridLines,attr,omitempty"` + ShowRowCol bool `xml:"showRowCol,attr,omitempty"` + OutlineSymbols bool `xml:"outlineSymbols,attr,omitempty"` + ZeroValues bool `xml:"zeroValues,attr,omitempty"` + FitToPage bool `xml:"fitToPage,attr,omitempty"` + PrintArea bool `xml:"printArea,attr,omitempty"` + Filter bool `xml:"filter,attr,omitempty"` + ShowAutoFilter bool `xml:"showAutoFilter,attr,omitempty"` + HiddenRows bool `xml:"hiddenRows,attr,omitempty"` + HiddenColumns bool `xml:"hiddenColumns,attr,omitempty"` + State string `xml:"state,attr,omitempty"` + FilterUnique bool `xml:"filterUnique,attr,omitempty"` + View string `xml:"view,attr,omitempty"` + ShowRuler bool `xml:"showRuler,attr,omitempty"` + TopLeftCell string `xml:"topLeftCell,attr,omitempty"` +} + +// xlsxMergeCell directly maps the mergeCell element. A single merged cell. +type xlsxMergeCell struct { + Ref string `xml:"ref,attr,omitempty"` + rect []int +} + +// xlsxMergeCells directly maps the mergeCells element. This collection +// expresses all the merged cells in the sheet. +type xlsxMergeCells struct { + XMLName xml.Name `xml:"mergeCells"` + Count int `xml:"count,attr,omitempty"` + Cells []*xlsxMergeCell `xml:"mergeCell,omitempty"` +} + +// xlsxDataValidations expresses all data validation information for cells in a +// sheet which have data validation features applied. +type xlsxDataValidations struct { + XMLName xml.Name `xml:"dataValidations"` + Count int `xml:"count,attr,omitempty"` + DisablePrompts bool `xml:"disablePrompts,attr,omitempty"` + XWindow int `xml:"xWindow,attr,omitempty"` + YWindow int `xml:"yWindow,attr,omitempty"` + DataValidation []*DataValidation `xml:"dataValidation"` +} + +// DataValidation directly maps the a single item of data validation defined +// on a range of the worksheet. +type DataValidation struct { + AllowBlank bool `xml:"allowBlank,attr"` + Error *string `xml:"error,attr"` + ErrorStyle *string `xml:"errorStyle,attr"` + ErrorTitle *string `xml:"errorTitle,attr"` + Operator string `xml:"operator,attr,omitempty"` + Prompt *string `xml:"prompt,attr"` + PromptTitle *string `xml:"promptTitle,attr"` + ShowDropDown bool `xml:"showDropDown,attr,omitempty"` + ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"` + ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"` + Sqref string `xml:"sqref,attr"` + Type string `xml:"type,attr,omitempty"` + Formula1 string `xml:",innerxml"` + Formula2 string `xml:",innerxml"` +} + +// xlsxC collection represents a cell in the worksheet. Information about the +// cell's location (reference), value, data type, formatting, and formula is +// expressed here. +// +// This simple type is restricted to the values listed in the following table: +// +// Enumeration Value | Description +// ---------------------------+--------------------------------- +// b (Boolean) | Cell containing a boolean. +// d (Date) | Cell contains a date in the ISO 8601 format. +// e (Error) | Cell containing an error. +// inlineStr (Inline String) | Cell containing an (inline) rich string, i.e., one not in the shared string table. If this cell type is used, then the cell value is in the is element rather than the v element in the cell (c element). +// n (Number) | Cell containing a number. +// s (Shared String) | Cell containing a shared string. +// str (String) | Cell containing a formula string. +// +type xlsxC struct { + XMLName xml.Name `xml:"c"` + XMLSpace xml.Attr `xml:"space,attr,omitempty"` + R string `xml:"r,attr,omitempty"` // Cell ID, e.g. A1 + S int `xml:"s,attr,omitempty"` // Style reference. + // Str string `xml:"str,attr,omitempty"` // Style reference. + T string `xml:"t,attr,omitempty"` // Type. + F *xlsxF `xml:"f,omitempty"` // Formula + V string `xml:"v,omitempty"` // Value + IS *xlsxSI `xml:"is"` +} + +// xlsxF represents a formula for the cell. The formula expression is +// contained in the character node of this element. +type xlsxF struct { + Content string `xml:",chardata"` + T string `xml:"t,attr,omitempty"` // Formula type + Aca bool `xml:"aca,attr,omitempty"` + Ref string `xml:"ref,attr,omitempty"` // Shared formula ref + Dt2D bool `xml:"dt2D,attr,omitempty"` + Dtr bool `xml:"dtr,attr,omitempty"` + Del1 bool `xml:"del1,attr,omitempty"` + Del2 bool `xml:"del2,attr,omitempty"` + R1 string `xml:"r1,attr,omitempty"` + R2 string `xml:"r2,attr,omitempty"` + Ca bool `xml:"ca,attr,omitempty"` + Si *int `xml:"si,attr"` // Shared formula index + Bx bool `xml:"bx,attr,omitempty"` +} + +// xlsxSheetProtection collection expresses the sheet protection options to +// enforce when the sheet is protected. +type xlsxSheetProtection struct { + XMLName xml.Name `xml:"sheetProtection"` + AlgorithmName string `xml:"algorithmName,attr,omitempty"` + Password string `xml:"password,attr,omitempty"` + HashValue string `xml:"hashValue,attr,omitempty"` + SaltValue string `xml:"saltValue,attr,omitempty"` + SpinCount int `xml:"spinCount,attr,omitempty"` + Sheet bool `xml:"sheet,attr"` + Objects bool `xml:"objects,attr"` + Scenarios bool `xml:"scenarios,attr"` + FormatCells bool `xml:"formatCells,attr"` + FormatColumns bool `xml:"formatColumns,attr"` + FormatRows bool `xml:"formatRows,attr"` + InsertColumns bool `xml:"insertColumns,attr"` + InsertRows bool `xml:"insertRows,attr"` + InsertHyperlinks bool `xml:"insertHyperlinks,attr"` + DeleteColumns bool `xml:"deleteColumns,attr"` + DeleteRows bool `xml:"deleteRows,attr"` + SelectLockedCells bool `xml:"selectLockedCells,attr"` + Sort bool `xml:"sort,attr"` + AutoFilter bool `xml:"autoFilter,attr"` + PivotTables bool `xml:"pivotTables,attr"` + SelectUnlockedCells bool `xml:"selectUnlockedCells,attr"` +} + +// xlsxPhoneticPr (Phonetic Properties) represents a collection of phonetic +// properties that affect the display of phonetic text for this String Item +// (si). Phonetic text is used to give hints as to the pronunciation of an East +// Asian language, and the hints are displayed as text within the spreadsheet +// cells across the top portion of the cell. Since the phonetic hints are text, +// every phonetic hint is expressed as a phonetic run (rPh), and these +// properties specify how to display that phonetic run. +type xlsxPhoneticPr struct { + XMLName xml.Name `xml:"phoneticPr"` + Alignment string `xml:"alignment,attr,omitempty"` + FontID *int `xml:"fontId,attr"` + Type string `xml:"type,attr,omitempty"` +} + +// A Conditional Format is a format, such as cell shading or font color, that a +// spreadsheet application can automatically apply to cells if a specified +// condition is true. This collection expresses conditional formatting rules +// applied to a particular cell or range. +type xlsxConditionalFormatting struct { + XMLName xml.Name `xml:"conditionalFormatting"` + Pivot bool `xml:"pivot,attr,omitempty"` + SQRef string `xml:"sqref,attr,omitempty"` + CfRule []*xlsxCfRule `xml:"cfRule"` +} + +// xlsxCfRule (Conditional Formatting Rule) represents a description of a +// conditional formatting rule. +type xlsxCfRule struct { + Type string `xml:"type,attr,omitempty"` + DxfID *int `xml:"dxfId,attr"` + Priority int `xml:"priority,attr,omitempty"` + StopIfTrue bool `xml:"stopIfTrue,attr,omitempty"` + AboveAverage *bool `xml:"aboveAverage,attr"` + Percent bool `xml:"percent,attr,omitempty"` + Bottom bool `xml:"bottom,attr,omitempty"` + Operator string `xml:"operator,attr,omitempty"` + Text string `xml:"text,attr,omitempty"` + TimePeriod string `xml:"timePeriod,attr,omitempty"` + Rank int `xml:"rank,attr,omitempty"` + StdDev int `xml:"stdDev,attr,omitempty"` + EqualAverage bool `xml:"equalAverage,attr,omitempty"` + Formula []string `xml:"formula,omitempty"` + ColorScale *xlsxColorScale `xml:"colorScale"` + DataBar *xlsxDataBar `xml:"dataBar"` + IconSet *xlsxIconSet `xml:"iconSet"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxColorScale (Color Scale) describes a gradated color scale in this +// conditional formatting rule. +type xlsxColorScale struct { + Cfvo []*xlsxCfvo `xml:"cfvo"` + Color []*xlsxColor `xml:"color"` +} + +// dataBar (Data Bar) describes a data bar conditional formatting rule. +type xlsxDataBar struct { + MaxLength int `xml:"maxLength,attr,omitempty"` + MinLength int `xml:"minLength,attr,omitempty"` + ShowValue bool `xml:"showValue,attr,omitempty"` + Cfvo []*xlsxCfvo `xml:"cfvo"` + Color []*xlsxColor `xml:"color"` +} + +// xlsxIconSet (Icon Set) describes an icon set conditional formatting rule. +type xlsxIconSet struct { + Cfvo []*xlsxCfvo `xml:"cfvo"` + IconSet string `xml:"iconSet,attr,omitempty"` + ShowValue bool `xml:"showValue,attr,omitempty"` + Percent bool `xml:"percent,attr,omitempty"` + Reverse bool `xml:"reverse,attr,omitempty"` +} + +// cfvo (Conditional Format Value Object) describes the values of the +// interpolation points in a gradient scale. +type xlsxCfvo struct { + Gte bool `xml:"gte,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + Val string `xml:"val,attr,omitempty"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxHyperlinks directly maps the hyperlinks element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - A hyperlink can +// be stored in a package as a relationship. Hyperlinks shall be identified by +// containing a target which specifies the destination of the given hyperlink. +type xlsxHyperlinks struct { + XMLName xml.Name `xml:"hyperlinks"` + Hyperlink []xlsxHyperlink `xml:"hyperlink"` +} + +// xlsxHyperlink directly maps the hyperlink element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main +type xlsxHyperlink struct { + Ref string `xml:"ref,attr"` + Location string `xml:"location,attr,omitempty"` + Display string `xml:"display,attr,omitempty"` + Tooltip string `xml:"tooltip,attr,omitempty"` + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` +} + +// xlsxTableParts directly maps the tableParts element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - The table element +// has several attributes applied to identify the table and the data range it +// covers. The table id attribute needs to be unique across all table parts, the +// same goes for the name and displayName. The displayName has the further +// restriction that it must be unique across all defined names in the workbook. +// Later on we will see that you can define names for many elements, such as +// cells or formulas. The name value is used for the object model in Microsoft +// Office Excel. The displayName is used for references in formulas. The ref +// attribute is used to identify the cell range that the table covers. This +// includes not only the table data, but also the table header containing column +// names. +// To add columns to your table you add new tableColumn elements to the +// tableColumns container. Similar to the shared string table the collection +// keeps a count attribute identifying the number of columns. Besides the table +// definition in the table part there is also the need to identify which tables +// are displayed in the worksheet. The worksheet part has a separate element +// tableParts to store this information. Each table part is referenced through +// the relationship ID and again a count of the number of table parts is +// maintained. The following markup sample is taken from the documents +// accompanying this book. The sheet data element has been removed to reduce the +// size of the sample. To reference the table, just add the tableParts element, +// of course after having created and stored the table part. For example: +// +// +// ... +// +// +// +// +// +type xlsxTableParts struct { + XMLName xml.Name `xml:"tableParts"` + Count int `xml:"count,attr,omitempty"` + TableParts []*xlsxTablePart `xml:"tablePart"` +} + +// xlsxTablePart directly maps the tablePart element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main +type xlsxTablePart struct { + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` +} + +// xlsxPicture directly maps the picture element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Background sheet +// image. For example: +// +// +// +type xlsxPicture struct { + XMLName xml.Name `xml:"picture"` + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` +} + +// xlsxLegacyDrawing directly maps the legacyDrawing element in the namespace +// http://schemas.openxmlformats.org/spreadsheetml/2006/main - A comment is a +// rich text note that is attached to, and associated with, a cell, separate +// from other cell content. Comment content is stored separate from the cell, +// and is displayed in a drawing object (like a text box) that is separate from, +// but associated with, a cell. Comments are used as reminders, such as noting +// how a complex formula works, or to provide feedback to other users. Comments +// can also be used to explain assumptions made in a formula or to call out +// something special about the cell. +type xlsxLegacyDrawing struct { + XMLName xml.Name `xml:"legacyDrawing"` + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` +} + +// xlsxLegacyDrawingHF specifies the explicit relationship to the part +// containing the VML defining pictures rendered in the header / footer of the +// sheet. +type xlsxLegacyDrawingHF struct { + XMLName xml.Name `xml:"legacyDrawingHF"` + RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` +} + +// xlsxAlternateContent is a container for a sequence of multiple +// representations of a given piece of content. The program reading the file +// should only process one of these, and the one chosen should be based on +// which conditions match. +type xlsxAlternateContent struct { + XMLNSMC string `xml:"xmlns:mc,attr,omitempty"` + Content string `xml:",innerxml"` +} + +// xlsxInnerXML holds parts of XML content currently not unmarshal. +type xlsxInnerXML struct { + Content string `xml:",innerxml"` +} + +// xlsxWorksheetExt directly maps the ext element in the worksheet. +type xlsxWorksheetExt struct { + XMLName xml.Name `xml:"ext"` + URI string `xml:"uri,attr"` + Content string `xml:",innerxml"` +} + +// decodeWorksheetExt directly maps the ext element. +type decodeWorksheetExt struct { + XMLName xml.Name `xml:"extLst"` + Ext []*xlsxWorksheetExt `xml:"ext"` +} + +// decodeX14SparklineGroups directly maps the sparklineGroups element. +type decodeX14SparklineGroups struct { + XMLName xml.Name `xml:"sparklineGroups"` + XMLNSXM string `xml:"xmlns:xm,attr"` + Content string `xml:",innerxml"` +} + +// xlsxX14SparklineGroups directly maps the sparklineGroups element. +type xlsxX14SparklineGroups struct { + XMLName xml.Name `xml:"x14:sparklineGroups"` + XMLNSXM string `xml:"xmlns:xm,attr"` + SparklineGroups []*xlsxX14SparklineGroup `xml:"x14:sparklineGroup"` + Content string `xml:",innerxml"` +} + +// xlsxX14SparklineGroup directly maps the sparklineGroup element. +type xlsxX14SparklineGroup struct { + XMLName xml.Name `xml:"x14:sparklineGroup"` + ManualMax int `xml:"manualMax,attr,omitempty"` + ManualMin int `xml:"manualMin,attr,omitempty"` + LineWeight float64 `xml:"lineWeight,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + DateAxis bool `xml:"dateAxis,attr,omitempty"` + DisplayEmptyCellsAs string `xml:"displayEmptyCellsAs,attr,omitempty"` + Markers bool `xml:"markers,attr,omitempty"` + High bool `xml:"high,attr,omitempty"` + Low bool `xml:"low,attr,omitempty"` + First bool `xml:"first,attr,omitempty"` + Last bool `xml:"last,attr,omitempty"` + Negative bool `xml:"negative,attr,omitempty"` + DisplayXAxis bool `xml:"displayXAxis,attr,omitempty"` + DisplayHidden bool `xml:"displayHidden,attr,omitempty"` + MinAxisType string `xml:"minAxisType,attr,omitempty"` + MaxAxisType string `xml:"maxAxisType,attr,omitempty"` + RightToLeft bool `xml:"rightToLeft,attr,omitempty"` + ColorSeries *xlsxTabColor `xml:"x14:colorSeries"` + ColorNegative *xlsxTabColor `xml:"x14:colorNegative"` + ColorAxis *xlsxColor `xml:"x14:colorAxis"` + ColorMarkers *xlsxTabColor `xml:"x14:colorMarkers"` + ColorFirst *xlsxTabColor `xml:"x14:colorFirst"` + ColorLast *xlsxTabColor `xml:"x14:colorLast"` + ColorHigh *xlsxTabColor `xml:"x14:colorHigh"` + ColorLow *xlsxTabColor `xml:"x14:colorLow"` + Sparklines xlsxX14Sparklines `xml:"x14:sparklines"` +} + +// xlsxX14Sparklines directly maps the sparklines element. +type xlsxX14Sparklines struct { + Sparkline []*xlsxX14Sparkline `xml:"x14:sparkline"` +} + +// xlsxX14Sparkline directly maps the sparkline element. +type xlsxX14Sparkline struct { + F string `xml:"xm:f"` + Sqref string `xml:"xm:sqref"` +} + +// SparklineOption directly maps the settings of the sparkline. +type SparklineOption struct { + Location []string + Range []string + Max int + CustMax int + Min int + CustMin int + Type string + Weight float64 + DateAxis bool + Markers bool + High bool + Low bool + First bool + Last bool + Negative bool + Axis bool + Hidden bool + Reverse bool + Style int + SeriesColor string + NegativeColor string + MarkersColor string + FirstColor string + LastColor string + HightColor string + LowColor string + EmptyCells string +} + +// formatPanes directly maps the settings of the panes. +type formatPanes struct { + Freeze bool `json:"freeze"` + Split bool `json:"split"` + XSplit int `json:"x_split"` + YSplit int `json:"y_split"` + TopLeftCell string `json:"top_left_cell"` + ActivePane string `json:"active_pane"` + Panes []struct { + SQRef string `json:"sqref"` + ActiveCell string `json:"active_cell"` + Pane string `json:"pane"` + } `json:"panes"` +} + +// formatConditional directly maps the conditional format settings of the cells. +type formatConditional struct { + Type string `json:"type"` + AboveAverage bool `json:"above_average"` + Percent bool `json:"percent"` + Format int `json:"format"` + Criteria string `json:"criteria"` + Value string `json:"value,omitempty"` + Minimum string `json:"minimum,omitempty"` + Maximum string `json:"maximum,omitempty"` + MinType string `json:"min_type,omitempty"` + MidType string `json:"mid_type,omitempty"` + MaxType string `json:"max_type,omitempty"` + MinValue string `json:"min_value,omitempty"` + MidValue string `json:"mid_value,omitempty"` + MaxValue string `json:"max_value,omitempty"` + MinColor string `json:"min_color,omitempty"` + MidColor string `json:"mid_color,omitempty"` + MaxColor string `json:"max_color,omitempty"` + MinLength string `json:"min_length,omitempty"` + MaxLength string `json:"max_length,omitempty"` + MultiRange string `json:"multi_range,omitempty"` + BarColor string `json:"bar_color,omitempty"` +} + +// FormatSheetProtection directly maps the settings of worksheet protection. +type FormatSheetProtection struct { + AlgorithmName string + AutoFilter bool + DeleteColumns bool + DeleteRows bool + EditObjects bool + EditScenarios bool + FormatCells bool + FormatColumns bool + FormatRows bool + InsertColumns bool + InsertHyperlinks bool + InsertRows bool + Password string + PivotTables bool + SelectLockedCells bool + SelectUnlockedCells bool + Sort bool +} + +// FormatHeaderFooter directly maps the settings of header and footer. +type FormatHeaderFooter struct { + AlignWithMargins bool + DifferentFirst bool + DifferentOddEven bool + ScaleWithDoc bool + OddHeader string + OddFooter string + EvenHeader string + EvenFooter string + FirstFooter string + FirstHeader string +} + +// FormatPageMargins directly maps the settings of page margins +type FormatPageMargins struct { + Bottom string + Footer string + Header string + Left string + Right string + Top string +} diff --git a/vendor/github.com/xuri/nfp/CODE_OF_CONDUCT.md b/vendor/github.com/xuri/nfp/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..572b5612 --- /dev/null +++ b/vendor/github.com/xuri/nfp/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [xuri.me](https://xuri.me). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct][version] + +[homepage]: https://www.contributor-covenant.org +[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct diff --git a/vendor/github.com/xuri/nfp/LICENSE b/vendor/github.com/xuri/nfp/LICENSE new file mode 100644 index 00000000..e0749f94 --- /dev/null +++ b/vendor/github.com/xuri/nfp/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2022 Ri Xu All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of nfp nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/github.com/xuri/nfp/PULL_REQUEST_TEMPLATE.md b/vendor/github.com/xuri/nfp/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..d2ac755e --- /dev/null +++ b/vendor/github.com/xuri/nfp/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,45 @@ +# PR Details + + + +## Description + + + +## Related Issue + + + + + + +## Motivation and Context + + + +## How Has This Been Tested + + + + + +## Types of changes + + + +- [ ] Docs change / refactoring / dependency upgrade +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +## Checklist + + + + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have read the **CONTRIBUTING** document. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. diff --git a/vendor/github.com/xuri/nfp/README.md b/vendor/github.com/xuri/nfp/README.md new file mode 100644 index 00000000..125758f9 --- /dev/null +++ b/vendor/github.com/xuri/nfp/README.md @@ -0,0 +1,66 @@ +# NFP (Number Format Parser) + +[![Build Status](https://github.com/xuri/nfp/workflows/Go/badge.svg)](https://github.com/xuri/nfp/actions?workflow=Go) +[![Code Coverage](https://codecov.io/gh/xuri/nfp/branch/main/graph/badge.svg)](https://codecov.io/gh/xuri/nfp) +[![Go Report Card](https://goreportcard.com/badge/github.com/xuri/nfp)](https://goreportcard.com/report/github.com/xuri/nfp) +[![go.dev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/xuri/nfp) +[![Licenses](https://img.shields.io/badge/license-bsd-orange.svg)](https://opensource.org/licenses/BSD-3-Clause) + +Using NFP (Number Format Parser) you can get an Abstract Syntax Tree (AST) from Excel number format expression. + +## Installation + +```bash +go get github.com/xuri/nfp +``` + +## Example + +```go +package main + +import "github.com/xuri/nfp" + +func main() { + ps := nfp.NumberFormatParser() + tokens := ps.Parse("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + println(p.PrettyPrint()) +} +``` + +Get AST + +```text + + + # + + ## + 0 + . + 00 + + + ( + # + , + ## + 0 + . + 00 + ) + + + - + ?? + + @ +``` + +## Contributing + +Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. + +## Licenses + +This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause). diff --git a/vendor/github.com/xuri/nfp/SECURITY.md b/vendor/github.com/xuri/nfp/SECURITY.md new file mode 100644 index 00000000..2221c893 --- /dev/null +++ b/vendor/github.com/xuri/nfp/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Supported Versions + +We will dive into any security-related issue as long as your nfp version is still supported by us. When reporting an issue, include as much information as possible, but no need to fill fancy forms or answer tedious questions. Just tell us what you found, how to reproduce it, and any concerns you have about it. We will respond as soon as possible and follow up with any missing information. + +## Reporting a Vulnerability + +Please e-mail us directly at `xuri.me@gmail.com` or use the security issue template on GitHub. In general, public disclosure is made after the issue has been fully identified and a patch is ready to be released. A security issue gets the highest priority assigned and a reply regarding the vulnerability is given within a typical 24 hours. Thank you! diff --git a/vendor/github.com/xuri/nfp/go.mod b/vendor/github.com/xuri/nfp/go.mod new file mode 100644 index 00000000..92986da7 --- /dev/null +++ b/vendor/github.com/xuri/nfp/go.mod @@ -0,0 +1,3 @@ +module github.com/xuri/nfp + +go 1.15 diff --git a/vendor/github.com/xuri/nfp/nfp.go b/vendor/github.com/xuri/nfp/nfp.go new file mode 100644 index 00000000..94a399f3 --- /dev/null +++ b/vendor/github.com/xuri/nfp/nfp.go @@ -0,0 +1,800 @@ +// Copyright 2022 The nfp Authors. All rights reserved. Use of this source code +// is governed by a BSD-style license that can be found in the LICENSE file. +// +// This package NFP (Number Format Parser) produce syntax trees for number +// format expression. Excel Number format controls options such the number of +// decimal digits, the currency sign, commas to separate thousands, and +// display of negative numbers. The number format of an index applies wherever +// that index is used, including row or column headers of a table, or graph +// axis that uses that index. +// +// Implementation with Go language by Ri Xu: https://xuri.me + +package nfp + +import "strings" + +// Asterisk, At and other's constants are token definitions. +const ( + // Character constants + Asterisk = "*" + At = "@" + BackSlash = "\\" + BlockDelimiter = ";" + BracketClose = "]" + BracketOpen = "[" + Colon = ":" + Comma = "," + Dash = "-" + Dollar = "$" + Dot = "." + Hash = "#" + ParenClose = ")" + ParenOpen = "(" + Percent = "%" + Plus = "+" + Question = "?" + QuoteDouble = "\"" + QuoteSingle = "'" + Slash = "/" + Underscore = "_" + Whitespace = " " + Zero = "0" + // DatesTimesCodeChars defined dates and times control codes in upper case + DatesTimesCodeChars = "EYMDHSG" + // NumCodeChars defined numeric code character + NumCodeChars = "0123456789" + // Token section types + TokenSectionNegative = "Negative" + TokenSectionPositive = "Positive" + TokenSectionText = "Text" + TokenSectionZero = "Zero" + // Token subtypes + TokenSubTypeCurrencyString = "CurrencyString" + TokenSubTypeLanguageInfo = "LanguageInfo" + TokenTypeColor = "Color" + // Token types + TokenTypeCondition = "Condition" + TokenTypeCurrencyLanguage = "CurrencyLanguage" + TokenTypeDateTimes = "DateTimes" + TokenTypeDecimalPoint = "DecimalPoint" + TokenTypeDenominator = "Denominator" + TokenTypeDigitalPlaceHolder = "DigitalPlaceHolder" + TokenTypeElapsedDateTimes = "ElapsedDateTimes" + TokenTypeExponential = "Exponential" + TokenTypeFraction = "Fraction" + TokenTypeGeneral = "General" + TokenTypeHashPlaceHolder = "HashPlaceHolder" + TokenTypeLiteral = "Literal" + TokenTypeOperand = "Operand" + TokenTypeOperator = "Operator" + TokenTypePercent = "Percent" + TokenTypeRepeatsChar = "RepeatsChar" + TokenTypeSwitchArgument = "SwitchArgument" + TokenTypeTextPlaceHolder = "TextPlaceHolder" + TokenTypeThousandsSeparator = "ThousandsSeparator" + TokenTypeUnknown = "Unknown" + TokenTypeZeroPlaceHolder = "ZeroPlaceHolder" +) + +// ColorNames defined colors name used in for a section of the format, use the +// name of one of the following eight colors in square brackets in the +// section. The color code shall be the first item in the section. +var ColorNames = []string{ + "black", + "blue", + "cyan", + "green", + "magenta", + "red", + "white", + "yellow", +} + +// GeneralFormattingSwitchArguments defined switch-arguments apply to fields +// whose field result is a numeric value. If the result type of the field is +// not numeric, then these switches have no effect. +var GeneralFormattingSwitchArguments = []string{ + "AIUEO", + "ALPHABETIC", + "alphabetic", + "Arabic", + "ARABICABJAD", + "ARABICALPHA", + "ArabicDash", + "BAHTTEXT", + "CardText", + "CHINESENUM1", + "CHINESENUM2", + "CHINESENUM3", + "CHOSUNG", + "CIRCLENUM", + "DBCHAR", + "DBNUM1", + "DBNUM2", + "DBNUM3", + "DBNUM4", + "DollarText", + "GANADA", + "GB1", + "GB2", + "GB3", + "GB4", + "HEBREW1", + "HEBREW2", + "Hex", + "HINDIARABIC", + "HINDICARDTEXT", + "HINDILETTER1", + "HINDILETTER2", + "IROHA", + "KANJINUM1", + "KANJINUM2", + "KANJINUM3", + "Ordinal", + "OrdText", + "Roman", + "roman", + "SBCHAR", + "THAIARABIC", + "THAICARDTEXT", + "THAILETTER", + "VIETCARDTEXT", + "ZODIAC1", + "ZODIAC2", + "ZODIAC3", +} + +// AmPm defined the AM and PM with international considerations. +var AmPm = []string{"AM/PM", "A/P", "上午/下午"} + +// ConditionOperators defined the condition operators. +var ConditionOperators = []string{"<", "<=", ">", ">=", "<>", "="} + +// Part directly maps the sub part of the token. +type Part struct { + Token Token + Value string +} + +// Token encapsulate a number format token. +type Token struct { + TValue string + TType string + Parts []Part +} + +// Section directly maps sections of the number format. Up to four sections of +// format codes can be specified. The format codes, separated by semicolons, +// define the formats for positive numbers, negative numbers, zero values, and +// text, in that order. If only two sections are specified, the first is used +// for positive numbers and zeros, and the second is used for negative +// numbers. If only one section is specified, it is used for all numbers. To +// skip a section, the ending semicolon for that section shall be written. +type Section struct { + Type string + Items []Token +} + +// Tokens directly maps the ordered list of tokens. +// Attributes: +// +// Index - Current position in the number format expression +// SectionIndex - Current position in section +// Sections - Ordered section of token sequences +// +type Tokens struct { + Index int + SectionIndex int + Sections []Section +} + +// fTokens provides function to handle an ordered list of tokens. +func fTokens() Tokens { + return Tokens{ + Index: -1, + } +} + +// fToken provides function to encapsulate a number format token. +func fToken(value, tokenType string, parts []Part) Token { + return Token{ + TValue: value, + TType: tokenType, + Parts: parts, + } +} + +// add provides function to add a token to the end of the list. +func (tk *Tokens) add(value, tokenType string, parts []Part) Token { + token := fToken(value, tokenType, parts) + tk.addRef(token) + return token +} + +// addRef provides function to add a token to the end of the list. +func (tk *Tokens) addRef(token Token) { + if len(tk.Sections) <= tk.SectionIndex { + sectionType := []string{TokenSectionPositive, TokenSectionNegative, TokenSectionZero, TokenSectionText}[tk.SectionIndex] + for i := len(tk.Sections) - 1; i < tk.SectionIndex; i++ { + tk.Sections = append(tk.Sections, Section{Type: sectionType}) + } + } + tk.Sections[tk.SectionIndex].Items = append(tk.Sections[tk.SectionIndex].Items, token) +} + +// reset provides function to reset the index to -1. +func (tk *Tokens) reset() { + tk.Index = -1 +} + +// Parser inheritable container. +type Parser struct { + InBracket bool + InString bool + InPlaceholder bool + NumFmt string + Offset int + Tokens Tokens + Token Token +} + +// NumberFormatParser provides function to parse an Excel number format into a +// stream of tokens. +func NumberFormatParser() Parser { + return Parser{} +} + +// EOF provides function to check whether end of tokens stack. +func (ps *Parser) EOF() bool { + return ps.Offset >= len([]rune(ps.NumFmt)) +} + +// getTokens return a token stream (list). +func (ps *Parser) getTokens() Tokens { + ps.NumFmt = strings.TrimSpace(ps.NumFmt) + // state-dependent character evaluation (order is important) + for !ps.EOF() { + if ps.InBracket { + if ps.Token.TType == TokenTypeCurrencyLanguage { + if ps.currentChar() != Dash && ps.currentChar() != BracketClose { + ps.Token.Parts[1].Token.TValue += ps.currentChar() + } + if ps.currentChar() == Dash { + ps.Token.Parts[0].Token.TValue, ps.Token.Parts[1].Token.TValue = ps.Token.Parts[1].Token.TValue, ps.Token.Parts[0].Token.TValue + } + } + + if len(ps.Token.TValue) > 1 && inStrSlice(ConditionOperators, ps.Token.TValue[1:], true) != -1 { + if ps.currentChar() == Dash || strings.ContainsAny(NumCodeChars, ps.currentChar()) { + ps.Token.TType = TokenTypeCondition + ps.Token.Parts = []Part{ + {Token: Token{TType: TokenTypeOperator, TValue: ps.Token.TValue[1:]}}, + {Token: Token{TType: TokenTypeOperand}}, + } + ps.Token.TValue = "" + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + } + + if ps.currentChar() == BracketClose { + ps.InBracket = false + if ps.Token.TType == TokenTypeCondition && len(ps.Token.Parts) == 2 { + ps.Token.Parts[1].Token.TValue = ps.Token.TValue + ps.Tokens.add(ps.Token.Parts[0].Token.TValue+ps.Token.Parts[1].Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + ps.Token.TValue += ps.currentChar() + if l := len(ps.Token.TValue); l > 2 { + lit := ps.Token.TValue[1 : l-1] + + if idx := inStrSlice(ColorNames, lit, false); idx != -1 { + ps.Tokens.add(lit, TokenTypeColor, nil) + ps.Token = Token{} + ps.Offset++ + continue + } + + if idx := inStrSlice(GeneralFormattingSwitchArguments, lit, false); idx != -1 { + ps.Tokens.add(ps.Token.TValue, TokenTypeSwitchArgument, nil) + ps.Token = Token{} + ps.Offset++ + continue + } + + if ps.Token.TType == TokenTypeCurrencyLanguage { + if ps.Token.Parts[0].Token.TValue == "" { + ps.Token.Parts = []Part{{Token: Token{TType: ps.Token.Parts[1].Token.TType, TValue: ps.Token.Parts[1].Token.TValue}}} + } + + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + ps.Token.TType, ps.Token.TValue = TokenTypeUnknown, lit + isDateTime := true + for _, ch := range lit { + if !strings.ContainsAny(DatesTimesCodeChars, strings.ToUpper(string(ch))) { + isDateTime = false + } + } + if isDateTime { + ps.Token.TType = TokenTypeElapsedDateTimes + } + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + } + } + + if !ps.InBracket { + if strings.ContainsAny(NumCodeChars, ps.currentChar()) { + if ps.Token.TType == TokenTypeZeroPlaceHolder || ps.Token.TType == TokenTypeDenominator { + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + if ps.Token.TType == TokenTypeFraction { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{TType: TokenTypeDenominator, TValue: ps.currentChar()} + ps.Offset++ + continue + } + if ps.Token.TType != "" { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.Token.TType = TokenTypeZeroPlaceHolder + if ps.currentChar() != Zero { + ps.Token.TType = TokenTypeLiteral + } + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + + if ps.currentChar() == Hash { + if ps.Token.TType != TokenTypeHashPlaceHolder && ps.Token.TType != "" { + if ps.Token.TValue == Dot { + ps.Token.TType = TokenTypeDecimalPoint + } + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.Token.TType = TokenTypeHashPlaceHolder + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + + if ps.currentChar() == Dot { + if ps.Token.TType == TokenTypeZeroPlaceHolder || ps.Token.TType == TokenTypeHashPlaceHolder { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Tokens.add(ps.currentChar(), TokenTypeDecimalPoint, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + if !ps.InString { + if ps.Token.TType != "" && strings.ContainsAny(NumCodeChars, ps.nextChar()) { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.Token.TType = TokenTypeDecimalPoint + } + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + } + + if strings.ContainsAny(Dollar+Dash+Plus+ParenOpen+ParenClose+Colon+Whitespace, ps.currentChar()) { + if ps.InBracket { + if len(ps.Token.Parts) == 0 { + ps.Token.Parts = []Part{ + {Token: Token{TType: TokenSubTypeCurrencyString}}, + {Token: Token{TType: TokenSubTypeLanguageInfo}}, + } + } + ps.Token.TValue += ps.currentChar() + ps.Token.TType = TokenTypeCurrencyLanguage + ps.Offset++ + continue + } + + if ps.Token.TType != TokenTypeLiteral && ps.Token.TType != TokenTypeDateTimes && ps.Token.TType != "" { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{TType: TokenTypeLiteral, TValue: ps.currentChar()} + ps.Offset++ + continue + } + + if ps.Token.TValue != BackSlash && ps.Token.TType == "" && inStrSlice(AmPm, ps.Token.TValue, false) == -1 { + ps.Token.TType = TokenTypeLiteral + } + + if ps.Token.TType == TokenTypeLiteral { + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + } + + if ps.currentChar() == Underscore { + ps.Offset += 2 + continue + } + + if ps.currentChar() == Asterisk { + ps.Tokens.add(ps.nextChar(), TokenTypeRepeatsChar, ps.Token.Parts) + ps.Token = Token{} + ps.Offset += 2 + continue + } + + if ps.currentChar() == BackSlash { + if ps.Token.TValue != "" { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.Tokens.add(ps.nextChar(), TokenTypeLiteral, ps.Token.Parts) + ps.Token = Token{} + ps.Offset += 2 + continue + } + + if ps.currentChar() == Dash { + if ps.Token.TType != "" { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + } + ps.Token.TType = TokenTypeLiteral + if ps.currentChar() != ps.nextChar() { + ps.Tokens.add(ps.currentChar(), ps.Token.TType, ps.Token.Parts) + } + ps.Token = Token{} + ps.Offset++ + continue + } + + if ps.currentChar() == Comma { + if ps.Token.TType == TokenTypeZeroPlaceHolder || ps.Token.TType == TokenTypeHashPlaceHolder { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Tokens.add(ps.currentChar(), TokenTypeThousandsSeparator, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + if !ps.InString { + if ps.Token.TType == TokenTypeLiteral { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{TType: TokenTypeThousandsSeparator} + } + if ps.Token.TType == TokenTypeDateTimes { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{TType: TokenTypeLiteral} + } + if ps.currentChar() != ps.nextChar() { + if ps.Token.TType == "" { + ps.Token.TType = TokenTypeLiteral + } + ps.Tokens.add(ps.currentChar(), ps.Token.TType, ps.Token.Parts) + } + ps.Token = Token{} + ps.Offset++ + continue + } + ps.Token.TType = TokenTypeLiteral + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + + if ps.currentChar() == Whitespace { + if inStrSlice(AmPm, ps.Token.TValue, false) != -1 { + ps.Token.TType = TokenTypeDateTimes + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + if ps.Token.TType != "" && ps.Token.TType != TokenTypeLiteral { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + } + ps.Token.TType = TokenTypeLiteral + ps.Tokens.add(ps.currentChar(), ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + + if ps.currentChar() == Slash { + if ps.Token.TType == TokenTypeDateTimes { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Tokens.add(ps.currentChar(), TokenTypeLiteral, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + if ps.Token.TType == TokenTypeDigitalPlaceHolder { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{TType: TokenTypeFraction, TValue: ps.currentChar()} + ps.Offset++ + continue + } + } + + if ps.currentChar() == Colon && ps.Token.TType == TokenTypeDateTimes { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Tokens.add(ps.currentChar(), TokenTypeLiteral, ps.Token.Parts) + ps.Token = Token{} + ps.Offset++ + continue + } + + if ps.currentChar() == QuoteDouble { + ps.Offset++ + if ps.InString && len(ps.Token.TValue) > 0 { + ps.Tokens.add(ps.Token.TValue, TokenTypeLiteral, ps.Token.Parts) + ps.Token = Token{} + ps.InString = false + continue + } + if ps.Token.TValue != "" { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + } + ps.InString = true + ps.Token = Token{TType: TokenTypeLiteral} + continue + } + + if ps.currentChar() == At { + if len(ps.Tokens.Sections) <= ps.Tokens.SectionIndex { + ps.Tokens.Sections = append(ps.Tokens.Sections, Section{Type: TokenSectionText}) + } + ps.Tokens.Sections[ps.Tokens.SectionIndex].Type = TokenSectionText + if ps.Token.TType != "" && !ps.InBracket { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + } + ps.Token = Token{TType: TokenTypeTextPlaceHolder, TValue: ps.currentChar()} + ps.Offset++ + continue + } + + if ps.currentChar() == BracketOpen { + if ps.Token.TType != "" && !ps.InBracket { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.InBracket = true + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + + if ps.currentChar() == Question { + if ps.Token.TType != "" && ps.Token.TType != TokenTypeDigitalPlaceHolder { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.Token.TType = TokenTypeDigitalPlaceHolder + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + + if ps.currentChar() == Percent { + if ps.Token.TType != "" { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.Token.TType = TokenTypePercent + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + + if ps.currentChar() == BlockDelimiter { + sectionTypes := []string{TokenSectionPositive, TokenSectionNegative, TokenSectionZero, TokenSectionText} + if ps.Token.TType != "" { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + } + if len(ps.Tokens.Sections) <= ps.Tokens.SectionIndex { + ps.Tokens.Sections = append(ps.Tokens.Sections, Section{Type: sectionTypes[ps.Tokens.SectionIndex]}) + } + ps.Tokens.SectionIndex++ + if ps.Tokens.SectionIndex > 3 { + tokens := fTokens() + tokens.reset() + return Tokens{} + } + ps.Token = Token{} + ps.Tokens.Sections = append(ps.Tokens.Sections, Section{Type: sectionTypes[ps.Tokens.SectionIndex]}) + ps.Offset++ + continue + } + + if strings.EqualFold("E+", ps.doubleChar()) { + if ps.Token.TType != "" { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.Token.TType = TokenTypeExponential + ps.Token.TValue += ps.doubleChar() + ps.Offset += 2 + continue + } + + if ap, matched := ps.apPattern(); ap != -1 { + ps.Tokens.add(matched, TokenTypeDateTimes, ps.Token.Parts) + ps.Token = Token{} + ps.Offset += len(matched) + continue + } + + if general, matched := ps.generalPattern(); general != -1 { + ps.Tokens.add(matched, TokenTypeGeneral, ps.Token.Parts) + ps.Token = Token{} + ps.Offset += len(matched) + continue + } + + // token accumulation + if !ps.InBracket && !ps.InString { + if strings.ContainsAny(DatesTimesCodeChars, strings.ToUpper(ps.currentChar())) { + if inStrSlice(AmPm, ps.Token.TValue, false) != -1 { + ps.Token.TType = TokenTypeDateTimes + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + if ps.Token.TType == TokenTypeLiteral || ps.Token.TType == TokenTypeDateTimes && !strings.ContainsAny(ps.Token.TValue, ps.currentChar()) { + ps.Tokens.add(ps.Token.TValue, ps.Token.TType, ps.Token.Parts) + ps.Token = Token{} + } + ps.Token.TType = TokenTypeDateTimes + ps.Token.TValue += ps.currentChar() + ps.Offset++ + continue + } + if strings.ContainsAny(DatesTimesCodeChars, strings.ToUpper(ps.Token.TValue)) { + ps.Tokens.add(ps.Token.TValue, TokenTypeDateTimes, ps.Token.Parts) + ps.Token = Token{TType: TokenTypeLiteral, TValue: ps.currentChar()} + ps.Offset++ + continue + } + if strings.ContainsAny(DatesTimesCodeChars, strings.ToUpper(ps.nextChar())) { + ps.Token.TValue += ps.currentChar() + ps.Token.TType = TokenTypeLiteral + ps.Offset++ + continue + } + if ps.currentChar() == QuoteSingle { + ps.Offset++ + continue + } + } + ps.Token.TValue += ps.currentChar() + ps.Offset++ + } + + // dump remaining accumulation + if len(ps.Token.TValue) > 0 { + tokenType := TokenTypeLiteral + if ps.Token.TType != "" { + tokenType = ps.Token.TType + } + ps.Tokens.add(ps.Token.TValue, tokenType, nil) + } + + tokens := fTokens() + tokens.reset() + return ps.Tokens +} + +// Parse provides function to parse number format as a token stream (list). +func (ps *Parser) Parse(numFmt string) []Section { + ps.NumFmt = numFmt + ps.Tokens = ps.getTokens() + return ps.Tokens.Sections +} + +// doubleChar provides function to get two characters after the current +// position. +func (ps *Parser) doubleChar() string { + if len([]rune(ps.NumFmt)) >= ps.Offset+2 { + return string([]rune(ps.NumFmt)[ps.Offset : ps.Offset+2]) + } + return "" +} + +// currentChar provides function to get the character of the current position. +func (ps *Parser) currentChar() string { + return string([]rune(ps.NumFmt)[ps.Offset]) +} + +// nextChar provides function to get the next character of the current +// position. +func (ps *Parser) nextChar() string { + if len([]rune(ps.NumFmt)) >= ps.Offset+2 { + return string([]rune(ps.NumFmt)[ps.Offset+1 : ps.Offset+2]) + } + return "" +} + +// apPattern infers whether the subsequent characters match the AM/PM pattern, +// it will be returned matched index and result. +func (ps *Parser) apPattern() (int, string) { + for i, pattern := range AmPm { + l := len(pattern) + if len([]rune(ps.NumFmt)) >= ps.Offset+l { + matched := string([]rune(ps.NumFmt)[ps.Offset : ps.Offset+l]) + if strings.EqualFold(matched, pattern) { + return i, matched + } + } + } + return -1, "" +} + +// generalPattern infers whether the subsequent characters match the +// general pattern, it will be returned matched result and result. +func (ps *Parser) generalPattern() (int, string) { + l := len(TokenTypeGeneral) + if len([]rune(ps.NumFmt)) >= ps.Offset+l { + matched := string([]rune(ps.NumFmt)[ps.Offset : ps.Offset+l]) + if strings.EqualFold(matched, TokenTypeGeneral) { + return 0, matched + } + } + return -1, "" +} + +// inStrSlice provides a method to check if an element is present in an array, +// and return the index of its location, otherwise return -1. +func inStrSlice(a []string, x string, caseSensitive bool) int { + for idx, n := range a { + if !caseSensitive && strings.EqualFold(x, n) { + return idx + } + if x == n { + return idx + } + } + return -1 +} + +// PrettyPrint provides function to pretty the parsed result with the indented +// format. +func (ps *Parser) PrettyPrint() string { + indent, output := 0, "" + for _, section := range ps.Tokens.Sections { + output += "<" + section.Type + ">" + "\n" + for _, item := range section.Items { + indent++ + for i := 0; i < indent; i++ { + output += "\t" + } + if len(item.Parts) == 0 { + output += item.TValue + " <" + item.TType + ">" + "\n" + } else { + output += "<" + item.TType + ">" + "\n" + } + for _, part := range item.Parts { + indent++ + for i := 0; i < indent; i++ { + output += "\t" + } + output += part.Token.TValue + " <" + part.Token.TType + ">" + "\n" + indent-- + } + indent-- + } + } + return output +} diff --git a/vendor/golang.org/x/crypto/ocsp/ocsp.go b/vendor/golang.org/x/crypto/ocsp/ocsp.go index 9d3fffa8..4269ed11 100644 --- a/vendor/golang.org/x/crypto/ocsp/ocsp.go +++ b/vendor/golang.org/x/crypto/ocsp/ocsp.go @@ -345,6 +345,8 @@ func (req *Request) Marshal() ([]byte, error) { // Response represents an OCSP response containing a single SingleResponse. See // RFC 6960. type Response struct { + Raw []byte + // Status is one of {Good, Revoked, Unknown} Status int SerialNumber *big.Int @@ -518,6 +520,7 @@ func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Respon } ret := &Response{ + Raw: bytes, TBSResponseData: basicResp.TBSResponseData.Raw, Signature: basicResp.Signature.RightAlign(), SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm), @@ -668,7 +671,7 @@ func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte // The responder cert is used to populate the responder's name field, and the // certificate itself is provided alongside the OCSP response signature. // -// The issuer cert is used to puplate the IssuerNameHash and IssuerKeyHash fields. +// The issuer cert is used to populate the IssuerNameHash and IssuerKeyHash fields. // // The template is used to populate the SerialNumber, Status, RevokedAt, // RevocationReason, ThisUpdate, and NextUpdate fields. diff --git a/vendor/golang.org/x/crypto/ripemd160/ripemd160.go b/vendor/golang.org/x/crypto/ripemd160/ripemd160.go new file mode 100644 index 00000000..cf3eeb15 --- /dev/null +++ b/vendor/golang.org/x/crypto/ripemd160/ripemd160.go @@ -0,0 +1,124 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ripemd160 implements the RIPEMD-160 hash algorithm. +// +// Deprecated: RIPEMD-160 is a legacy hash and should not be used for new +// applications. Also, this package does not and will not provide an optimized +// implementation. Instead, use a modern hash like SHA-256 (from crypto/sha256). +package ripemd160 // import "golang.org/x/crypto/ripemd160" + +// RIPEMD-160 is designed by Hans Dobbertin, Antoon Bosselaers, and Bart +// Preneel with specifications available at: +// http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/AB-9601.pdf. + +import ( + "crypto" + "hash" +) + +func init() { + crypto.RegisterHash(crypto.RIPEMD160, New) +} + +// The size of the checksum in bytes. +const Size = 20 + +// The block size of the hash algorithm in bytes. +const BlockSize = 64 + +const ( + _s0 = 0x67452301 + _s1 = 0xefcdab89 + _s2 = 0x98badcfe + _s3 = 0x10325476 + _s4 = 0xc3d2e1f0 +) + +// digest represents the partial evaluation of a checksum. +type digest struct { + s [5]uint32 // running context + x [BlockSize]byte // temporary buffer + nx int // index into x + tc uint64 // total count of bytes processed +} + +func (d *digest) Reset() { + d.s[0], d.s[1], d.s[2], d.s[3], d.s[4] = _s0, _s1, _s2, _s3, _s4 + d.nx = 0 + d.tc = 0 +} + +// New returns a new hash.Hash computing the checksum. +func New() hash.Hash { + result := new(digest) + result.Reset() + return result +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.tc += uint64(nn) + if d.nx > 0 { + n := len(p) + if n > BlockSize-d.nx { + n = BlockSize - d.nx + } + for i := 0; i < n; i++ { + d.x[d.nx+i] = p[i] + } + d.nx += n + if d.nx == BlockSize { + _Block(d, d.x[0:]) + d.nx = 0 + } + p = p[n:] + } + n := _Block(d, p) + p = p[n:] + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d0 *digest) Sum(in []byte) []byte { + // Make a copy of d0 so that caller can keep writing and summing. + d := *d0 + + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + tc := d.tc + var tmp [64]byte + tmp[0] = 0x80 + if tc%64 < 56 { + d.Write(tmp[0 : 56-tc%64]) + } else { + d.Write(tmp[0 : 64+56-tc%64]) + } + + // Length in bits. + tc <<= 3 + for i := uint(0); i < 8; i++ { + tmp[i] = byte(tc >> (8 * i)) + } + d.Write(tmp[0:8]) + + if d.nx != 0 { + panic("d.nx != 0") + } + + var digest [Size]byte + for i, s := range d.s { + digest[i*4] = byte(s) + digest[i*4+1] = byte(s >> 8) + digest[i*4+2] = byte(s >> 16) + digest[i*4+3] = byte(s >> 24) + } + + return append(in, digest[:]...) +} diff --git a/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go b/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go new file mode 100644 index 00000000..e0edc02f --- /dev/null +++ b/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go @@ -0,0 +1,165 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// RIPEMD-160 block step. +// In its own file so that a faster assembly or C version +// can be substituted easily. + +package ripemd160 + +import ( + "math/bits" +) + +// work buffer indices and roll amounts for one line +var _n = [80]uint{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, +} + +var _r = [80]uint{ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, +} + +// same for the other parallel one +var n_ = [80]uint{ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, +} + +var r_ = [80]uint{ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, +} + +func _Block(md *digest, p []byte) int { + n := 0 + var x [16]uint32 + var alpha, beta uint32 + for len(p) >= BlockSize { + a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4] + aa, bb, cc, dd, ee := a, b, c, d, e + j := 0 + for i := 0; i < 16; i++ { + x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 + j += 4 + } + + // round 1 + i := 0 + for i < 16 { + alpha = a + (b ^ c ^ d) + x[_n[i]] + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 2 + for i < 32 { + alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999 + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 3 + for i < 48 { + alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1 + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 4 + for i < 64 { + alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 5 + for i < 80 { + alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ cc ^ dd) + x[n_[i]] + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // combine results + dd += c + md.s[1] + md.s[1] = md.s[2] + d + ee + md.s[2] = md.s[3] + e + aa + md.s[3] = md.s[4] + a + bb + md.s[4] = md.s[0] + b + cc + md.s[0] = dd + + p = p[BlockSize:] + n += BlockSize + } + return n +} diff --git a/vendor/golang.org/x/net/html/atom/atom.go b/vendor/golang.org/x/net/html/atom/atom.go new file mode 100644 index 00000000..cd0a8ac1 --- /dev/null +++ b/vendor/golang.org/x/net/html/atom/atom.go @@ -0,0 +1,78 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package atom provides integer codes (also known as atoms) for a fixed set of +// frequently occurring HTML strings: tag names and attribute keys such as "p" +// and "id". +// +// Sharing an atom's name between all elements with the same tag can result in +// fewer string allocations when tokenizing and parsing HTML. Integer +// comparisons are also generally faster than string comparisons. +// +// The value of an atom's particular code is not guaranteed to stay the same +// between versions of this package. Neither is any ordering guaranteed: +// whether atom.H1 < atom.H2 may also change. The codes are not guaranteed to +// be dense. The only guarantees are that e.g. looking up "div" will yield +// atom.Div, calling atom.Div.String will return "div", and atom.Div != 0. +package atom // import "golang.org/x/net/html/atom" + +// Atom is an integer code for a string. The zero value maps to "". +type Atom uint32 + +// String returns the atom's name. +func (a Atom) String() string { + start := uint32(a >> 8) + n := uint32(a & 0xff) + if start+n > uint32(len(atomText)) { + return "" + } + return atomText[start : start+n] +} + +func (a Atom) string() string { + return atomText[a>>8 : a>>8+a&0xff] +} + +// fnv computes the FNV hash with an arbitrary starting value h. +func fnv(h uint32, s []byte) uint32 { + for i := range s { + h ^= uint32(s[i]) + h *= 16777619 + } + return h +} + +func match(s string, t []byte) bool { + for i, c := range t { + if s[i] != c { + return false + } + } + return true +} + +// Lookup returns the atom whose name is s. It returns zero if there is no +// such atom. The lookup is case sensitive. +func Lookup(s []byte) Atom { + if len(s) == 0 || len(s) > maxAtomLen { + return 0 + } + h := fnv(hash0, s) + if a := table[h&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { + return a + } + if a := table[(h>>16)&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { + return a + } + return 0 +} + +// String returns a string whose contents are equal to s. In that sense, it is +// equivalent to string(s) but may be more efficient. +func String(s []byte) string { + if a := Lookup(s); a != 0 { + return a.String() + } + return string(s) +} diff --git a/vendor/golang.org/x/net/html/atom/table.go b/vendor/golang.org/x/net/html/atom/table.go new file mode 100644 index 00000000..2a938864 --- /dev/null +++ b/vendor/golang.org/x/net/html/atom/table.go @@ -0,0 +1,783 @@ +// Code generated by go generate gen.go; DO NOT EDIT. + +//go:generate go run gen.go + +package atom + +const ( + A Atom = 0x1 + Abbr Atom = 0x4 + Accept Atom = 0x1a06 + AcceptCharset Atom = 0x1a0e + Accesskey Atom = 0x2c09 + Acronym Atom = 0xaa07 + Action Atom = 0x27206 + Address Atom = 0x6f307 + Align Atom = 0xb105 + Allowfullscreen Atom = 0x2080f + Allowpaymentrequest Atom = 0xc113 + Allowusermedia Atom = 0xdd0e + Alt Atom = 0xf303 + Annotation Atom = 0x1c90a + AnnotationXml Atom = 0x1c90e + Applet Atom = 0x31906 + Area Atom = 0x35604 + Article Atom = 0x3fc07 + As Atom = 0x3c02 + Aside Atom = 0x10705 + Async Atom = 0xff05 + Audio Atom = 0x11505 + Autocomplete Atom = 0x2780c + Autofocus Atom = 0x12109 + Autoplay Atom = 0x13c08 + B Atom = 0x101 + Base Atom = 0x3b04 + Basefont Atom = 0x3b08 + Bdi Atom = 0xba03 + Bdo Atom = 0x14b03 + Bgsound Atom = 0x15e07 + Big Atom = 0x17003 + Blink Atom = 0x17305 + Blockquote Atom = 0x1870a + Body Atom = 0x2804 + Br Atom = 0x202 + Button Atom = 0x19106 + Canvas Atom = 0x10306 + Caption Atom = 0x23107 + Center Atom = 0x22006 + Challenge Atom = 0x29b09 + Charset Atom = 0x2107 + Checked Atom = 0x47907 + Cite Atom = 0x19c04 + Class Atom = 0x56405 + Code Atom = 0x5c504 + Col Atom = 0x1ab03 + Colgroup Atom = 0x1ab08 + Color Atom = 0x1bf05 + Cols Atom = 0x1c404 + Colspan Atom = 0x1c407 + Command Atom = 0x1d707 + Content Atom = 0x58b07 + Contenteditable Atom = 0x58b0f + Contextmenu Atom = 0x3800b + Controls Atom = 0x1de08 + Coords Atom = 0x1ea06 + Crossorigin Atom = 0x1fb0b + Data Atom = 0x4a504 + Datalist Atom = 0x4a508 + Datetime Atom = 0x2b808 + Dd Atom = 0x2d702 + Default Atom = 0x10a07 + Defer Atom = 0x5c705 + Del Atom = 0x45203 + Desc Atom = 0x56104 + Details Atom = 0x7207 + Dfn Atom = 0x8703 + Dialog Atom = 0xbb06 + Dir Atom = 0x9303 + Dirname Atom = 0x9307 + Disabled Atom = 0x16408 + Div Atom = 0x16b03 + Dl Atom = 0x5e602 + Download Atom = 0x46308 + Draggable Atom = 0x17a09 + Dropzone Atom = 0x40508 + Dt Atom = 0x64b02 + Em Atom = 0x6e02 + Embed Atom = 0x6e05 + Enctype Atom = 0x28d07 + Face Atom = 0x21e04 + Fieldset Atom = 0x22608 + Figcaption Atom = 0x22e0a + Figure Atom = 0x24806 + Font Atom = 0x3f04 + Footer Atom = 0xf606 + For Atom = 0x25403 + ForeignObject Atom = 0x2540d + Foreignobject Atom = 0x2610d + Form Atom = 0x26e04 + Formaction Atom = 0x26e0a + Formenctype Atom = 0x2890b + Formmethod Atom = 0x2a40a + Formnovalidate Atom = 0x2ae0e + Formtarget Atom = 0x2c00a + Frame Atom = 0x8b05 + Frameset Atom = 0x8b08 + H1 Atom = 0x15c02 + H2 Atom = 0x2de02 + H3 Atom = 0x30d02 + H4 Atom = 0x34502 + H5 Atom = 0x34f02 + H6 Atom = 0x64d02 + Head Atom = 0x33104 + Header Atom = 0x33106 + Headers Atom = 0x33107 + Height Atom = 0x5206 + Hgroup Atom = 0x2ca06 + Hidden Atom = 0x2d506 + High Atom = 0x2db04 + Hr Atom = 0x15702 + Href Atom = 0x2e004 + Hreflang Atom = 0x2e008 + Html Atom = 0x5604 + HttpEquiv Atom = 0x2e80a + I Atom = 0x601 + Icon Atom = 0x58a04 + Id Atom = 0x10902 + Iframe Atom = 0x2fc06 + Image Atom = 0x30205 + Img Atom = 0x30703 + Input Atom = 0x44b05 + Inputmode Atom = 0x44b09 + Ins Atom = 0x20403 + Integrity Atom = 0x23f09 + Is Atom = 0x16502 + Isindex Atom = 0x30f07 + Ismap Atom = 0x31605 + Itemid Atom = 0x38b06 + Itemprop Atom = 0x19d08 + Itemref Atom = 0x3cd07 + Itemscope Atom = 0x67109 + Itemtype Atom = 0x31f08 + Kbd Atom = 0xb903 + Keygen Atom = 0x3206 + Keytype Atom = 0xd607 + Kind Atom = 0x17704 + Label Atom = 0x5905 + Lang Atom = 0x2e404 + Legend Atom = 0x18106 + Li Atom = 0xb202 + Link Atom = 0x17404 + List Atom = 0x4a904 + Listing Atom = 0x4a907 + Loop Atom = 0x5d04 + Low Atom = 0xc303 + Main Atom = 0x1004 + Malignmark Atom = 0xb00a + Manifest Atom = 0x6d708 + Map Atom = 0x31803 + Mark Atom = 0xb604 + Marquee Atom = 0x32707 + Math Atom = 0x32e04 + Max Atom = 0x33d03 + Maxlength Atom = 0x33d09 + Media Atom = 0xe605 + Mediagroup Atom = 0xe60a + Menu Atom = 0x38704 + Menuitem Atom = 0x38708 + Meta Atom = 0x4b804 + Meter Atom = 0x9805 + Method Atom = 0x2a806 + Mglyph Atom = 0x30806 + Mi Atom = 0x34702 + Min Atom = 0x34703 + Minlength Atom = 0x34709 + Mn Atom = 0x2b102 + Mo Atom = 0xa402 + Ms Atom = 0x67402 + Mtext Atom = 0x35105 + Multiple Atom = 0x35f08 + Muted Atom = 0x36705 + Name Atom = 0x9604 + Nav Atom = 0x1303 + Nobr Atom = 0x3704 + Noembed Atom = 0x6c07 + Noframes Atom = 0x8908 + Nomodule Atom = 0xa208 + Nonce Atom = 0x1a605 + Noscript Atom = 0x21608 + Novalidate Atom = 0x2b20a + Object Atom = 0x26806 + Ol Atom = 0x13702 + Onabort Atom = 0x19507 + Onafterprint Atom = 0x2360c + Onautocomplete Atom = 0x2760e + Onautocompleteerror Atom = 0x27613 + Onauxclick Atom = 0x61f0a + Onbeforeprint Atom = 0x69e0d + Onbeforeunload Atom = 0x6e70e + Onblur Atom = 0x56d06 + Oncancel Atom = 0x11908 + Oncanplay Atom = 0x14d09 + Oncanplaythrough Atom = 0x14d10 + Onchange Atom = 0x41b08 + Onclick Atom = 0x2f507 + Onclose Atom = 0x36c07 + Oncontextmenu Atom = 0x37e0d + Oncopy Atom = 0x39106 + Oncuechange Atom = 0x3970b + Oncut Atom = 0x3a205 + Ondblclick Atom = 0x3a70a + Ondrag Atom = 0x3b106 + Ondragend Atom = 0x3b109 + Ondragenter Atom = 0x3ba0b + Ondragexit Atom = 0x3c50a + Ondragleave Atom = 0x3df0b + Ondragover Atom = 0x3ea0a + Ondragstart Atom = 0x3f40b + Ondrop Atom = 0x40306 + Ondurationchange Atom = 0x41310 + Onemptied Atom = 0x40a09 + Onended Atom = 0x42307 + Onerror Atom = 0x42a07 + Onfocus Atom = 0x43107 + Onhashchange Atom = 0x43d0c + Oninput Atom = 0x44907 + Oninvalid Atom = 0x45509 + Onkeydown Atom = 0x45e09 + Onkeypress Atom = 0x46b0a + Onkeyup Atom = 0x48007 + Onlanguagechange Atom = 0x48d10 + Onload Atom = 0x49d06 + Onloadeddata Atom = 0x49d0c + Onloadedmetadata Atom = 0x4b010 + Onloadend Atom = 0x4c609 + Onloadstart Atom = 0x4cf0b + Onmessage Atom = 0x4da09 + Onmessageerror Atom = 0x4da0e + Onmousedown Atom = 0x4e80b + Onmouseenter Atom = 0x4f30c + Onmouseleave Atom = 0x4ff0c + Onmousemove Atom = 0x50b0b + Onmouseout Atom = 0x5160a + Onmouseover Atom = 0x5230b + Onmouseup Atom = 0x52e09 + Onmousewheel Atom = 0x53c0c + Onoffline Atom = 0x54809 + Ononline Atom = 0x55108 + Onpagehide Atom = 0x5590a + Onpageshow Atom = 0x5730a + Onpaste Atom = 0x57f07 + Onpause Atom = 0x59a07 + Onplay Atom = 0x5a406 + Onplaying Atom = 0x5a409 + Onpopstate Atom = 0x5ad0a + Onprogress Atom = 0x5b70a + Onratechange Atom = 0x5cc0c + Onrejectionhandled Atom = 0x5d812 + Onreset Atom = 0x5ea07 + Onresize Atom = 0x5f108 + Onscroll Atom = 0x60008 + Onsecuritypolicyviolation Atom = 0x60819 + Onseeked Atom = 0x62908 + Onseeking Atom = 0x63109 + Onselect Atom = 0x63a08 + Onshow Atom = 0x64406 + Onsort Atom = 0x64f06 + Onstalled Atom = 0x65909 + Onstorage Atom = 0x66209 + Onsubmit Atom = 0x66b08 + Onsuspend Atom = 0x67b09 + Ontimeupdate Atom = 0x400c + Ontoggle Atom = 0x68408 + Onunhandledrejection Atom = 0x68c14 + Onunload Atom = 0x6ab08 + Onvolumechange Atom = 0x6b30e + Onwaiting Atom = 0x6c109 + Onwheel Atom = 0x6ca07 + Open Atom = 0x1a304 + Optgroup Atom = 0x5f08 + Optimum Atom = 0x6d107 + Option Atom = 0x6e306 + Output Atom = 0x51d06 + P Atom = 0xc01 + Param Atom = 0xc05 + Pattern Atom = 0x6607 + Picture Atom = 0x7b07 + Ping Atom = 0xef04 + Placeholder Atom = 0x1310b + Plaintext Atom = 0x1b209 + Playsinline Atom = 0x1400b + Poster Atom = 0x2cf06 + Pre Atom = 0x47003 + Preload Atom = 0x48607 + Progress Atom = 0x5b908 + Prompt Atom = 0x53606 + Public Atom = 0x58606 + Q Atom = 0xcf01 + Radiogroup Atom = 0x30a + Rb Atom = 0x3a02 + Readonly Atom = 0x35708 + Referrerpolicy Atom = 0x3d10e + Rel Atom = 0x48703 + Required Atom = 0x24c08 + Reversed Atom = 0x8008 + Rows Atom = 0x9c04 + Rowspan Atom = 0x9c07 + Rp Atom = 0x23c02 + Rt Atom = 0x19a02 + Rtc Atom = 0x19a03 + Ruby Atom = 0xfb04 + S Atom = 0x2501 + Samp Atom = 0x7804 + Sandbox Atom = 0x12907 + Scope Atom = 0x67505 + Scoped Atom = 0x67506 + Script Atom = 0x21806 + Seamless Atom = 0x37108 + Section Atom = 0x56807 + Select Atom = 0x63c06 + Selected Atom = 0x63c08 + Shape Atom = 0x1e505 + Size Atom = 0x5f504 + Sizes Atom = 0x5f505 + Slot Atom = 0x1ef04 + Small Atom = 0x20605 + Sortable Atom = 0x65108 + Sorted Atom = 0x33706 + Source Atom = 0x37806 + Spacer Atom = 0x43706 + Span Atom = 0x9f04 + Spellcheck Atom = 0x4740a + Src Atom = 0x5c003 + Srcdoc Atom = 0x5c006 + Srclang Atom = 0x5f907 + Srcset Atom = 0x6f906 + Start Atom = 0x3fa05 + Step Atom = 0x58304 + Strike Atom = 0xd206 + Strong Atom = 0x6dd06 + Style Atom = 0x6ff05 + Sub Atom = 0x66d03 + Summary Atom = 0x70407 + Sup Atom = 0x70b03 + Svg Atom = 0x70e03 + System Atom = 0x71106 + Tabindex Atom = 0x4be08 + Table Atom = 0x59505 + Target Atom = 0x2c406 + Tbody Atom = 0x2705 + Td Atom = 0x9202 + Template Atom = 0x71408 + Textarea Atom = 0x35208 + Tfoot Atom = 0xf505 + Th Atom = 0x15602 + Thead Atom = 0x33005 + Time Atom = 0x4204 + Title Atom = 0x11005 + Tr Atom = 0xcc02 + Track Atom = 0x1ba05 + Translate Atom = 0x1f209 + Tt Atom = 0x6802 + Type Atom = 0xd904 + Typemustmatch Atom = 0x2900d + U Atom = 0xb01 + Ul Atom = 0xa702 + Updateviacache Atom = 0x460e + Usemap Atom = 0x59e06 + Value Atom = 0x1505 + Var Atom = 0x16d03 + Video Atom = 0x2f105 + Wbr Atom = 0x57c03 + Width Atom = 0x64905 + Workertype Atom = 0x71c0a + Wrap Atom = 0x72604 + Xmp Atom = 0x12f03 +) + +const hash0 = 0x81cdf10e + +const maxAtomLen = 25 + +var table = [1 << 9]Atom{ + 0x1: 0xe60a, // mediagroup + 0x2: 0x2e404, // lang + 0x4: 0x2c09, // accesskey + 0x5: 0x8b08, // frameset + 0x7: 0x63a08, // onselect + 0x8: 0x71106, // system + 0xa: 0x64905, // width + 0xc: 0x2890b, // formenctype + 0xd: 0x13702, // ol + 0xe: 0x3970b, // oncuechange + 0x10: 0x14b03, // bdo + 0x11: 0x11505, // audio + 0x12: 0x17a09, // draggable + 0x14: 0x2f105, // video + 0x15: 0x2b102, // mn + 0x16: 0x38704, // menu + 0x17: 0x2cf06, // poster + 0x19: 0xf606, // footer + 0x1a: 0x2a806, // method + 0x1b: 0x2b808, // datetime + 0x1c: 0x19507, // onabort + 0x1d: 0x460e, // updateviacache + 0x1e: 0xff05, // async + 0x1f: 0x49d06, // onload + 0x21: 0x11908, // oncancel + 0x22: 0x62908, // onseeked + 0x23: 0x30205, // image + 0x24: 0x5d812, // onrejectionhandled + 0x26: 0x17404, // link + 0x27: 0x51d06, // output + 0x28: 0x33104, // head + 0x29: 0x4ff0c, // onmouseleave + 0x2a: 0x57f07, // onpaste + 0x2b: 0x5a409, // onplaying + 0x2c: 0x1c407, // colspan + 0x2f: 0x1bf05, // color + 0x30: 0x5f504, // size + 0x31: 0x2e80a, // http-equiv + 0x33: 0x601, // i + 0x34: 0x5590a, // onpagehide + 0x35: 0x68c14, // onunhandledrejection + 0x37: 0x42a07, // onerror + 0x3a: 0x3b08, // basefont + 0x3f: 0x1303, // nav + 0x40: 0x17704, // kind + 0x41: 0x35708, // readonly + 0x42: 0x30806, // mglyph + 0x44: 0xb202, // li + 0x46: 0x2d506, // hidden + 0x47: 0x70e03, // svg + 0x48: 0x58304, // step + 0x49: 0x23f09, // integrity + 0x4a: 0x58606, // public + 0x4c: 0x1ab03, // col + 0x4d: 0x1870a, // blockquote + 0x4e: 0x34f02, // h5 + 0x50: 0x5b908, // progress + 0x51: 0x5f505, // sizes + 0x52: 0x34502, // h4 + 0x56: 0x33005, // thead + 0x57: 0xd607, // keytype + 0x58: 0x5b70a, // onprogress + 0x59: 0x44b09, // inputmode + 0x5a: 0x3b109, // ondragend + 0x5d: 0x3a205, // oncut + 0x5e: 0x43706, // spacer + 0x5f: 0x1ab08, // colgroup + 0x62: 0x16502, // is + 0x65: 0x3c02, // as + 0x66: 0x54809, // onoffline + 0x67: 0x33706, // sorted + 0x69: 0x48d10, // onlanguagechange + 0x6c: 0x43d0c, // onhashchange + 0x6d: 0x9604, // name + 0x6e: 0xf505, // tfoot + 0x6f: 0x56104, // desc + 0x70: 0x33d03, // max + 0x72: 0x1ea06, // coords + 0x73: 0x30d02, // h3 + 0x74: 0x6e70e, // onbeforeunload + 0x75: 0x9c04, // rows + 0x76: 0x63c06, // select + 0x77: 0x9805, // meter + 0x78: 0x38b06, // itemid + 0x79: 0x53c0c, // onmousewheel + 0x7a: 0x5c006, // srcdoc + 0x7d: 0x1ba05, // track + 0x7f: 0x31f08, // itemtype + 0x82: 0xa402, // mo + 0x83: 0x41b08, // onchange + 0x84: 0x33107, // headers + 0x85: 0x5cc0c, // onratechange + 0x86: 0x60819, // onsecuritypolicyviolation + 0x88: 0x4a508, // datalist + 0x89: 0x4e80b, // onmousedown + 0x8a: 0x1ef04, // slot + 0x8b: 0x4b010, // onloadedmetadata + 0x8c: 0x1a06, // accept + 0x8d: 0x26806, // object + 0x91: 0x6b30e, // onvolumechange + 0x92: 0x2107, // charset + 0x93: 0x27613, // onautocompleteerror + 0x94: 0xc113, // allowpaymentrequest + 0x95: 0x2804, // body + 0x96: 0x10a07, // default + 0x97: 0x63c08, // selected + 0x98: 0x21e04, // face + 0x99: 0x1e505, // shape + 0x9b: 0x68408, // ontoggle + 0x9e: 0x64b02, // dt + 0x9f: 0xb604, // mark + 0xa1: 0xb01, // u + 0xa4: 0x6ab08, // onunload + 0xa5: 0x5d04, // loop + 0xa6: 0x16408, // disabled + 0xaa: 0x42307, // onended + 0xab: 0xb00a, // malignmark + 0xad: 0x67b09, // onsuspend + 0xae: 0x35105, // mtext + 0xaf: 0x64f06, // onsort + 0xb0: 0x19d08, // itemprop + 0xb3: 0x67109, // itemscope + 0xb4: 0x17305, // blink + 0xb6: 0x3b106, // ondrag + 0xb7: 0xa702, // ul + 0xb8: 0x26e04, // form + 0xb9: 0x12907, // sandbox + 0xba: 0x8b05, // frame + 0xbb: 0x1505, // value + 0xbc: 0x66209, // onstorage + 0xbf: 0xaa07, // acronym + 0xc0: 0x19a02, // rt + 0xc2: 0x202, // br + 0xc3: 0x22608, // fieldset + 0xc4: 0x2900d, // typemustmatch + 0xc5: 0xa208, // nomodule + 0xc6: 0x6c07, // noembed + 0xc7: 0x69e0d, // onbeforeprint + 0xc8: 0x19106, // button + 0xc9: 0x2f507, // onclick + 0xca: 0x70407, // summary + 0xcd: 0xfb04, // ruby + 0xce: 0x56405, // class + 0xcf: 0x3f40b, // ondragstart + 0xd0: 0x23107, // caption + 0xd4: 0xdd0e, // allowusermedia + 0xd5: 0x4cf0b, // onloadstart + 0xd9: 0x16b03, // div + 0xda: 0x4a904, // list + 0xdb: 0x32e04, // math + 0xdc: 0x44b05, // input + 0xdf: 0x3ea0a, // ondragover + 0xe0: 0x2de02, // h2 + 0xe2: 0x1b209, // plaintext + 0xe4: 0x4f30c, // onmouseenter + 0xe7: 0x47907, // checked + 0xe8: 0x47003, // pre + 0xea: 0x35f08, // multiple + 0xeb: 0xba03, // bdi + 0xec: 0x33d09, // maxlength + 0xed: 0xcf01, // q + 0xee: 0x61f0a, // onauxclick + 0xf0: 0x57c03, // wbr + 0xf2: 0x3b04, // base + 0xf3: 0x6e306, // option + 0xf5: 0x41310, // ondurationchange + 0xf7: 0x8908, // noframes + 0xf9: 0x40508, // dropzone + 0xfb: 0x67505, // scope + 0xfc: 0x8008, // reversed + 0xfd: 0x3ba0b, // ondragenter + 0xfe: 0x3fa05, // start + 0xff: 0x12f03, // xmp + 0x100: 0x5f907, // srclang + 0x101: 0x30703, // img + 0x104: 0x101, // b + 0x105: 0x25403, // for + 0x106: 0x10705, // aside + 0x107: 0x44907, // oninput + 0x108: 0x35604, // area + 0x109: 0x2a40a, // formmethod + 0x10a: 0x72604, // wrap + 0x10c: 0x23c02, // rp + 0x10d: 0x46b0a, // onkeypress + 0x10e: 0x6802, // tt + 0x110: 0x34702, // mi + 0x111: 0x36705, // muted + 0x112: 0xf303, // alt + 0x113: 0x5c504, // code + 0x114: 0x6e02, // em + 0x115: 0x3c50a, // ondragexit + 0x117: 0x9f04, // span + 0x119: 0x6d708, // manifest + 0x11a: 0x38708, // menuitem + 0x11b: 0x58b07, // content + 0x11d: 0x6c109, // onwaiting + 0x11f: 0x4c609, // onloadend + 0x121: 0x37e0d, // oncontextmenu + 0x123: 0x56d06, // onblur + 0x124: 0x3fc07, // article + 0x125: 0x9303, // dir + 0x126: 0xef04, // ping + 0x127: 0x24c08, // required + 0x128: 0x45509, // oninvalid + 0x129: 0xb105, // align + 0x12b: 0x58a04, // icon + 0x12c: 0x64d02, // h6 + 0x12d: 0x1c404, // cols + 0x12e: 0x22e0a, // figcaption + 0x12f: 0x45e09, // onkeydown + 0x130: 0x66b08, // onsubmit + 0x131: 0x14d09, // oncanplay + 0x132: 0x70b03, // sup + 0x133: 0xc01, // p + 0x135: 0x40a09, // onemptied + 0x136: 0x39106, // oncopy + 0x137: 0x19c04, // cite + 0x138: 0x3a70a, // ondblclick + 0x13a: 0x50b0b, // onmousemove + 0x13c: 0x66d03, // sub + 0x13d: 0x48703, // rel + 0x13e: 0x5f08, // optgroup + 0x142: 0x9c07, // rowspan + 0x143: 0x37806, // source + 0x144: 0x21608, // noscript + 0x145: 0x1a304, // open + 0x146: 0x20403, // ins + 0x147: 0x2540d, // foreignObject + 0x148: 0x5ad0a, // onpopstate + 0x14a: 0x28d07, // enctype + 0x14b: 0x2760e, // onautocomplete + 0x14c: 0x35208, // textarea + 0x14e: 0x2780c, // autocomplete + 0x14f: 0x15702, // hr + 0x150: 0x1de08, // controls + 0x151: 0x10902, // id + 0x153: 0x2360c, // onafterprint + 0x155: 0x2610d, // foreignobject + 0x156: 0x32707, // marquee + 0x157: 0x59a07, // onpause + 0x158: 0x5e602, // dl + 0x159: 0x5206, // height + 0x15a: 0x34703, // min + 0x15b: 0x9307, // dirname + 0x15c: 0x1f209, // translate + 0x15d: 0x5604, // html + 0x15e: 0x34709, // minlength + 0x15f: 0x48607, // preload + 0x160: 0x71408, // template + 0x161: 0x3df0b, // ondragleave + 0x162: 0x3a02, // rb + 0x164: 0x5c003, // src + 0x165: 0x6dd06, // strong + 0x167: 0x7804, // samp + 0x168: 0x6f307, // address + 0x169: 0x55108, // ononline + 0x16b: 0x1310b, // placeholder + 0x16c: 0x2c406, // target + 0x16d: 0x20605, // small + 0x16e: 0x6ca07, // onwheel + 0x16f: 0x1c90a, // annotation + 0x170: 0x4740a, // spellcheck + 0x171: 0x7207, // details + 0x172: 0x10306, // canvas + 0x173: 0x12109, // autofocus + 0x174: 0xc05, // param + 0x176: 0x46308, // download + 0x177: 0x45203, // del + 0x178: 0x36c07, // onclose + 0x179: 0xb903, // kbd + 0x17a: 0x31906, // applet + 0x17b: 0x2e004, // href + 0x17c: 0x5f108, // onresize + 0x17e: 0x49d0c, // onloadeddata + 0x180: 0xcc02, // tr + 0x181: 0x2c00a, // formtarget + 0x182: 0x11005, // title + 0x183: 0x6ff05, // style + 0x184: 0xd206, // strike + 0x185: 0x59e06, // usemap + 0x186: 0x2fc06, // iframe + 0x187: 0x1004, // main + 0x189: 0x7b07, // picture + 0x18c: 0x31605, // ismap + 0x18e: 0x4a504, // data + 0x18f: 0x5905, // label + 0x191: 0x3d10e, // referrerpolicy + 0x192: 0x15602, // th + 0x194: 0x53606, // prompt + 0x195: 0x56807, // section + 0x197: 0x6d107, // optimum + 0x198: 0x2db04, // high + 0x199: 0x15c02, // h1 + 0x19a: 0x65909, // onstalled + 0x19b: 0x16d03, // var + 0x19c: 0x4204, // time + 0x19e: 0x67402, // ms + 0x19f: 0x33106, // header + 0x1a0: 0x4da09, // onmessage + 0x1a1: 0x1a605, // nonce + 0x1a2: 0x26e0a, // formaction + 0x1a3: 0x22006, // center + 0x1a4: 0x3704, // nobr + 0x1a5: 0x59505, // table + 0x1a6: 0x4a907, // listing + 0x1a7: 0x18106, // legend + 0x1a9: 0x29b09, // challenge + 0x1aa: 0x24806, // figure + 0x1ab: 0xe605, // media + 0x1ae: 0xd904, // type + 0x1af: 0x3f04, // font + 0x1b0: 0x4da0e, // onmessageerror + 0x1b1: 0x37108, // seamless + 0x1b2: 0x8703, // dfn + 0x1b3: 0x5c705, // defer + 0x1b4: 0xc303, // low + 0x1b5: 0x19a03, // rtc + 0x1b6: 0x5230b, // onmouseover + 0x1b7: 0x2b20a, // novalidate + 0x1b8: 0x71c0a, // workertype + 0x1ba: 0x3cd07, // itemref + 0x1bd: 0x1, // a + 0x1be: 0x31803, // map + 0x1bf: 0x400c, // ontimeupdate + 0x1c0: 0x15e07, // bgsound + 0x1c1: 0x3206, // keygen + 0x1c2: 0x2705, // tbody + 0x1c5: 0x64406, // onshow + 0x1c7: 0x2501, // s + 0x1c8: 0x6607, // pattern + 0x1cc: 0x14d10, // oncanplaythrough + 0x1ce: 0x2d702, // dd + 0x1cf: 0x6f906, // srcset + 0x1d0: 0x17003, // big + 0x1d2: 0x65108, // sortable + 0x1d3: 0x48007, // onkeyup + 0x1d5: 0x5a406, // onplay + 0x1d7: 0x4b804, // meta + 0x1d8: 0x40306, // ondrop + 0x1da: 0x60008, // onscroll + 0x1db: 0x1fb0b, // crossorigin + 0x1dc: 0x5730a, // onpageshow + 0x1dd: 0x4, // abbr + 0x1de: 0x9202, // td + 0x1df: 0x58b0f, // contenteditable + 0x1e0: 0x27206, // action + 0x1e1: 0x1400b, // playsinline + 0x1e2: 0x43107, // onfocus + 0x1e3: 0x2e008, // hreflang + 0x1e5: 0x5160a, // onmouseout + 0x1e6: 0x5ea07, // onreset + 0x1e7: 0x13c08, // autoplay + 0x1e8: 0x63109, // onseeking + 0x1ea: 0x67506, // scoped + 0x1ec: 0x30a, // radiogroup + 0x1ee: 0x3800b, // contextmenu + 0x1ef: 0x52e09, // onmouseup + 0x1f1: 0x2ca06, // hgroup + 0x1f2: 0x2080f, // allowfullscreen + 0x1f3: 0x4be08, // tabindex + 0x1f6: 0x30f07, // isindex + 0x1f7: 0x1a0e, // accept-charset + 0x1f8: 0x2ae0e, // formnovalidate + 0x1fb: 0x1c90e, // annotation-xml + 0x1fc: 0x6e05, // embed + 0x1fd: 0x21806, // script + 0x1fe: 0xbb06, // dialog + 0x1ff: 0x1d707, // command +} + +const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" + + "asefontimeupdateviacacheightmlabelooptgroupatternoembedetail" + + "sampictureversedfnoframesetdirnameterowspanomoduleacronymali" + + "gnmarkbdialogallowpaymentrequestrikeytypeallowusermediagroup" + + "ingaltfooterubyasyncanvasidefaultitleaudioncancelautofocusan" + + "dboxmplaceholderautoplaysinlinebdoncanplaythrough1bgsoundisa" + + "bledivarbigblinkindraggablegendblockquotebuttonabortcitempro" + + "penoncecolgrouplaintextrackcolorcolspannotation-xmlcommandco" + + "ntrolshapecoordslotranslatecrossoriginsmallowfullscreenoscri" + + "ptfacenterfieldsetfigcaptionafterprintegrityfigurequiredfore" + + "ignObjectforeignobjectformactionautocompleteerrorformenctype" + + "mustmatchallengeformmethodformnovalidatetimeformtargethgroup" + + "osterhiddenhigh2hreflanghttp-equivideonclickiframeimageimgly" + + "ph3isindexismappletitemtypemarqueematheadersortedmaxlength4m" + + "inlength5mtextareadonlymultiplemutedoncloseamlessourceoncont" + + "extmenuitemidoncopyoncuechangeoncutondblclickondragendondrag" + + "enterondragexitemreferrerpolicyondragleaveondragoverondragst" + + "articleondropzonemptiedondurationchangeonendedonerroronfocus" + + "paceronhashchangeoninputmodeloninvalidonkeydownloadonkeypres" + + "spellcheckedonkeyupreloadonlanguagechangeonloadeddatalisting" + + "onloadedmetadatabindexonloadendonloadstartonmessageerroronmo" + + "usedownonmouseenteronmouseleaveonmousemoveonmouseoutputonmou" + + "seoveronmouseupromptonmousewheelonofflineononlineonpagehides" + + "classectionbluronpageshowbronpastepublicontenteditableonpaus" + + "emaponplayingonpopstateonprogressrcdocodeferonratechangeonre" + + "jectionhandledonresetonresizesrclangonscrollonsecuritypolicy" + + "violationauxclickonseekedonseekingonselectedonshowidth6onsor" + + "tableonstalledonstorageonsubmitemscopedonsuspendontoggleonun" + + "handledrejectionbeforeprintonunloadonvolumechangeonwaitingon" + + "wheeloptimumanifestrongoptionbeforeunloaddressrcsetstylesumm" + + "arysupsvgsystemplateworkertypewrap" diff --git a/vendor/golang.org/x/net/html/charset/charset.go b/vendor/golang.org/x/net/html/charset/charset.go new file mode 100644 index 00000000..13bed159 --- /dev/null +++ b/vendor/golang.org/x/net/html/charset/charset.go @@ -0,0 +1,257 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package charset provides common text encodings for HTML documents. +// +// The mapping from encoding labels to encodings is defined at +// https://encoding.spec.whatwg.org/. +package charset // import "golang.org/x/net/html/charset" + +import ( + "bytes" + "fmt" + "io" + "mime" + "strings" + "unicode/utf8" + + "golang.org/x/net/html" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/encoding/htmlindex" + "golang.org/x/text/transform" +) + +// Lookup returns the encoding with the specified label, and its canonical +// name. It returns nil and the empty string if label is not one of the +// standard encodings for HTML. Matching is case-insensitive and ignores +// leading and trailing whitespace. Encoders will use HTML escape sequences for +// runes that are not supported by the character set. +func Lookup(label string) (e encoding.Encoding, name string) { + e, err := htmlindex.Get(label) + if err != nil { + return nil, "" + } + name, _ = htmlindex.Name(e) + return &htmlEncoding{e}, name +} + +type htmlEncoding struct{ encoding.Encoding } + +func (h *htmlEncoding) NewEncoder() *encoding.Encoder { + // HTML requires a non-terminating legacy encoder. We use HTML escapes to + // substitute unsupported code points. + return encoding.HTMLEscapeUnsupported(h.Encoding.NewEncoder()) +} + +// DetermineEncoding determines the encoding of an HTML document by examining +// up to the first 1024 bytes of content and the declared Content-Type. +// +// See http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#determining-the-character-encoding +func DetermineEncoding(content []byte, contentType string) (e encoding.Encoding, name string, certain bool) { + if len(content) > 1024 { + content = content[:1024] + } + + for _, b := range boms { + if bytes.HasPrefix(content, b.bom) { + e, name = Lookup(b.enc) + return e, name, true + } + } + + if _, params, err := mime.ParseMediaType(contentType); err == nil { + if cs, ok := params["charset"]; ok { + if e, name = Lookup(cs); e != nil { + return e, name, true + } + } + } + + if len(content) > 0 { + e, name = prescan(content) + if e != nil { + return e, name, false + } + } + + // Try to detect UTF-8. + // First eliminate any partial rune at the end. + for i := len(content) - 1; i >= 0 && i > len(content)-4; i-- { + b := content[i] + if b < 0x80 { + break + } + if utf8.RuneStart(b) { + content = content[:i] + break + } + } + hasHighBit := false + for _, c := range content { + if c >= 0x80 { + hasHighBit = true + break + } + } + if hasHighBit && utf8.Valid(content) { + return encoding.Nop, "utf-8", false + } + + // TODO: change default depending on user's locale? + return charmap.Windows1252, "windows-1252", false +} + +// NewReader returns an io.Reader that converts the content of r to UTF-8. +// It calls DetermineEncoding to find out what r's encoding is. +func NewReader(r io.Reader, contentType string) (io.Reader, error) { + preview := make([]byte, 1024) + n, err := io.ReadFull(r, preview) + switch { + case err == io.ErrUnexpectedEOF: + preview = preview[:n] + r = bytes.NewReader(preview) + case err != nil: + return nil, err + default: + r = io.MultiReader(bytes.NewReader(preview), r) + } + + if e, _, _ := DetermineEncoding(preview, contentType); e != encoding.Nop { + r = transform.NewReader(r, e.NewDecoder()) + } + return r, nil +} + +// NewReaderLabel returns a reader that converts from the specified charset to +// UTF-8. It uses Lookup to find the encoding that corresponds to label, and +// returns an error if Lookup returns nil. It is suitable for use as +// encoding/xml.Decoder's CharsetReader function. +func NewReaderLabel(label string, input io.Reader) (io.Reader, error) { + e, _ := Lookup(label) + if e == nil { + return nil, fmt.Errorf("unsupported charset: %q", label) + } + return transform.NewReader(input, e.NewDecoder()), nil +} + +func prescan(content []byte) (e encoding.Encoding, name string) { + z := html.NewTokenizer(bytes.NewReader(content)) + for { + switch z.Next() { + case html.ErrorToken: + return nil, "" + + case html.StartTagToken, html.SelfClosingTagToken: + tagName, hasAttr := z.TagName() + if !bytes.Equal(tagName, []byte("meta")) { + continue + } + attrList := make(map[string]bool) + gotPragma := false + + const ( + dontKnow = iota + doNeedPragma + doNotNeedPragma + ) + needPragma := dontKnow + + name = "" + e = nil + for hasAttr { + var key, val []byte + key, val, hasAttr = z.TagAttr() + ks := string(key) + if attrList[ks] { + continue + } + attrList[ks] = true + for i, c := range val { + if 'A' <= c && c <= 'Z' { + val[i] = c + 0x20 + } + } + + switch ks { + case "http-equiv": + if bytes.Equal(val, []byte("content-type")) { + gotPragma = true + } + + case "content": + if e == nil { + name = fromMetaElement(string(val)) + if name != "" { + e, name = Lookup(name) + if e != nil { + needPragma = doNeedPragma + } + } + } + + case "charset": + e, name = Lookup(string(val)) + needPragma = doNotNeedPragma + } + } + + if needPragma == dontKnow || needPragma == doNeedPragma && !gotPragma { + continue + } + + if strings.HasPrefix(name, "utf-16") { + name = "utf-8" + e = encoding.Nop + } + + if e != nil { + return e, name + } + } + } +} + +func fromMetaElement(s string) string { + for s != "" { + csLoc := strings.Index(s, "charset") + if csLoc == -1 { + return "" + } + s = s[csLoc+len("charset"):] + s = strings.TrimLeft(s, " \t\n\f\r") + if !strings.HasPrefix(s, "=") { + continue + } + s = s[1:] + s = strings.TrimLeft(s, " \t\n\f\r") + if s == "" { + return "" + } + if q := s[0]; q == '"' || q == '\'' { + s = s[1:] + closeQuote := strings.IndexRune(s, rune(q)) + if closeQuote == -1 { + return "" + } + return s[:closeQuote] + } + + end := strings.IndexAny(s, "; \t\n\f\r") + if end == -1 { + end = len(s) + } + return s[:end] + } + return "" +} + +var boms = []struct { + bom []byte + enc string +}{ + {[]byte{0xfe, 0xff}, "utf-16be"}, + {[]byte{0xff, 0xfe}, "utf-16le"}, + {[]byte{0xef, 0xbb, 0xbf}, "utf-8"}, +} diff --git a/vendor/golang.org/x/net/html/const.go b/vendor/golang.org/x/net/html/const.go new file mode 100644 index 00000000..ff7acf2d --- /dev/null +++ b/vendor/golang.org/x/net/html/const.go @@ -0,0 +1,111 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +// Section 12.2.4.2 of the HTML5 specification says "The following elements +// have varying levels of special parsing rules". +// https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements +var isSpecialElementMap = map[string]bool{ + "address": true, + "applet": true, + "area": true, + "article": true, + "aside": true, + "base": true, + "basefont": true, + "bgsound": true, + "blockquote": true, + "body": true, + "br": true, + "button": true, + "caption": true, + "center": true, + "col": true, + "colgroup": true, + "dd": true, + "details": true, + "dir": true, + "div": true, + "dl": true, + "dt": true, + "embed": true, + "fieldset": true, + "figcaption": true, + "figure": true, + "footer": true, + "form": true, + "frame": true, + "frameset": true, + "h1": true, + "h2": true, + "h3": true, + "h4": true, + "h5": true, + "h6": true, + "head": true, + "header": true, + "hgroup": true, + "hr": true, + "html": true, + "iframe": true, + "img": true, + "input": true, + "keygen": true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility. + "li": true, + "link": true, + "listing": true, + "main": true, + "marquee": true, + "menu": true, + "meta": true, + "nav": true, + "noembed": true, + "noframes": true, + "noscript": true, + "object": true, + "ol": true, + "p": true, + "param": true, + "plaintext": true, + "pre": true, + "script": true, + "section": true, + "select": true, + "source": true, + "style": true, + "summary": true, + "table": true, + "tbody": true, + "td": true, + "template": true, + "textarea": true, + "tfoot": true, + "th": true, + "thead": true, + "title": true, + "tr": true, + "track": true, + "ul": true, + "wbr": true, + "xmp": true, +} + +func isSpecialElement(element *Node) bool { + switch element.Namespace { + case "", "html": + return isSpecialElementMap[element.Data] + case "math": + switch element.Data { + case "mi", "mo", "mn", "ms", "mtext", "annotation-xml": + return true + } + case "svg": + switch element.Data { + case "foreignObject", "desc", "title": + return true + } + } + return false +} diff --git a/vendor/golang.org/x/net/html/doc.go b/vendor/golang.org/x/net/html/doc.go new file mode 100644 index 00000000..822ed42a --- /dev/null +++ b/vendor/golang.org/x/net/html/doc.go @@ -0,0 +1,106 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package html implements an HTML5-compliant tokenizer and parser. + +Tokenization is done by creating a Tokenizer for an io.Reader r. It is the +caller's responsibility to ensure that r provides UTF-8 encoded HTML. + + z := html.NewTokenizer(r) + +Given a Tokenizer z, the HTML is tokenized by repeatedly calling z.Next(), +which parses the next token and returns its type, or an error: + + for { + tt := z.Next() + if tt == html.ErrorToken { + // ... + return ... + } + // Process the current token. + } + +There are two APIs for retrieving the current token. The high-level API is to +call Token; the low-level API is to call Text or TagName / TagAttr. Both APIs +allow optionally calling Raw after Next but before Token, Text, TagName, or +TagAttr. In EBNF notation, the valid call sequence per token is: + + Next {Raw} [ Token | Text | TagName {TagAttr} ] + +Token returns an independent data structure that completely describes a token. +Entities (such as "<") are unescaped, tag names and attribute keys are +lower-cased, and attributes are collected into a []Attribute. For example: + + for { + if z.Next() == html.ErrorToken { + // Returning io.EOF indicates success. + return z.Err() + } + emitToken(z.Token()) + } + +The low-level API performs fewer allocations and copies, but the contents of +the []byte values returned by Text, TagName and TagAttr may change on the next +call to Next. For example, to extract an HTML page's anchor text: + + depth := 0 + for { + tt := z.Next() + switch tt { + case html.ErrorToken: + return z.Err() + case html.TextToken: + if depth > 0 { + // emitBytes should copy the []byte it receives, + // if it doesn't process it immediately. + emitBytes(z.Text()) + } + case html.StartTagToken, html.EndTagToken: + tn, _ := z.TagName() + if len(tn) == 1 && tn[0] == 'a' { + if tt == html.StartTagToken { + depth++ + } else { + depth-- + } + } + } + } + +Parsing is done by calling Parse with an io.Reader, which returns the root of +the parse tree (the document element) as a *Node. It is the caller's +responsibility to ensure that the Reader provides UTF-8 encoded HTML. For +example, to process each anchor node in depth-first order: + + doc, err := html.Parse(r) + if err != nil { + // ... + } + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "a" { + // Do something with n... + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + +The relevant specifications include: +https://html.spec.whatwg.org/multipage/syntax.html and +https://html.spec.whatwg.org/multipage/syntax.html#tokenization +*/ +package html // import "golang.org/x/net/html" + +// The tokenization algorithm implemented by this package is not a line-by-line +// transliteration of the relatively verbose state-machine in the WHATWG +// specification. A more direct approach is used instead, where the program +// counter implies the state, such as whether it is tokenizing a tag or a text +// node. Specification compliance is verified by checking expected and actual +// outputs over a test suite rather than aiming for algorithmic fidelity. + +// TODO(nigeltao): Does a DOM API belong in this package or a separate one? +// TODO(nigeltao): How does parsing interact with a JavaScript engine? diff --git a/vendor/golang.org/x/net/html/doctype.go b/vendor/golang.org/x/net/html/doctype.go new file mode 100644 index 00000000..c484e5a9 --- /dev/null +++ b/vendor/golang.org/x/net/html/doctype.go @@ -0,0 +1,156 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "strings" +) + +// parseDoctype parses the data from a DoctypeToken into a name, +// public identifier, and system identifier. It returns a Node whose Type +// is DoctypeNode, whose Data is the name, and which has attributes +// named "system" and "public" for the two identifiers if they were present. +// quirks is whether the document should be parsed in "quirks mode". +func parseDoctype(s string) (n *Node, quirks bool) { + n = &Node{Type: DoctypeNode} + + // Find the name. + space := strings.IndexAny(s, whitespace) + if space == -1 { + space = len(s) + } + n.Data = s[:space] + // The comparison to "html" is case-sensitive. + if n.Data != "html" { + quirks = true + } + n.Data = strings.ToLower(n.Data) + s = strings.TrimLeft(s[space:], whitespace) + + if len(s) < 6 { + // It can't start with "PUBLIC" or "SYSTEM". + // Ignore the rest of the string. + return n, quirks || s != "" + } + + key := strings.ToLower(s[:6]) + s = s[6:] + for key == "public" || key == "system" { + s = strings.TrimLeft(s, whitespace) + if s == "" { + break + } + quote := s[0] + if quote != '"' && quote != '\'' { + break + } + s = s[1:] + q := strings.IndexRune(s, rune(quote)) + var id string + if q == -1 { + id = s + s = "" + } else { + id = s[:q] + s = s[q+1:] + } + n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) + if key == "public" { + key = "system" + } else { + key = "" + } + } + + if key != "" || s != "" { + quirks = true + } else if len(n.Attr) > 0 { + if n.Attr[0].Key == "public" { + public := strings.ToLower(n.Attr[0].Val) + switch public { + case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html": + quirks = true + default: + for _, q := range quirkyIDs { + if strings.HasPrefix(public, q) { + quirks = true + break + } + } + } + // The following two public IDs only cause quirks mode if there is no system ID. + if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") || + strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) { + quirks = true + } + } + if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && + strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { + quirks = true + } + } + + return n, quirks +} + +// quirkyIDs is a list of public doctype identifiers that cause a document +// to be interpreted in quirks mode. The identifiers should be in lower case. +var quirkyIDs = []string{ + "+//silmaril//dtd html pro v0r11 19970101//", + "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", + "-//as//dtd html 3.0 aswedit + extensions//", + "-//ietf//dtd html 2.0 level 1//", + "-//ietf//dtd html 2.0 level 2//", + "-//ietf//dtd html 2.0 strict level 1//", + "-//ietf//dtd html 2.0 strict level 2//", + "-//ietf//dtd html 2.0 strict//", + "-//ietf//dtd html 2.0//", + "-//ietf//dtd html 2.1e//", + "-//ietf//dtd html 3.0//", + "-//ietf//dtd html 3.2 final//", + "-//ietf//dtd html 3.2//", + "-//ietf//dtd html 3//", + "-//ietf//dtd html level 0//", + "-//ietf//dtd html level 1//", + "-//ietf//dtd html level 2//", + "-//ietf//dtd html level 3//", + "-//ietf//dtd html strict level 0//", + "-//ietf//dtd html strict level 1//", + "-//ietf//dtd html strict level 2//", + "-//ietf//dtd html strict level 3//", + "-//ietf//dtd html strict//", + "-//ietf//dtd html//", + "-//metrius//dtd metrius presentational//", + "-//microsoft//dtd internet explorer 2.0 html strict//", + "-//microsoft//dtd internet explorer 2.0 html//", + "-//microsoft//dtd internet explorer 2.0 tables//", + "-//microsoft//dtd internet explorer 3.0 html strict//", + "-//microsoft//dtd internet explorer 3.0 html//", + "-//microsoft//dtd internet explorer 3.0 tables//", + "-//netscape comm. corp.//dtd html//", + "-//netscape comm. corp.//dtd strict html//", + "-//o'reilly and associates//dtd html 2.0//", + "-//o'reilly and associates//dtd html extended 1.0//", + "-//o'reilly and associates//dtd html extended relaxed 1.0//", + "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", + "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", + "-//spyglass//dtd html 2.0 extended//", + "-//sq//dtd html 2.0 hotmetal + extensions//", + "-//sun microsystems corp.//dtd hotjava html//", + "-//sun microsystems corp.//dtd hotjava strict html//", + "-//w3c//dtd html 3 1995-03-24//", + "-//w3c//dtd html 3.2 draft//", + "-//w3c//dtd html 3.2 final//", + "-//w3c//dtd html 3.2//", + "-//w3c//dtd html 3.2s draft//", + "-//w3c//dtd html 4.0 frameset//", + "-//w3c//dtd html 4.0 transitional//", + "-//w3c//dtd html experimental 19960712//", + "-//w3c//dtd html experimental 970421//", + "-//w3c//dtd w3 html//", + "-//w3o//dtd w3 html 3.0//", + "-//webtechs//dtd mozilla html 2.0//", + "-//webtechs//dtd mozilla html//", +} diff --git a/vendor/golang.org/x/net/html/entity.go b/vendor/golang.org/x/net/html/entity.go new file mode 100644 index 00000000..b628880a --- /dev/null +++ b/vendor/golang.org/x/net/html/entity.go @@ -0,0 +1,2253 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +// All entities that do not end with ';' are 6 or fewer bytes long. +const longestEntityWithoutSemicolon = 6 + +// entity is a map from HTML entity names to their values. The semicolon matters: +// https://html.spec.whatwg.org/multipage/syntax.html#named-character-references +// lists both "amp" and "amp;" as two separate entries. +// +// Note that the HTML5 list is larger than the HTML4 list at +// http://www.w3.org/TR/html4/sgml/entities.html +var entity = map[string]rune{ + "AElig;": '\U000000C6', + "AMP;": '\U00000026', + "Aacute;": '\U000000C1', + "Abreve;": '\U00000102', + "Acirc;": '\U000000C2', + "Acy;": '\U00000410', + "Afr;": '\U0001D504', + "Agrave;": '\U000000C0', + "Alpha;": '\U00000391', + "Amacr;": '\U00000100', + "And;": '\U00002A53', + "Aogon;": '\U00000104', + "Aopf;": '\U0001D538', + "ApplyFunction;": '\U00002061', + "Aring;": '\U000000C5', + "Ascr;": '\U0001D49C', + "Assign;": '\U00002254', + "Atilde;": '\U000000C3', + "Auml;": '\U000000C4', + "Backslash;": '\U00002216', + "Barv;": '\U00002AE7', + "Barwed;": '\U00002306', + "Bcy;": '\U00000411', + "Because;": '\U00002235', + "Bernoullis;": '\U0000212C', + "Beta;": '\U00000392', + "Bfr;": '\U0001D505', + "Bopf;": '\U0001D539', + "Breve;": '\U000002D8', + "Bscr;": '\U0000212C', + "Bumpeq;": '\U0000224E', + "CHcy;": '\U00000427', + "COPY;": '\U000000A9', + "Cacute;": '\U00000106', + "Cap;": '\U000022D2', + "CapitalDifferentialD;": '\U00002145', + "Cayleys;": '\U0000212D', + "Ccaron;": '\U0000010C', + "Ccedil;": '\U000000C7', + "Ccirc;": '\U00000108', + "Cconint;": '\U00002230', + "Cdot;": '\U0000010A', + "Cedilla;": '\U000000B8', + "CenterDot;": '\U000000B7', + "Cfr;": '\U0000212D', + "Chi;": '\U000003A7', + "CircleDot;": '\U00002299', + "CircleMinus;": '\U00002296', + "CirclePlus;": '\U00002295', + "CircleTimes;": '\U00002297', + "ClockwiseContourIntegral;": '\U00002232', + "CloseCurlyDoubleQuote;": '\U0000201D', + "CloseCurlyQuote;": '\U00002019', + "Colon;": '\U00002237', + "Colone;": '\U00002A74', + "Congruent;": '\U00002261', + "Conint;": '\U0000222F', + "ContourIntegral;": '\U0000222E', + "Copf;": '\U00002102', + "Coproduct;": '\U00002210', + "CounterClockwiseContourIntegral;": '\U00002233', + "Cross;": '\U00002A2F', + "Cscr;": '\U0001D49E', + "Cup;": '\U000022D3', + "CupCap;": '\U0000224D', + "DD;": '\U00002145', + "DDotrahd;": '\U00002911', + "DJcy;": '\U00000402', + "DScy;": '\U00000405', + "DZcy;": '\U0000040F', + "Dagger;": '\U00002021', + "Darr;": '\U000021A1', + "Dashv;": '\U00002AE4', + "Dcaron;": '\U0000010E', + "Dcy;": '\U00000414', + "Del;": '\U00002207', + "Delta;": '\U00000394', + "Dfr;": '\U0001D507', + "DiacriticalAcute;": '\U000000B4', + "DiacriticalDot;": '\U000002D9', + "DiacriticalDoubleAcute;": '\U000002DD', + "DiacriticalGrave;": '\U00000060', + "DiacriticalTilde;": '\U000002DC', + "Diamond;": '\U000022C4', + "DifferentialD;": '\U00002146', + "Dopf;": '\U0001D53B', + "Dot;": '\U000000A8', + "DotDot;": '\U000020DC', + "DotEqual;": '\U00002250', + "DoubleContourIntegral;": '\U0000222F', + "DoubleDot;": '\U000000A8', + "DoubleDownArrow;": '\U000021D3', + "DoubleLeftArrow;": '\U000021D0', + "DoubleLeftRightArrow;": '\U000021D4', + "DoubleLeftTee;": '\U00002AE4', + "DoubleLongLeftArrow;": '\U000027F8', + "DoubleLongLeftRightArrow;": '\U000027FA', + "DoubleLongRightArrow;": '\U000027F9', + "DoubleRightArrow;": '\U000021D2', + "DoubleRightTee;": '\U000022A8', + "DoubleUpArrow;": '\U000021D1', + "DoubleUpDownArrow;": '\U000021D5', + "DoubleVerticalBar;": '\U00002225', + "DownArrow;": '\U00002193', + "DownArrowBar;": '\U00002913', + "DownArrowUpArrow;": '\U000021F5', + "DownBreve;": '\U00000311', + "DownLeftRightVector;": '\U00002950', + "DownLeftTeeVector;": '\U0000295E', + "DownLeftVector;": '\U000021BD', + "DownLeftVectorBar;": '\U00002956', + "DownRightTeeVector;": '\U0000295F', + "DownRightVector;": '\U000021C1', + "DownRightVectorBar;": '\U00002957', + "DownTee;": '\U000022A4', + "DownTeeArrow;": '\U000021A7', + "Downarrow;": '\U000021D3', + "Dscr;": '\U0001D49F', + "Dstrok;": '\U00000110', + "ENG;": '\U0000014A', + "ETH;": '\U000000D0', + "Eacute;": '\U000000C9', + "Ecaron;": '\U0000011A', + "Ecirc;": '\U000000CA', + "Ecy;": '\U0000042D', + "Edot;": '\U00000116', + "Efr;": '\U0001D508', + "Egrave;": '\U000000C8', + "Element;": '\U00002208', + "Emacr;": '\U00000112', + "EmptySmallSquare;": '\U000025FB', + "EmptyVerySmallSquare;": '\U000025AB', + "Eogon;": '\U00000118', + "Eopf;": '\U0001D53C', + "Epsilon;": '\U00000395', + "Equal;": '\U00002A75', + "EqualTilde;": '\U00002242', + "Equilibrium;": '\U000021CC', + "Escr;": '\U00002130', + "Esim;": '\U00002A73', + "Eta;": '\U00000397', + "Euml;": '\U000000CB', + "Exists;": '\U00002203', + "ExponentialE;": '\U00002147', + "Fcy;": '\U00000424', + "Ffr;": '\U0001D509', + "FilledSmallSquare;": '\U000025FC', + "FilledVerySmallSquare;": '\U000025AA', + "Fopf;": '\U0001D53D', + "ForAll;": '\U00002200', + "Fouriertrf;": '\U00002131', + "Fscr;": '\U00002131', + "GJcy;": '\U00000403', + "GT;": '\U0000003E', + "Gamma;": '\U00000393', + "Gammad;": '\U000003DC', + "Gbreve;": '\U0000011E', + "Gcedil;": '\U00000122', + "Gcirc;": '\U0000011C', + "Gcy;": '\U00000413', + "Gdot;": '\U00000120', + "Gfr;": '\U0001D50A', + "Gg;": '\U000022D9', + "Gopf;": '\U0001D53E', + "GreaterEqual;": '\U00002265', + "GreaterEqualLess;": '\U000022DB', + "GreaterFullEqual;": '\U00002267', + "GreaterGreater;": '\U00002AA2', + "GreaterLess;": '\U00002277', + "GreaterSlantEqual;": '\U00002A7E', + "GreaterTilde;": '\U00002273', + "Gscr;": '\U0001D4A2', + "Gt;": '\U0000226B', + "HARDcy;": '\U0000042A', + "Hacek;": '\U000002C7', + "Hat;": '\U0000005E', + "Hcirc;": '\U00000124', + "Hfr;": '\U0000210C', + "HilbertSpace;": '\U0000210B', + "Hopf;": '\U0000210D', + "HorizontalLine;": '\U00002500', + "Hscr;": '\U0000210B', + "Hstrok;": '\U00000126', + "HumpDownHump;": '\U0000224E', + "HumpEqual;": '\U0000224F', + "IEcy;": '\U00000415', + "IJlig;": '\U00000132', + "IOcy;": '\U00000401', + "Iacute;": '\U000000CD', + "Icirc;": '\U000000CE', + "Icy;": '\U00000418', + "Idot;": '\U00000130', + "Ifr;": '\U00002111', + "Igrave;": '\U000000CC', + "Im;": '\U00002111', + "Imacr;": '\U0000012A', + "ImaginaryI;": '\U00002148', + "Implies;": '\U000021D2', + "Int;": '\U0000222C', + "Integral;": '\U0000222B', + "Intersection;": '\U000022C2', + "InvisibleComma;": '\U00002063', + "InvisibleTimes;": '\U00002062', + "Iogon;": '\U0000012E', + "Iopf;": '\U0001D540', + "Iota;": '\U00000399', + "Iscr;": '\U00002110', + "Itilde;": '\U00000128', + "Iukcy;": '\U00000406', + "Iuml;": '\U000000CF', + "Jcirc;": '\U00000134', + "Jcy;": '\U00000419', + "Jfr;": '\U0001D50D', + "Jopf;": '\U0001D541', + "Jscr;": '\U0001D4A5', + "Jsercy;": '\U00000408', + "Jukcy;": '\U00000404', + "KHcy;": '\U00000425', + "KJcy;": '\U0000040C', + "Kappa;": '\U0000039A', + "Kcedil;": '\U00000136', + "Kcy;": '\U0000041A', + "Kfr;": '\U0001D50E', + "Kopf;": '\U0001D542', + "Kscr;": '\U0001D4A6', + "LJcy;": '\U00000409', + "LT;": '\U0000003C', + "Lacute;": '\U00000139', + "Lambda;": '\U0000039B', + "Lang;": '\U000027EA', + "Laplacetrf;": '\U00002112', + "Larr;": '\U0000219E', + "Lcaron;": '\U0000013D', + "Lcedil;": '\U0000013B', + "Lcy;": '\U0000041B', + "LeftAngleBracket;": '\U000027E8', + "LeftArrow;": '\U00002190', + "LeftArrowBar;": '\U000021E4', + "LeftArrowRightArrow;": '\U000021C6', + "LeftCeiling;": '\U00002308', + "LeftDoubleBracket;": '\U000027E6', + "LeftDownTeeVector;": '\U00002961', + "LeftDownVector;": '\U000021C3', + "LeftDownVectorBar;": '\U00002959', + "LeftFloor;": '\U0000230A', + "LeftRightArrow;": '\U00002194', + "LeftRightVector;": '\U0000294E', + "LeftTee;": '\U000022A3', + "LeftTeeArrow;": '\U000021A4', + "LeftTeeVector;": '\U0000295A', + "LeftTriangle;": '\U000022B2', + "LeftTriangleBar;": '\U000029CF', + "LeftTriangleEqual;": '\U000022B4', + "LeftUpDownVector;": '\U00002951', + "LeftUpTeeVector;": '\U00002960', + "LeftUpVector;": '\U000021BF', + "LeftUpVectorBar;": '\U00002958', + "LeftVector;": '\U000021BC', + "LeftVectorBar;": '\U00002952', + "Leftarrow;": '\U000021D0', + "Leftrightarrow;": '\U000021D4', + "LessEqualGreater;": '\U000022DA', + "LessFullEqual;": '\U00002266', + "LessGreater;": '\U00002276', + "LessLess;": '\U00002AA1', + "LessSlantEqual;": '\U00002A7D', + "LessTilde;": '\U00002272', + "Lfr;": '\U0001D50F', + "Ll;": '\U000022D8', + "Lleftarrow;": '\U000021DA', + "Lmidot;": '\U0000013F', + "LongLeftArrow;": '\U000027F5', + "LongLeftRightArrow;": '\U000027F7', + "LongRightArrow;": '\U000027F6', + "Longleftarrow;": '\U000027F8', + "Longleftrightarrow;": '\U000027FA', + "Longrightarrow;": '\U000027F9', + "Lopf;": '\U0001D543', + "LowerLeftArrow;": '\U00002199', + "LowerRightArrow;": '\U00002198', + "Lscr;": '\U00002112', + "Lsh;": '\U000021B0', + "Lstrok;": '\U00000141', + "Lt;": '\U0000226A', + "Map;": '\U00002905', + "Mcy;": '\U0000041C', + "MediumSpace;": '\U0000205F', + "Mellintrf;": '\U00002133', + "Mfr;": '\U0001D510', + "MinusPlus;": '\U00002213', + "Mopf;": '\U0001D544', + "Mscr;": '\U00002133', + "Mu;": '\U0000039C', + "NJcy;": '\U0000040A', + "Nacute;": '\U00000143', + "Ncaron;": '\U00000147', + "Ncedil;": '\U00000145', + "Ncy;": '\U0000041D', + "NegativeMediumSpace;": '\U0000200B', + "NegativeThickSpace;": '\U0000200B', + "NegativeThinSpace;": '\U0000200B', + "NegativeVeryThinSpace;": '\U0000200B', + "NestedGreaterGreater;": '\U0000226B', + "NestedLessLess;": '\U0000226A', + "NewLine;": '\U0000000A', + "Nfr;": '\U0001D511', + "NoBreak;": '\U00002060', + "NonBreakingSpace;": '\U000000A0', + "Nopf;": '\U00002115', + "Not;": '\U00002AEC', + "NotCongruent;": '\U00002262', + "NotCupCap;": '\U0000226D', + "NotDoubleVerticalBar;": '\U00002226', + "NotElement;": '\U00002209', + "NotEqual;": '\U00002260', + "NotExists;": '\U00002204', + "NotGreater;": '\U0000226F', + "NotGreaterEqual;": '\U00002271', + "NotGreaterLess;": '\U00002279', + "NotGreaterTilde;": '\U00002275', + "NotLeftTriangle;": '\U000022EA', + "NotLeftTriangleEqual;": '\U000022EC', + "NotLess;": '\U0000226E', + "NotLessEqual;": '\U00002270', + "NotLessGreater;": '\U00002278', + "NotLessTilde;": '\U00002274', + "NotPrecedes;": '\U00002280', + "NotPrecedesSlantEqual;": '\U000022E0', + "NotReverseElement;": '\U0000220C', + "NotRightTriangle;": '\U000022EB', + "NotRightTriangleEqual;": '\U000022ED', + "NotSquareSubsetEqual;": '\U000022E2', + "NotSquareSupersetEqual;": '\U000022E3', + "NotSubsetEqual;": '\U00002288', + "NotSucceeds;": '\U00002281', + "NotSucceedsSlantEqual;": '\U000022E1', + "NotSupersetEqual;": '\U00002289', + "NotTilde;": '\U00002241', + "NotTildeEqual;": '\U00002244', + "NotTildeFullEqual;": '\U00002247', + "NotTildeTilde;": '\U00002249', + "NotVerticalBar;": '\U00002224', + "Nscr;": '\U0001D4A9', + "Ntilde;": '\U000000D1', + "Nu;": '\U0000039D', + "OElig;": '\U00000152', + "Oacute;": '\U000000D3', + "Ocirc;": '\U000000D4', + "Ocy;": '\U0000041E', + "Odblac;": '\U00000150', + "Ofr;": '\U0001D512', + "Ograve;": '\U000000D2', + "Omacr;": '\U0000014C', + "Omega;": '\U000003A9', + "Omicron;": '\U0000039F', + "Oopf;": '\U0001D546', + "OpenCurlyDoubleQuote;": '\U0000201C', + "OpenCurlyQuote;": '\U00002018', + "Or;": '\U00002A54', + "Oscr;": '\U0001D4AA', + "Oslash;": '\U000000D8', + "Otilde;": '\U000000D5', + "Otimes;": '\U00002A37', + "Ouml;": '\U000000D6', + "OverBar;": '\U0000203E', + "OverBrace;": '\U000023DE', + "OverBracket;": '\U000023B4', + "OverParenthesis;": '\U000023DC', + "PartialD;": '\U00002202', + "Pcy;": '\U0000041F', + "Pfr;": '\U0001D513', + "Phi;": '\U000003A6', + "Pi;": '\U000003A0', + "PlusMinus;": '\U000000B1', + "Poincareplane;": '\U0000210C', + "Popf;": '\U00002119', + "Pr;": '\U00002ABB', + "Precedes;": '\U0000227A', + "PrecedesEqual;": '\U00002AAF', + "PrecedesSlantEqual;": '\U0000227C', + "PrecedesTilde;": '\U0000227E', + "Prime;": '\U00002033', + "Product;": '\U0000220F', + "Proportion;": '\U00002237', + "Proportional;": '\U0000221D', + "Pscr;": '\U0001D4AB', + "Psi;": '\U000003A8', + "QUOT;": '\U00000022', + "Qfr;": '\U0001D514', + "Qopf;": '\U0000211A', + "Qscr;": '\U0001D4AC', + "RBarr;": '\U00002910', + "REG;": '\U000000AE', + "Racute;": '\U00000154', + "Rang;": '\U000027EB', + "Rarr;": '\U000021A0', + "Rarrtl;": '\U00002916', + "Rcaron;": '\U00000158', + "Rcedil;": '\U00000156', + "Rcy;": '\U00000420', + "Re;": '\U0000211C', + "ReverseElement;": '\U0000220B', + "ReverseEquilibrium;": '\U000021CB', + "ReverseUpEquilibrium;": '\U0000296F', + "Rfr;": '\U0000211C', + "Rho;": '\U000003A1', + "RightAngleBracket;": '\U000027E9', + "RightArrow;": '\U00002192', + "RightArrowBar;": '\U000021E5', + "RightArrowLeftArrow;": '\U000021C4', + "RightCeiling;": '\U00002309', + "RightDoubleBracket;": '\U000027E7', + "RightDownTeeVector;": '\U0000295D', + "RightDownVector;": '\U000021C2', + "RightDownVectorBar;": '\U00002955', + "RightFloor;": '\U0000230B', + "RightTee;": '\U000022A2', + "RightTeeArrow;": '\U000021A6', + "RightTeeVector;": '\U0000295B', + "RightTriangle;": '\U000022B3', + "RightTriangleBar;": '\U000029D0', + "RightTriangleEqual;": '\U000022B5', + "RightUpDownVector;": '\U0000294F', + "RightUpTeeVector;": '\U0000295C', + "RightUpVector;": '\U000021BE', + "RightUpVectorBar;": '\U00002954', + "RightVector;": '\U000021C0', + "RightVectorBar;": '\U00002953', + "Rightarrow;": '\U000021D2', + "Ropf;": '\U0000211D', + "RoundImplies;": '\U00002970', + "Rrightarrow;": '\U000021DB', + "Rscr;": '\U0000211B', + "Rsh;": '\U000021B1', + "RuleDelayed;": '\U000029F4', + "SHCHcy;": '\U00000429', + "SHcy;": '\U00000428', + "SOFTcy;": '\U0000042C', + "Sacute;": '\U0000015A', + "Sc;": '\U00002ABC', + "Scaron;": '\U00000160', + "Scedil;": '\U0000015E', + "Scirc;": '\U0000015C', + "Scy;": '\U00000421', + "Sfr;": '\U0001D516', + "ShortDownArrow;": '\U00002193', + "ShortLeftArrow;": '\U00002190', + "ShortRightArrow;": '\U00002192', + "ShortUpArrow;": '\U00002191', + "Sigma;": '\U000003A3', + "SmallCircle;": '\U00002218', + "Sopf;": '\U0001D54A', + "Sqrt;": '\U0000221A', + "Square;": '\U000025A1', + "SquareIntersection;": '\U00002293', + "SquareSubset;": '\U0000228F', + "SquareSubsetEqual;": '\U00002291', + "SquareSuperset;": '\U00002290', + "SquareSupersetEqual;": '\U00002292', + "SquareUnion;": '\U00002294', + "Sscr;": '\U0001D4AE', + "Star;": '\U000022C6', + "Sub;": '\U000022D0', + "Subset;": '\U000022D0', + "SubsetEqual;": '\U00002286', + "Succeeds;": '\U0000227B', + "SucceedsEqual;": '\U00002AB0', + "SucceedsSlantEqual;": '\U0000227D', + "SucceedsTilde;": '\U0000227F', + "SuchThat;": '\U0000220B', + "Sum;": '\U00002211', + "Sup;": '\U000022D1', + "Superset;": '\U00002283', + "SupersetEqual;": '\U00002287', + "Supset;": '\U000022D1', + "THORN;": '\U000000DE', + "TRADE;": '\U00002122', + "TSHcy;": '\U0000040B', + "TScy;": '\U00000426', + "Tab;": '\U00000009', + "Tau;": '\U000003A4', + "Tcaron;": '\U00000164', + "Tcedil;": '\U00000162', + "Tcy;": '\U00000422', + "Tfr;": '\U0001D517', + "Therefore;": '\U00002234', + "Theta;": '\U00000398', + "ThinSpace;": '\U00002009', + "Tilde;": '\U0000223C', + "TildeEqual;": '\U00002243', + "TildeFullEqual;": '\U00002245', + "TildeTilde;": '\U00002248', + "Topf;": '\U0001D54B', + "TripleDot;": '\U000020DB', + "Tscr;": '\U0001D4AF', + "Tstrok;": '\U00000166', + "Uacute;": '\U000000DA', + "Uarr;": '\U0000219F', + "Uarrocir;": '\U00002949', + "Ubrcy;": '\U0000040E', + "Ubreve;": '\U0000016C', + "Ucirc;": '\U000000DB', + "Ucy;": '\U00000423', + "Udblac;": '\U00000170', + "Ufr;": '\U0001D518', + "Ugrave;": '\U000000D9', + "Umacr;": '\U0000016A', + "UnderBar;": '\U0000005F', + "UnderBrace;": '\U000023DF', + "UnderBracket;": '\U000023B5', + "UnderParenthesis;": '\U000023DD', + "Union;": '\U000022C3', + "UnionPlus;": '\U0000228E', + "Uogon;": '\U00000172', + "Uopf;": '\U0001D54C', + "UpArrow;": '\U00002191', + "UpArrowBar;": '\U00002912', + "UpArrowDownArrow;": '\U000021C5', + "UpDownArrow;": '\U00002195', + "UpEquilibrium;": '\U0000296E', + "UpTee;": '\U000022A5', + "UpTeeArrow;": '\U000021A5', + "Uparrow;": '\U000021D1', + "Updownarrow;": '\U000021D5', + "UpperLeftArrow;": '\U00002196', + "UpperRightArrow;": '\U00002197', + "Upsi;": '\U000003D2', + "Upsilon;": '\U000003A5', + "Uring;": '\U0000016E', + "Uscr;": '\U0001D4B0', + "Utilde;": '\U00000168', + "Uuml;": '\U000000DC', + "VDash;": '\U000022AB', + "Vbar;": '\U00002AEB', + "Vcy;": '\U00000412', + "Vdash;": '\U000022A9', + "Vdashl;": '\U00002AE6', + "Vee;": '\U000022C1', + "Verbar;": '\U00002016', + "Vert;": '\U00002016', + "VerticalBar;": '\U00002223', + "VerticalLine;": '\U0000007C', + "VerticalSeparator;": '\U00002758', + "VerticalTilde;": '\U00002240', + "VeryThinSpace;": '\U0000200A', + "Vfr;": '\U0001D519', + "Vopf;": '\U0001D54D', + "Vscr;": '\U0001D4B1', + "Vvdash;": '\U000022AA', + "Wcirc;": '\U00000174', + "Wedge;": '\U000022C0', + "Wfr;": '\U0001D51A', + "Wopf;": '\U0001D54E', + "Wscr;": '\U0001D4B2', + "Xfr;": '\U0001D51B', + "Xi;": '\U0000039E', + "Xopf;": '\U0001D54F', + "Xscr;": '\U0001D4B3', + "YAcy;": '\U0000042F', + "YIcy;": '\U00000407', + "YUcy;": '\U0000042E', + "Yacute;": '\U000000DD', + "Ycirc;": '\U00000176', + "Ycy;": '\U0000042B', + "Yfr;": '\U0001D51C', + "Yopf;": '\U0001D550', + "Yscr;": '\U0001D4B4', + "Yuml;": '\U00000178', + "ZHcy;": '\U00000416', + "Zacute;": '\U00000179', + "Zcaron;": '\U0000017D', + "Zcy;": '\U00000417', + "Zdot;": '\U0000017B', + "ZeroWidthSpace;": '\U0000200B', + "Zeta;": '\U00000396', + "Zfr;": '\U00002128', + "Zopf;": '\U00002124', + "Zscr;": '\U0001D4B5', + "aacute;": '\U000000E1', + "abreve;": '\U00000103', + "ac;": '\U0000223E', + "acd;": '\U0000223F', + "acirc;": '\U000000E2', + "acute;": '\U000000B4', + "acy;": '\U00000430', + "aelig;": '\U000000E6', + "af;": '\U00002061', + "afr;": '\U0001D51E', + "agrave;": '\U000000E0', + "alefsym;": '\U00002135', + "aleph;": '\U00002135', + "alpha;": '\U000003B1', + "amacr;": '\U00000101', + "amalg;": '\U00002A3F', + "amp;": '\U00000026', + "and;": '\U00002227', + "andand;": '\U00002A55', + "andd;": '\U00002A5C', + "andslope;": '\U00002A58', + "andv;": '\U00002A5A', + "ang;": '\U00002220', + "ange;": '\U000029A4', + "angle;": '\U00002220', + "angmsd;": '\U00002221', + "angmsdaa;": '\U000029A8', + "angmsdab;": '\U000029A9', + "angmsdac;": '\U000029AA', + "angmsdad;": '\U000029AB', + "angmsdae;": '\U000029AC', + "angmsdaf;": '\U000029AD', + "angmsdag;": '\U000029AE', + "angmsdah;": '\U000029AF', + "angrt;": '\U0000221F', + "angrtvb;": '\U000022BE', + "angrtvbd;": '\U0000299D', + "angsph;": '\U00002222', + "angst;": '\U000000C5', + "angzarr;": '\U0000237C', + "aogon;": '\U00000105', + "aopf;": '\U0001D552', + "ap;": '\U00002248', + "apE;": '\U00002A70', + "apacir;": '\U00002A6F', + "ape;": '\U0000224A', + "apid;": '\U0000224B', + "apos;": '\U00000027', + "approx;": '\U00002248', + "approxeq;": '\U0000224A', + "aring;": '\U000000E5', + "ascr;": '\U0001D4B6', + "ast;": '\U0000002A', + "asymp;": '\U00002248', + "asympeq;": '\U0000224D', + "atilde;": '\U000000E3', + "auml;": '\U000000E4', + "awconint;": '\U00002233', + "awint;": '\U00002A11', + "bNot;": '\U00002AED', + "backcong;": '\U0000224C', + "backepsilon;": '\U000003F6', + "backprime;": '\U00002035', + "backsim;": '\U0000223D', + "backsimeq;": '\U000022CD', + "barvee;": '\U000022BD', + "barwed;": '\U00002305', + "barwedge;": '\U00002305', + "bbrk;": '\U000023B5', + "bbrktbrk;": '\U000023B6', + "bcong;": '\U0000224C', + "bcy;": '\U00000431', + "bdquo;": '\U0000201E', + "becaus;": '\U00002235', + "because;": '\U00002235', + "bemptyv;": '\U000029B0', + "bepsi;": '\U000003F6', + "bernou;": '\U0000212C', + "beta;": '\U000003B2', + "beth;": '\U00002136', + "between;": '\U0000226C', + "bfr;": '\U0001D51F', + "bigcap;": '\U000022C2', + "bigcirc;": '\U000025EF', + "bigcup;": '\U000022C3', + "bigodot;": '\U00002A00', + "bigoplus;": '\U00002A01', + "bigotimes;": '\U00002A02', + "bigsqcup;": '\U00002A06', + "bigstar;": '\U00002605', + "bigtriangledown;": '\U000025BD', + "bigtriangleup;": '\U000025B3', + "biguplus;": '\U00002A04', + "bigvee;": '\U000022C1', + "bigwedge;": '\U000022C0', + "bkarow;": '\U0000290D', + "blacklozenge;": '\U000029EB', + "blacksquare;": '\U000025AA', + "blacktriangle;": '\U000025B4', + "blacktriangledown;": '\U000025BE', + "blacktriangleleft;": '\U000025C2', + "blacktriangleright;": '\U000025B8', + "blank;": '\U00002423', + "blk12;": '\U00002592', + "blk14;": '\U00002591', + "blk34;": '\U00002593', + "block;": '\U00002588', + "bnot;": '\U00002310', + "bopf;": '\U0001D553', + "bot;": '\U000022A5', + "bottom;": '\U000022A5', + "bowtie;": '\U000022C8', + "boxDL;": '\U00002557', + "boxDR;": '\U00002554', + "boxDl;": '\U00002556', + "boxDr;": '\U00002553', + "boxH;": '\U00002550', + "boxHD;": '\U00002566', + "boxHU;": '\U00002569', + "boxHd;": '\U00002564', + "boxHu;": '\U00002567', + "boxUL;": '\U0000255D', + "boxUR;": '\U0000255A', + "boxUl;": '\U0000255C', + "boxUr;": '\U00002559', + "boxV;": '\U00002551', + "boxVH;": '\U0000256C', + "boxVL;": '\U00002563', + "boxVR;": '\U00002560', + "boxVh;": '\U0000256B', + "boxVl;": '\U00002562', + "boxVr;": '\U0000255F', + "boxbox;": '\U000029C9', + "boxdL;": '\U00002555', + "boxdR;": '\U00002552', + "boxdl;": '\U00002510', + "boxdr;": '\U0000250C', + "boxh;": '\U00002500', + "boxhD;": '\U00002565', + "boxhU;": '\U00002568', + "boxhd;": '\U0000252C', + "boxhu;": '\U00002534', + "boxminus;": '\U0000229F', + "boxplus;": '\U0000229E', + "boxtimes;": '\U000022A0', + "boxuL;": '\U0000255B', + "boxuR;": '\U00002558', + "boxul;": '\U00002518', + "boxur;": '\U00002514', + "boxv;": '\U00002502', + "boxvH;": '\U0000256A', + "boxvL;": '\U00002561', + "boxvR;": '\U0000255E', + "boxvh;": '\U0000253C', + "boxvl;": '\U00002524', + "boxvr;": '\U0000251C', + "bprime;": '\U00002035', + "breve;": '\U000002D8', + "brvbar;": '\U000000A6', + "bscr;": '\U0001D4B7', + "bsemi;": '\U0000204F', + "bsim;": '\U0000223D', + "bsime;": '\U000022CD', + "bsol;": '\U0000005C', + "bsolb;": '\U000029C5', + "bsolhsub;": '\U000027C8', + "bull;": '\U00002022', + "bullet;": '\U00002022', + "bump;": '\U0000224E', + "bumpE;": '\U00002AAE', + "bumpe;": '\U0000224F', + "bumpeq;": '\U0000224F', + "cacute;": '\U00000107', + "cap;": '\U00002229', + "capand;": '\U00002A44', + "capbrcup;": '\U00002A49', + "capcap;": '\U00002A4B', + "capcup;": '\U00002A47', + "capdot;": '\U00002A40', + "caret;": '\U00002041', + "caron;": '\U000002C7', + "ccaps;": '\U00002A4D', + "ccaron;": '\U0000010D', + "ccedil;": '\U000000E7', + "ccirc;": '\U00000109', + "ccups;": '\U00002A4C', + "ccupssm;": '\U00002A50', + "cdot;": '\U0000010B', + "cedil;": '\U000000B8', + "cemptyv;": '\U000029B2', + "cent;": '\U000000A2', + "centerdot;": '\U000000B7', + "cfr;": '\U0001D520', + "chcy;": '\U00000447', + "check;": '\U00002713', + "checkmark;": '\U00002713', + "chi;": '\U000003C7', + "cir;": '\U000025CB', + "cirE;": '\U000029C3', + "circ;": '\U000002C6', + "circeq;": '\U00002257', + "circlearrowleft;": '\U000021BA', + "circlearrowright;": '\U000021BB', + "circledR;": '\U000000AE', + "circledS;": '\U000024C8', + "circledast;": '\U0000229B', + "circledcirc;": '\U0000229A', + "circleddash;": '\U0000229D', + "cire;": '\U00002257', + "cirfnint;": '\U00002A10', + "cirmid;": '\U00002AEF', + "cirscir;": '\U000029C2', + "clubs;": '\U00002663', + "clubsuit;": '\U00002663', + "colon;": '\U0000003A', + "colone;": '\U00002254', + "coloneq;": '\U00002254', + "comma;": '\U0000002C', + "commat;": '\U00000040', + "comp;": '\U00002201', + "compfn;": '\U00002218', + "complement;": '\U00002201', + "complexes;": '\U00002102', + "cong;": '\U00002245', + "congdot;": '\U00002A6D', + "conint;": '\U0000222E', + "copf;": '\U0001D554', + "coprod;": '\U00002210', + "copy;": '\U000000A9', + "copysr;": '\U00002117', + "crarr;": '\U000021B5', + "cross;": '\U00002717', + "cscr;": '\U0001D4B8', + "csub;": '\U00002ACF', + "csube;": '\U00002AD1', + "csup;": '\U00002AD0', + "csupe;": '\U00002AD2', + "ctdot;": '\U000022EF', + "cudarrl;": '\U00002938', + "cudarrr;": '\U00002935', + "cuepr;": '\U000022DE', + "cuesc;": '\U000022DF', + "cularr;": '\U000021B6', + "cularrp;": '\U0000293D', + "cup;": '\U0000222A', + "cupbrcap;": '\U00002A48', + "cupcap;": '\U00002A46', + "cupcup;": '\U00002A4A', + "cupdot;": '\U0000228D', + "cupor;": '\U00002A45', + "curarr;": '\U000021B7', + "curarrm;": '\U0000293C', + "curlyeqprec;": '\U000022DE', + "curlyeqsucc;": '\U000022DF', + "curlyvee;": '\U000022CE', + "curlywedge;": '\U000022CF', + "curren;": '\U000000A4', + "curvearrowleft;": '\U000021B6', + "curvearrowright;": '\U000021B7', + "cuvee;": '\U000022CE', + "cuwed;": '\U000022CF', + "cwconint;": '\U00002232', + "cwint;": '\U00002231', + "cylcty;": '\U0000232D', + "dArr;": '\U000021D3', + "dHar;": '\U00002965', + "dagger;": '\U00002020', + "daleth;": '\U00002138', + "darr;": '\U00002193', + "dash;": '\U00002010', + "dashv;": '\U000022A3', + "dbkarow;": '\U0000290F', + "dblac;": '\U000002DD', + "dcaron;": '\U0000010F', + "dcy;": '\U00000434', + "dd;": '\U00002146', + "ddagger;": '\U00002021', + "ddarr;": '\U000021CA', + "ddotseq;": '\U00002A77', + "deg;": '\U000000B0', + "delta;": '\U000003B4', + "demptyv;": '\U000029B1', + "dfisht;": '\U0000297F', + "dfr;": '\U0001D521', + "dharl;": '\U000021C3', + "dharr;": '\U000021C2', + "diam;": '\U000022C4', + "diamond;": '\U000022C4', + "diamondsuit;": '\U00002666', + "diams;": '\U00002666', + "die;": '\U000000A8', + "digamma;": '\U000003DD', + "disin;": '\U000022F2', + "div;": '\U000000F7', + "divide;": '\U000000F7', + "divideontimes;": '\U000022C7', + "divonx;": '\U000022C7', + "djcy;": '\U00000452', + "dlcorn;": '\U0000231E', + "dlcrop;": '\U0000230D', + "dollar;": '\U00000024', + "dopf;": '\U0001D555', + "dot;": '\U000002D9', + "doteq;": '\U00002250', + "doteqdot;": '\U00002251', + "dotminus;": '\U00002238', + "dotplus;": '\U00002214', + "dotsquare;": '\U000022A1', + "doublebarwedge;": '\U00002306', + "downarrow;": '\U00002193', + "downdownarrows;": '\U000021CA', + "downharpoonleft;": '\U000021C3', + "downharpoonright;": '\U000021C2', + "drbkarow;": '\U00002910', + "drcorn;": '\U0000231F', + "drcrop;": '\U0000230C', + "dscr;": '\U0001D4B9', + "dscy;": '\U00000455', + "dsol;": '\U000029F6', + "dstrok;": '\U00000111', + "dtdot;": '\U000022F1', + "dtri;": '\U000025BF', + "dtrif;": '\U000025BE', + "duarr;": '\U000021F5', + "duhar;": '\U0000296F', + "dwangle;": '\U000029A6', + "dzcy;": '\U0000045F', + "dzigrarr;": '\U000027FF', + "eDDot;": '\U00002A77', + "eDot;": '\U00002251', + "eacute;": '\U000000E9', + "easter;": '\U00002A6E', + "ecaron;": '\U0000011B', + "ecir;": '\U00002256', + "ecirc;": '\U000000EA', + "ecolon;": '\U00002255', + "ecy;": '\U0000044D', + "edot;": '\U00000117', + "ee;": '\U00002147', + "efDot;": '\U00002252', + "efr;": '\U0001D522', + "eg;": '\U00002A9A', + "egrave;": '\U000000E8', + "egs;": '\U00002A96', + "egsdot;": '\U00002A98', + "el;": '\U00002A99', + "elinters;": '\U000023E7', + "ell;": '\U00002113', + "els;": '\U00002A95', + "elsdot;": '\U00002A97', + "emacr;": '\U00000113', + "empty;": '\U00002205', + "emptyset;": '\U00002205', + "emptyv;": '\U00002205', + "emsp;": '\U00002003', + "emsp13;": '\U00002004', + "emsp14;": '\U00002005', + "eng;": '\U0000014B', + "ensp;": '\U00002002', + "eogon;": '\U00000119', + "eopf;": '\U0001D556', + "epar;": '\U000022D5', + "eparsl;": '\U000029E3', + "eplus;": '\U00002A71', + "epsi;": '\U000003B5', + "epsilon;": '\U000003B5', + "epsiv;": '\U000003F5', + "eqcirc;": '\U00002256', + "eqcolon;": '\U00002255', + "eqsim;": '\U00002242', + "eqslantgtr;": '\U00002A96', + "eqslantless;": '\U00002A95', + "equals;": '\U0000003D', + "equest;": '\U0000225F', + "equiv;": '\U00002261', + "equivDD;": '\U00002A78', + "eqvparsl;": '\U000029E5', + "erDot;": '\U00002253', + "erarr;": '\U00002971', + "escr;": '\U0000212F', + "esdot;": '\U00002250', + "esim;": '\U00002242', + "eta;": '\U000003B7', + "eth;": '\U000000F0', + "euml;": '\U000000EB', + "euro;": '\U000020AC', + "excl;": '\U00000021', + "exist;": '\U00002203', + "expectation;": '\U00002130', + "exponentiale;": '\U00002147', + "fallingdotseq;": '\U00002252', + "fcy;": '\U00000444', + "female;": '\U00002640', + "ffilig;": '\U0000FB03', + "fflig;": '\U0000FB00', + "ffllig;": '\U0000FB04', + "ffr;": '\U0001D523', + "filig;": '\U0000FB01', + "flat;": '\U0000266D', + "fllig;": '\U0000FB02', + "fltns;": '\U000025B1', + "fnof;": '\U00000192', + "fopf;": '\U0001D557', + "forall;": '\U00002200', + "fork;": '\U000022D4', + "forkv;": '\U00002AD9', + "fpartint;": '\U00002A0D', + "frac12;": '\U000000BD', + "frac13;": '\U00002153', + "frac14;": '\U000000BC', + "frac15;": '\U00002155', + "frac16;": '\U00002159', + "frac18;": '\U0000215B', + "frac23;": '\U00002154', + "frac25;": '\U00002156', + "frac34;": '\U000000BE', + "frac35;": '\U00002157', + "frac38;": '\U0000215C', + "frac45;": '\U00002158', + "frac56;": '\U0000215A', + "frac58;": '\U0000215D', + "frac78;": '\U0000215E', + "frasl;": '\U00002044', + "frown;": '\U00002322', + "fscr;": '\U0001D4BB', + "gE;": '\U00002267', + "gEl;": '\U00002A8C', + "gacute;": '\U000001F5', + "gamma;": '\U000003B3', + "gammad;": '\U000003DD', + "gap;": '\U00002A86', + "gbreve;": '\U0000011F', + "gcirc;": '\U0000011D', + "gcy;": '\U00000433', + "gdot;": '\U00000121', + "ge;": '\U00002265', + "gel;": '\U000022DB', + "geq;": '\U00002265', + "geqq;": '\U00002267', + "geqslant;": '\U00002A7E', + "ges;": '\U00002A7E', + "gescc;": '\U00002AA9', + "gesdot;": '\U00002A80', + "gesdoto;": '\U00002A82', + "gesdotol;": '\U00002A84', + "gesles;": '\U00002A94', + "gfr;": '\U0001D524', + "gg;": '\U0000226B', + "ggg;": '\U000022D9', + "gimel;": '\U00002137', + "gjcy;": '\U00000453', + "gl;": '\U00002277', + "glE;": '\U00002A92', + "gla;": '\U00002AA5', + "glj;": '\U00002AA4', + "gnE;": '\U00002269', + "gnap;": '\U00002A8A', + "gnapprox;": '\U00002A8A', + "gne;": '\U00002A88', + "gneq;": '\U00002A88', + "gneqq;": '\U00002269', + "gnsim;": '\U000022E7', + "gopf;": '\U0001D558', + "grave;": '\U00000060', + "gscr;": '\U0000210A', + "gsim;": '\U00002273', + "gsime;": '\U00002A8E', + "gsiml;": '\U00002A90', + "gt;": '\U0000003E', + "gtcc;": '\U00002AA7', + "gtcir;": '\U00002A7A', + "gtdot;": '\U000022D7', + "gtlPar;": '\U00002995', + "gtquest;": '\U00002A7C', + "gtrapprox;": '\U00002A86', + "gtrarr;": '\U00002978', + "gtrdot;": '\U000022D7', + "gtreqless;": '\U000022DB', + "gtreqqless;": '\U00002A8C', + "gtrless;": '\U00002277', + "gtrsim;": '\U00002273', + "hArr;": '\U000021D4', + "hairsp;": '\U0000200A', + "half;": '\U000000BD', + "hamilt;": '\U0000210B', + "hardcy;": '\U0000044A', + "harr;": '\U00002194', + "harrcir;": '\U00002948', + "harrw;": '\U000021AD', + "hbar;": '\U0000210F', + "hcirc;": '\U00000125', + "hearts;": '\U00002665', + "heartsuit;": '\U00002665', + "hellip;": '\U00002026', + "hercon;": '\U000022B9', + "hfr;": '\U0001D525', + "hksearow;": '\U00002925', + "hkswarow;": '\U00002926', + "hoarr;": '\U000021FF', + "homtht;": '\U0000223B', + "hookleftarrow;": '\U000021A9', + "hookrightarrow;": '\U000021AA', + "hopf;": '\U0001D559', + "horbar;": '\U00002015', + "hscr;": '\U0001D4BD', + "hslash;": '\U0000210F', + "hstrok;": '\U00000127', + "hybull;": '\U00002043', + "hyphen;": '\U00002010', + "iacute;": '\U000000ED', + "ic;": '\U00002063', + "icirc;": '\U000000EE', + "icy;": '\U00000438', + "iecy;": '\U00000435', + "iexcl;": '\U000000A1', + "iff;": '\U000021D4', + "ifr;": '\U0001D526', + "igrave;": '\U000000EC', + "ii;": '\U00002148', + "iiiint;": '\U00002A0C', + "iiint;": '\U0000222D', + "iinfin;": '\U000029DC', + "iiota;": '\U00002129', + "ijlig;": '\U00000133', + "imacr;": '\U0000012B', + "image;": '\U00002111', + "imagline;": '\U00002110', + "imagpart;": '\U00002111', + "imath;": '\U00000131', + "imof;": '\U000022B7', + "imped;": '\U000001B5', + "in;": '\U00002208', + "incare;": '\U00002105', + "infin;": '\U0000221E', + "infintie;": '\U000029DD', + "inodot;": '\U00000131', + "int;": '\U0000222B', + "intcal;": '\U000022BA', + "integers;": '\U00002124', + "intercal;": '\U000022BA', + "intlarhk;": '\U00002A17', + "intprod;": '\U00002A3C', + "iocy;": '\U00000451', + "iogon;": '\U0000012F', + "iopf;": '\U0001D55A', + "iota;": '\U000003B9', + "iprod;": '\U00002A3C', + "iquest;": '\U000000BF', + "iscr;": '\U0001D4BE', + "isin;": '\U00002208', + "isinE;": '\U000022F9', + "isindot;": '\U000022F5', + "isins;": '\U000022F4', + "isinsv;": '\U000022F3', + "isinv;": '\U00002208', + "it;": '\U00002062', + "itilde;": '\U00000129', + "iukcy;": '\U00000456', + "iuml;": '\U000000EF', + "jcirc;": '\U00000135', + "jcy;": '\U00000439', + "jfr;": '\U0001D527', + "jmath;": '\U00000237', + "jopf;": '\U0001D55B', + "jscr;": '\U0001D4BF', + "jsercy;": '\U00000458', + "jukcy;": '\U00000454', + "kappa;": '\U000003BA', + "kappav;": '\U000003F0', + "kcedil;": '\U00000137', + "kcy;": '\U0000043A', + "kfr;": '\U0001D528', + "kgreen;": '\U00000138', + "khcy;": '\U00000445', + "kjcy;": '\U0000045C', + "kopf;": '\U0001D55C', + "kscr;": '\U0001D4C0', + "lAarr;": '\U000021DA', + "lArr;": '\U000021D0', + "lAtail;": '\U0000291B', + "lBarr;": '\U0000290E', + "lE;": '\U00002266', + "lEg;": '\U00002A8B', + "lHar;": '\U00002962', + "lacute;": '\U0000013A', + "laemptyv;": '\U000029B4', + "lagran;": '\U00002112', + "lambda;": '\U000003BB', + "lang;": '\U000027E8', + "langd;": '\U00002991', + "langle;": '\U000027E8', + "lap;": '\U00002A85', + "laquo;": '\U000000AB', + "larr;": '\U00002190', + "larrb;": '\U000021E4', + "larrbfs;": '\U0000291F', + "larrfs;": '\U0000291D', + "larrhk;": '\U000021A9', + "larrlp;": '\U000021AB', + "larrpl;": '\U00002939', + "larrsim;": '\U00002973', + "larrtl;": '\U000021A2', + "lat;": '\U00002AAB', + "latail;": '\U00002919', + "late;": '\U00002AAD', + "lbarr;": '\U0000290C', + "lbbrk;": '\U00002772', + "lbrace;": '\U0000007B', + "lbrack;": '\U0000005B', + "lbrke;": '\U0000298B', + "lbrksld;": '\U0000298F', + "lbrkslu;": '\U0000298D', + "lcaron;": '\U0000013E', + "lcedil;": '\U0000013C', + "lceil;": '\U00002308', + "lcub;": '\U0000007B', + "lcy;": '\U0000043B', + "ldca;": '\U00002936', + "ldquo;": '\U0000201C', + "ldquor;": '\U0000201E', + "ldrdhar;": '\U00002967', + "ldrushar;": '\U0000294B', + "ldsh;": '\U000021B2', + "le;": '\U00002264', + "leftarrow;": '\U00002190', + "leftarrowtail;": '\U000021A2', + "leftharpoondown;": '\U000021BD', + "leftharpoonup;": '\U000021BC', + "leftleftarrows;": '\U000021C7', + "leftrightarrow;": '\U00002194', + "leftrightarrows;": '\U000021C6', + "leftrightharpoons;": '\U000021CB', + "leftrightsquigarrow;": '\U000021AD', + "leftthreetimes;": '\U000022CB', + "leg;": '\U000022DA', + "leq;": '\U00002264', + "leqq;": '\U00002266', + "leqslant;": '\U00002A7D', + "les;": '\U00002A7D', + "lescc;": '\U00002AA8', + "lesdot;": '\U00002A7F', + "lesdoto;": '\U00002A81', + "lesdotor;": '\U00002A83', + "lesges;": '\U00002A93', + "lessapprox;": '\U00002A85', + "lessdot;": '\U000022D6', + "lesseqgtr;": '\U000022DA', + "lesseqqgtr;": '\U00002A8B', + "lessgtr;": '\U00002276', + "lesssim;": '\U00002272', + "lfisht;": '\U0000297C', + "lfloor;": '\U0000230A', + "lfr;": '\U0001D529', + "lg;": '\U00002276', + "lgE;": '\U00002A91', + "lhard;": '\U000021BD', + "lharu;": '\U000021BC', + "lharul;": '\U0000296A', + "lhblk;": '\U00002584', + "ljcy;": '\U00000459', + "ll;": '\U0000226A', + "llarr;": '\U000021C7', + "llcorner;": '\U0000231E', + "llhard;": '\U0000296B', + "lltri;": '\U000025FA', + "lmidot;": '\U00000140', + "lmoust;": '\U000023B0', + "lmoustache;": '\U000023B0', + "lnE;": '\U00002268', + "lnap;": '\U00002A89', + "lnapprox;": '\U00002A89', + "lne;": '\U00002A87', + "lneq;": '\U00002A87', + "lneqq;": '\U00002268', + "lnsim;": '\U000022E6', + "loang;": '\U000027EC', + "loarr;": '\U000021FD', + "lobrk;": '\U000027E6', + "longleftarrow;": '\U000027F5', + "longleftrightarrow;": '\U000027F7', + "longmapsto;": '\U000027FC', + "longrightarrow;": '\U000027F6', + "looparrowleft;": '\U000021AB', + "looparrowright;": '\U000021AC', + "lopar;": '\U00002985', + "lopf;": '\U0001D55D', + "loplus;": '\U00002A2D', + "lotimes;": '\U00002A34', + "lowast;": '\U00002217', + "lowbar;": '\U0000005F', + "loz;": '\U000025CA', + "lozenge;": '\U000025CA', + "lozf;": '\U000029EB', + "lpar;": '\U00000028', + "lparlt;": '\U00002993', + "lrarr;": '\U000021C6', + "lrcorner;": '\U0000231F', + "lrhar;": '\U000021CB', + "lrhard;": '\U0000296D', + "lrm;": '\U0000200E', + "lrtri;": '\U000022BF', + "lsaquo;": '\U00002039', + "lscr;": '\U0001D4C1', + "lsh;": '\U000021B0', + "lsim;": '\U00002272', + "lsime;": '\U00002A8D', + "lsimg;": '\U00002A8F', + "lsqb;": '\U0000005B', + "lsquo;": '\U00002018', + "lsquor;": '\U0000201A', + "lstrok;": '\U00000142', + "lt;": '\U0000003C', + "ltcc;": '\U00002AA6', + "ltcir;": '\U00002A79', + "ltdot;": '\U000022D6', + "lthree;": '\U000022CB', + "ltimes;": '\U000022C9', + "ltlarr;": '\U00002976', + "ltquest;": '\U00002A7B', + "ltrPar;": '\U00002996', + "ltri;": '\U000025C3', + "ltrie;": '\U000022B4', + "ltrif;": '\U000025C2', + "lurdshar;": '\U0000294A', + "luruhar;": '\U00002966', + "mDDot;": '\U0000223A', + "macr;": '\U000000AF', + "male;": '\U00002642', + "malt;": '\U00002720', + "maltese;": '\U00002720', + "map;": '\U000021A6', + "mapsto;": '\U000021A6', + "mapstodown;": '\U000021A7', + "mapstoleft;": '\U000021A4', + "mapstoup;": '\U000021A5', + "marker;": '\U000025AE', + "mcomma;": '\U00002A29', + "mcy;": '\U0000043C', + "mdash;": '\U00002014', + "measuredangle;": '\U00002221', + "mfr;": '\U0001D52A', + "mho;": '\U00002127', + "micro;": '\U000000B5', + "mid;": '\U00002223', + "midast;": '\U0000002A', + "midcir;": '\U00002AF0', + "middot;": '\U000000B7', + "minus;": '\U00002212', + "minusb;": '\U0000229F', + "minusd;": '\U00002238', + "minusdu;": '\U00002A2A', + "mlcp;": '\U00002ADB', + "mldr;": '\U00002026', + "mnplus;": '\U00002213', + "models;": '\U000022A7', + "mopf;": '\U0001D55E', + "mp;": '\U00002213', + "mscr;": '\U0001D4C2', + "mstpos;": '\U0000223E', + "mu;": '\U000003BC', + "multimap;": '\U000022B8', + "mumap;": '\U000022B8', + "nLeftarrow;": '\U000021CD', + "nLeftrightarrow;": '\U000021CE', + "nRightarrow;": '\U000021CF', + "nVDash;": '\U000022AF', + "nVdash;": '\U000022AE', + "nabla;": '\U00002207', + "nacute;": '\U00000144', + "nap;": '\U00002249', + "napos;": '\U00000149', + "napprox;": '\U00002249', + "natur;": '\U0000266E', + "natural;": '\U0000266E', + "naturals;": '\U00002115', + "nbsp;": '\U000000A0', + "ncap;": '\U00002A43', + "ncaron;": '\U00000148', + "ncedil;": '\U00000146', + "ncong;": '\U00002247', + "ncup;": '\U00002A42', + "ncy;": '\U0000043D', + "ndash;": '\U00002013', + "ne;": '\U00002260', + "neArr;": '\U000021D7', + "nearhk;": '\U00002924', + "nearr;": '\U00002197', + "nearrow;": '\U00002197', + "nequiv;": '\U00002262', + "nesear;": '\U00002928', + "nexist;": '\U00002204', + "nexists;": '\U00002204', + "nfr;": '\U0001D52B', + "nge;": '\U00002271', + "ngeq;": '\U00002271', + "ngsim;": '\U00002275', + "ngt;": '\U0000226F', + "ngtr;": '\U0000226F', + "nhArr;": '\U000021CE', + "nharr;": '\U000021AE', + "nhpar;": '\U00002AF2', + "ni;": '\U0000220B', + "nis;": '\U000022FC', + "nisd;": '\U000022FA', + "niv;": '\U0000220B', + "njcy;": '\U0000045A', + "nlArr;": '\U000021CD', + "nlarr;": '\U0000219A', + "nldr;": '\U00002025', + "nle;": '\U00002270', + "nleftarrow;": '\U0000219A', + "nleftrightarrow;": '\U000021AE', + "nleq;": '\U00002270', + "nless;": '\U0000226E', + "nlsim;": '\U00002274', + "nlt;": '\U0000226E', + "nltri;": '\U000022EA', + "nltrie;": '\U000022EC', + "nmid;": '\U00002224', + "nopf;": '\U0001D55F', + "not;": '\U000000AC', + "notin;": '\U00002209', + "notinva;": '\U00002209', + "notinvb;": '\U000022F7', + "notinvc;": '\U000022F6', + "notni;": '\U0000220C', + "notniva;": '\U0000220C', + "notnivb;": '\U000022FE', + "notnivc;": '\U000022FD', + "npar;": '\U00002226', + "nparallel;": '\U00002226', + "npolint;": '\U00002A14', + "npr;": '\U00002280', + "nprcue;": '\U000022E0', + "nprec;": '\U00002280', + "nrArr;": '\U000021CF', + "nrarr;": '\U0000219B', + "nrightarrow;": '\U0000219B', + "nrtri;": '\U000022EB', + "nrtrie;": '\U000022ED', + "nsc;": '\U00002281', + "nsccue;": '\U000022E1', + "nscr;": '\U0001D4C3', + "nshortmid;": '\U00002224', + "nshortparallel;": '\U00002226', + "nsim;": '\U00002241', + "nsime;": '\U00002244', + "nsimeq;": '\U00002244', + "nsmid;": '\U00002224', + "nspar;": '\U00002226', + "nsqsube;": '\U000022E2', + "nsqsupe;": '\U000022E3', + "nsub;": '\U00002284', + "nsube;": '\U00002288', + "nsubseteq;": '\U00002288', + "nsucc;": '\U00002281', + "nsup;": '\U00002285', + "nsupe;": '\U00002289', + "nsupseteq;": '\U00002289', + "ntgl;": '\U00002279', + "ntilde;": '\U000000F1', + "ntlg;": '\U00002278', + "ntriangleleft;": '\U000022EA', + "ntrianglelefteq;": '\U000022EC', + "ntriangleright;": '\U000022EB', + "ntrianglerighteq;": '\U000022ED', + "nu;": '\U000003BD', + "num;": '\U00000023', + "numero;": '\U00002116', + "numsp;": '\U00002007', + "nvDash;": '\U000022AD', + "nvHarr;": '\U00002904', + "nvdash;": '\U000022AC', + "nvinfin;": '\U000029DE', + "nvlArr;": '\U00002902', + "nvrArr;": '\U00002903', + "nwArr;": '\U000021D6', + "nwarhk;": '\U00002923', + "nwarr;": '\U00002196', + "nwarrow;": '\U00002196', + "nwnear;": '\U00002927', + "oS;": '\U000024C8', + "oacute;": '\U000000F3', + "oast;": '\U0000229B', + "ocir;": '\U0000229A', + "ocirc;": '\U000000F4', + "ocy;": '\U0000043E', + "odash;": '\U0000229D', + "odblac;": '\U00000151', + "odiv;": '\U00002A38', + "odot;": '\U00002299', + "odsold;": '\U000029BC', + "oelig;": '\U00000153', + "ofcir;": '\U000029BF', + "ofr;": '\U0001D52C', + "ogon;": '\U000002DB', + "ograve;": '\U000000F2', + "ogt;": '\U000029C1', + "ohbar;": '\U000029B5', + "ohm;": '\U000003A9', + "oint;": '\U0000222E', + "olarr;": '\U000021BA', + "olcir;": '\U000029BE', + "olcross;": '\U000029BB', + "oline;": '\U0000203E', + "olt;": '\U000029C0', + "omacr;": '\U0000014D', + "omega;": '\U000003C9', + "omicron;": '\U000003BF', + "omid;": '\U000029B6', + "ominus;": '\U00002296', + "oopf;": '\U0001D560', + "opar;": '\U000029B7', + "operp;": '\U000029B9', + "oplus;": '\U00002295', + "or;": '\U00002228', + "orarr;": '\U000021BB', + "ord;": '\U00002A5D', + "order;": '\U00002134', + "orderof;": '\U00002134', + "ordf;": '\U000000AA', + "ordm;": '\U000000BA', + "origof;": '\U000022B6', + "oror;": '\U00002A56', + "orslope;": '\U00002A57', + "orv;": '\U00002A5B', + "oscr;": '\U00002134', + "oslash;": '\U000000F8', + "osol;": '\U00002298', + "otilde;": '\U000000F5', + "otimes;": '\U00002297', + "otimesas;": '\U00002A36', + "ouml;": '\U000000F6', + "ovbar;": '\U0000233D', + "par;": '\U00002225', + "para;": '\U000000B6', + "parallel;": '\U00002225', + "parsim;": '\U00002AF3', + "parsl;": '\U00002AFD', + "part;": '\U00002202', + "pcy;": '\U0000043F', + "percnt;": '\U00000025', + "period;": '\U0000002E', + "permil;": '\U00002030', + "perp;": '\U000022A5', + "pertenk;": '\U00002031', + "pfr;": '\U0001D52D', + "phi;": '\U000003C6', + "phiv;": '\U000003D5', + "phmmat;": '\U00002133', + "phone;": '\U0000260E', + "pi;": '\U000003C0', + "pitchfork;": '\U000022D4', + "piv;": '\U000003D6', + "planck;": '\U0000210F', + "planckh;": '\U0000210E', + "plankv;": '\U0000210F', + "plus;": '\U0000002B', + "plusacir;": '\U00002A23', + "plusb;": '\U0000229E', + "pluscir;": '\U00002A22', + "plusdo;": '\U00002214', + "plusdu;": '\U00002A25', + "pluse;": '\U00002A72', + "plusmn;": '\U000000B1', + "plussim;": '\U00002A26', + "plustwo;": '\U00002A27', + "pm;": '\U000000B1', + "pointint;": '\U00002A15', + "popf;": '\U0001D561', + "pound;": '\U000000A3', + "pr;": '\U0000227A', + "prE;": '\U00002AB3', + "prap;": '\U00002AB7', + "prcue;": '\U0000227C', + "pre;": '\U00002AAF', + "prec;": '\U0000227A', + "precapprox;": '\U00002AB7', + "preccurlyeq;": '\U0000227C', + "preceq;": '\U00002AAF', + "precnapprox;": '\U00002AB9', + "precneqq;": '\U00002AB5', + "precnsim;": '\U000022E8', + "precsim;": '\U0000227E', + "prime;": '\U00002032', + "primes;": '\U00002119', + "prnE;": '\U00002AB5', + "prnap;": '\U00002AB9', + "prnsim;": '\U000022E8', + "prod;": '\U0000220F', + "profalar;": '\U0000232E', + "profline;": '\U00002312', + "profsurf;": '\U00002313', + "prop;": '\U0000221D', + "propto;": '\U0000221D', + "prsim;": '\U0000227E', + "prurel;": '\U000022B0', + "pscr;": '\U0001D4C5', + "psi;": '\U000003C8', + "puncsp;": '\U00002008', + "qfr;": '\U0001D52E', + "qint;": '\U00002A0C', + "qopf;": '\U0001D562', + "qprime;": '\U00002057', + "qscr;": '\U0001D4C6', + "quaternions;": '\U0000210D', + "quatint;": '\U00002A16', + "quest;": '\U0000003F', + "questeq;": '\U0000225F', + "quot;": '\U00000022', + "rAarr;": '\U000021DB', + "rArr;": '\U000021D2', + "rAtail;": '\U0000291C', + "rBarr;": '\U0000290F', + "rHar;": '\U00002964', + "racute;": '\U00000155', + "radic;": '\U0000221A', + "raemptyv;": '\U000029B3', + "rang;": '\U000027E9', + "rangd;": '\U00002992', + "range;": '\U000029A5', + "rangle;": '\U000027E9', + "raquo;": '\U000000BB', + "rarr;": '\U00002192', + "rarrap;": '\U00002975', + "rarrb;": '\U000021E5', + "rarrbfs;": '\U00002920', + "rarrc;": '\U00002933', + "rarrfs;": '\U0000291E', + "rarrhk;": '\U000021AA', + "rarrlp;": '\U000021AC', + "rarrpl;": '\U00002945', + "rarrsim;": '\U00002974', + "rarrtl;": '\U000021A3', + "rarrw;": '\U0000219D', + "ratail;": '\U0000291A', + "ratio;": '\U00002236', + "rationals;": '\U0000211A', + "rbarr;": '\U0000290D', + "rbbrk;": '\U00002773', + "rbrace;": '\U0000007D', + "rbrack;": '\U0000005D', + "rbrke;": '\U0000298C', + "rbrksld;": '\U0000298E', + "rbrkslu;": '\U00002990', + "rcaron;": '\U00000159', + "rcedil;": '\U00000157', + "rceil;": '\U00002309', + "rcub;": '\U0000007D', + "rcy;": '\U00000440', + "rdca;": '\U00002937', + "rdldhar;": '\U00002969', + "rdquo;": '\U0000201D', + "rdquor;": '\U0000201D', + "rdsh;": '\U000021B3', + "real;": '\U0000211C', + "realine;": '\U0000211B', + "realpart;": '\U0000211C', + "reals;": '\U0000211D', + "rect;": '\U000025AD', + "reg;": '\U000000AE', + "rfisht;": '\U0000297D', + "rfloor;": '\U0000230B', + "rfr;": '\U0001D52F', + "rhard;": '\U000021C1', + "rharu;": '\U000021C0', + "rharul;": '\U0000296C', + "rho;": '\U000003C1', + "rhov;": '\U000003F1', + "rightarrow;": '\U00002192', + "rightarrowtail;": '\U000021A3', + "rightharpoondown;": '\U000021C1', + "rightharpoonup;": '\U000021C0', + "rightleftarrows;": '\U000021C4', + "rightleftharpoons;": '\U000021CC', + "rightrightarrows;": '\U000021C9', + "rightsquigarrow;": '\U0000219D', + "rightthreetimes;": '\U000022CC', + "ring;": '\U000002DA', + "risingdotseq;": '\U00002253', + "rlarr;": '\U000021C4', + "rlhar;": '\U000021CC', + "rlm;": '\U0000200F', + "rmoust;": '\U000023B1', + "rmoustache;": '\U000023B1', + "rnmid;": '\U00002AEE', + "roang;": '\U000027ED', + "roarr;": '\U000021FE', + "robrk;": '\U000027E7', + "ropar;": '\U00002986', + "ropf;": '\U0001D563', + "roplus;": '\U00002A2E', + "rotimes;": '\U00002A35', + "rpar;": '\U00000029', + "rpargt;": '\U00002994', + "rppolint;": '\U00002A12', + "rrarr;": '\U000021C9', + "rsaquo;": '\U0000203A', + "rscr;": '\U0001D4C7', + "rsh;": '\U000021B1', + "rsqb;": '\U0000005D', + "rsquo;": '\U00002019', + "rsquor;": '\U00002019', + "rthree;": '\U000022CC', + "rtimes;": '\U000022CA', + "rtri;": '\U000025B9', + "rtrie;": '\U000022B5', + "rtrif;": '\U000025B8', + "rtriltri;": '\U000029CE', + "ruluhar;": '\U00002968', + "rx;": '\U0000211E', + "sacute;": '\U0000015B', + "sbquo;": '\U0000201A', + "sc;": '\U0000227B', + "scE;": '\U00002AB4', + "scap;": '\U00002AB8', + "scaron;": '\U00000161', + "sccue;": '\U0000227D', + "sce;": '\U00002AB0', + "scedil;": '\U0000015F', + "scirc;": '\U0000015D', + "scnE;": '\U00002AB6', + "scnap;": '\U00002ABA', + "scnsim;": '\U000022E9', + "scpolint;": '\U00002A13', + "scsim;": '\U0000227F', + "scy;": '\U00000441', + "sdot;": '\U000022C5', + "sdotb;": '\U000022A1', + "sdote;": '\U00002A66', + "seArr;": '\U000021D8', + "searhk;": '\U00002925', + "searr;": '\U00002198', + "searrow;": '\U00002198', + "sect;": '\U000000A7', + "semi;": '\U0000003B', + "seswar;": '\U00002929', + "setminus;": '\U00002216', + "setmn;": '\U00002216', + "sext;": '\U00002736', + "sfr;": '\U0001D530', + "sfrown;": '\U00002322', + "sharp;": '\U0000266F', + "shchcy;": '\U00000449', + "shcy;": '\U00000448', + "shortmid;": '\U00002223', + "shortparallel;": '\U00002225', + "shy;": '\U000000AD', + "sigma;": '\U000003C3', + "sigmaf;": '\U000003C2', + "sigmav;": '\U000003C2', + "sim;": '\U0000223C', + "simdot;": '\U00002A6A', + "sime;": '\U00002243', + "simeq;": '\U00002243', + "simg;": '\U00002A9E', + "simgE;": '\U00002AA0', + "siml;": '\U00002A9D', + "simlE;": '\U00002A9F', + "simne;": '\U00002246', + "simplus;": '\U00002A24', + "simrarr;": '\U00002972', + "slarr;": '\U00002190', + "smallsetminus;": '\U00002216', + "smashp;": '\U00002A33', + "smeparsl;": '\U000029E4', + "smid;": '\U00002223', + "smile;": '\U00002323', + "smt;": '\U00002AAA', + "smte;": '\U00002AAC', + "softcy;": '\U0000044C', + "sol;": '\U0000002F', + "solb;": '\U000029C4', + "solbar;": '\U0000233F', + "sopf;": '\U0001D564', + "spades;": '\U00002660', + "spadesuit;": '\U00002660', + "spar;": '\U00002225', + "sqcap;": '\U00002293', + "sqcup;": '\U00002294', + "sqsub;": '\U0000228F', + "sqsube;": '\U00002291', + "sqsubset;": '\U0000228F', + "sqsubseteq;": '\U00002291', + "sqsup;": '\U00002290', + "sqsupe;": '\U00002292', + "sqsupset;": '\U00002290', + "sqsupseteq;": '\U00002292', + "squ;": '\U000025A1', + "square;": '\U000025A1', + "squarf;": '\U000025AA', + "squf;": '\U000025AA', + "srarr;": '\U00002192', + "sscr;": '\U0001D4C8', + "ssetmn;": '\U00002216', + "ssmile;": '\U00002323', + "sstarf;": '\U000022C6', + "star;": '\U00002606', + "starf;": '\U00002605', + "straightepsilon;": '\U000003F5', + "straightphi;": '\U000003D5', + "strns;": '\U000000AF', + "sub;": '\U00002282', + "subE;": '\U00002AC5', + "subdot;": '\U00002ABD', + "sube;": '\U00002286', + "subedot;": '\U00002AC3', + "submult;": '\U00002AC1', + "subnE;": '\U00002ACB', + "subne;": '\U0000228A', + "subplus;": '\U00002ABF', + "subrarr;": '\U00002979', + "subset;": '\U00002282', + "subseteq;": '\U00002286', + "subseteqq;": '\U00002AC5', + "subsetneq;": '\U0000228A', + "subsetneqq;": '\U00002ACB', + "subsim;": '\U00002AC7', + "subsub;": '\U00002AD5', + "subsup;": '\U00002AD3', + "succ;": '\U0000227B', + "succapprox;": '\U00002AB8', + "succcurlyeq;": '\U0000227D', + "succeq;": '\U00002AB0', + "succnapprox;": '\U00002ABA', + "succneqq;": '\U00002AB6', + "succnsim;": '\U000022E9', + "succsim;": '\U0000227F', + "sum;": '\U00002211', + "sung;": '\U0000266A', + "sup;": '\U00002283', + "sup1;": '\U000000B9', + "sup2;": '\U000000B2', + "sup3;": '\U000000B3', + "supE;": '\U00002AC6', + "supdot;": '\U00002ABE', + "supdsub;": '\U00002AD8', + "supe;": '\U00002287', + "supedot;": '\U00002AC4', + "suphsol;": '\U000027C9', + "suphsub;": '\U00002AD7', + "suplarr;": '\U0000297B', + "supmult;": '\U00002AC2', + "supnE;": '\U00002ACC', + "supne;": '\U0000228B', + "supplus;": '\U00002AC0', + "supset;": '\U00002283', + "supseteq;": '\U00002287', + "supseteqq;": '\U00002AC6', + "supsetneq;": '\U0000228B', + "supsetneqq;": '\U00002ACC', + "supsim;": '\U00002AC8', + "supsub;": '\U00002AD4', + "supsup;": '\U00002AD6', + "swArr;": '\U000021D9', + "swarhk;": '\U00002926', + "swarr;": '\U00002199', + "swarrow;": '\U00002199', + "swnwar;": '\U0000292A', + "szlig;": '\U000000DF', + "target;": '\U00002316', + "tau;": '\U000003C4', + "tbrk;": '\U000023B4', + "tcaron;": '\U00000165', + "tcedil;": '\U00000163', + "tcy;": '\U00000442', + "tdot;": '\U000020DB', + "telrec;": '\U00002315', + "tfr;": '\U0001D531', + "there4;": '\U00002234', + "therefore;": '\U00002234', + "theta;": '\U000003B8', + "thetasym;": '\U000003D1', + "thetav;": '\U000003D1', + "thickapprox;": '\U00002248', + "thicksim;": '\U0000223C', + "thinsp;": '\U00002009', + "thkap;": '\U00002248', + "thksim;": '\U0000223C', + "thorn;": '\U000000FE', + "tilde;": '\U000002DC', + "times;": '\U000000D7', + "timesb;": '\U000022A0', + "timesbar;": '\U00002A31', + "timesd;": '\U00002A30', + "tint;": '\U0000222D', + "toea;": '\U00002928', + "top;": '\U000022A4', + "topbot;": '\U00002336', + "topcir;": '\U00002AF1', + "topf;": '\U0001D565', + "topfork;": '\U00002ADA', + "tosa;": '\U00002929', + "tprime;": '\U00002034', + "trade;": '\U00002122', + "triangle;": '\U000025B5', + "triangledown;": '\U000025BF', + "triangleleft;": '\U000025C3', + "trianglelefteq;": '\U000022B4', + "triangleq;": '\U0000225C', + "triangleright;": '\U000025B9', + "trianglerighteq;": '\U000022B5', + "tridot;": '\U000025EC', + "trie;": '\U0000225C', + "triminus;": '\U00002A3A', + "triplus;": '\U00002A39', + "trisb;": '\U000029CD', + "tritime;": '\U00002A3B', + "trpezium;": '\U000023E2', + "tscr;": '\U0001D4C9', + "tscy;": '\U00000446', + "tshcy;": '\U0000045B', + "tstrok;": '\U00000167', + "twixt;": '\U0000226C', + "twoheadleftarrow;": '\U0000219E', + "twoheadrightarrow;": '\U000021A0', + "uArr;": '\U000021D1', + "uHar;": '\U00002963', + "uacute;": '\U000000FA', + "uarr;": '\U00002191', + "ubrcy;": '\U0000045E', + "ubreve;": '\U0000016D', + "ucirc;": '\U000000FB', + "ucy;": '\U00000443', + "udarr;": '\U000021C5', + "udblac;": '\U00000171', + "udhar;": '\U0000296E', + "ufisht;": '\U0000297E', + "ufr;": '\U0001D532', + "ugrave;": '\U000000F9', + "uharl;": '\U000021BF', + "uharr;": '\U000021BE', + "uhblk;": '\U00002580', + "ulcorn;": '\U0000231C', + "ulcorner;": '\U0000231C', + "ulcrop;": '\U0000230F', + "ultri;": '\U000025F8', + "umacr;": '\U0000016B', + "uml;": '\U000000A8', + "uogon;": '\U00000173', + "uopf;": '\U0001D566', + "uparrow;": '\U00002191', + "updownarrow;": '\U00002195', + "upharpoonleft;": '\U000021BF', + "upharpoonright;": '\U000021BE', + "uplus;": '\U0000228E', + "upsi;": '\U000003C5', + "upsih;": '\U000003D2', + "upsilon;": '\U000003C5', + "upuparrows;": '\U000021C8', + "urcorn;": '\U0000231D', + "urcorner;": '\U0000231D', + "urcrop;": '\U0000230E', + "uring;": '\U0000016F', + "urtri;": '\U000025F9', + "uscr;": '\U0001D4CA', + "utdot;": '\U000022F0', + "utilde;": '\U00000169', + "utri;": '\U000025B5', + "utrif;": '\U000025B4', + "uuarr;": '\U000021C8', + "uuml;": '\U000000FC', + "uwangle;": '\U000029A7', + "vArr;": '\U000021D5', + "vBar;": '\U00002AE8', + "vBarv;": '\U00002AE9', + "vDash;": '\U000022A8', + "vangrt;": '\U0000299C', + "varepsilon;": '\U000003F5', + "varkappa;": '\U000003F0', + "varnothing;": '\U00002205', + "varphi;": '\U000003D5', + "varpi;": '\U000003D6', + "varpropto;": '\U0000221D', + "varr;": '\U00002195', + "varrho;": '\U000003F1', + "varsigma;": '\U000003C2', + "vartheta;": '\U000003D1', + "vartriangleleft;": '\U000022B2', + "vartriangleright;": '\U000022B3', + "vcy;": '\U00000432', + "vdash;": '\U000022A2', + "vee;": '\U00002228', + "veebar;": '\U000022BB', + "veeeq;": '\U0000225A', + "vellip;": '\U000022EE', + "verbar;": '\U0000007C', + "vert;": '\U0000007C', + "vfr;": '\U0001D533', + "vltri;": '\U000022B2', + "vopf;": '\U0001D567', + "vprop;": '\U0000221D', + "vrtri;": '\U000022B3', + "vscr;": '\U0001D4CB', + "vzigzag;": '\U0000299A', + "wcirc;": '\U00000175', + "wedbar;": '\U00002A5F', + "wedge;": '\U00002227', + "wedgeq;": '\U00002259', + "weierp;": '\U00002118', + "wfr;": '\U0001D534', + "wopf;": '\U0001D568', + "wp;": '\U00002118', + "wr;": '\U00002240', + "wreath;": '\U00002240', + "wscr;": '\U0001D4CC', + "xcap;": '\U000022C2', + "xcirc;": '\U000025EF', + "xcup;": '\U000022C3', + "xdtri;": '\U000025BD', + "xfr;": '\U0001D535', + "xhArr;": '\U000027FA', + "xharr;": '\U000027F7', + "xi;": '\U000003BE', + "xlArr;": '\U000027F8', + "xlarr;": '\U000027F5', + "xmap;": '\U000027FC', + "xnis;": '\U000022FB', + "xodot;": '\U00002A00', + "xopf;": '\U0001D569', + "xoplus;": '\U00002A01', + "xotime;": '\U00002A02', + "xrArr;": '\U000027F9', + "xrarr;": '\U000027F6', + "xscr;": '\U0001D4CD', + "xsqcup;": '\U00002A06', + "xuplus;": '\U00002A04', + "xutri;": '\U000025B3', + "xvee;": '\U000022C1', + "xwedge;": '\U000022C0', + "yacute;": '\U000000FD', + "yacy;": '\U0000044F', + "ycirc;": '\U00000177', + "ycy;": '\U0000044B', + "yen;": '\U000000A5', + "yfr;": '\U0001D536', + "yicy;": '\U00000457', + "yopf;": '\U0001D56A', + "yscr;": '\U0001D4CE', + "yucy;": '\U0000044E', + "yuml;": '\U000000FF', + "zacute;": '\U0000017A', + "zcaron;": '\U0000017E', + "zcy;": '\U00000437', + "zdot;": '\U0000017C', + "zeetrf;": '\U00002128', + "zeta;": '\U000003B6', + "zfr;": '\U0001D537', + "zhcy;": '\U00000436', + "zigrarr;": '\U000021DD', + "zopf;": '\U0001D56B', + "zscr;": '\U0001D4CF', + "zwj;": '\U0000200D', + "zwnj;": '\U0000200C', + "AElig": '\U000000C6', + "AMP": '\U00000026', + "Aacute": '\U000000C1', + "Acirc": '\U000000C2', + "Agrave": '\U000000C0', + "Aring": '\U000000C5', + "Atilde": '\U000000C3', + "Auml": '\U000000C4', + "COPY": '\U000000A9', + "Ccedil": '\U000000C7', + "ETH": '\U000000D0', + "Eacute": '\U000000C9', + "Ecirc": '\U000000CA', + "Egrave": '\U000000C8', + "Euml": '\U000000CB', + "GT": '\U0000003E', + "Iacute": '\U000000CD', + "Icirc": '\U000000CE', + "Igrave": '\U000000CC', + "Iuml": '\U000000CF', + "LT": '\U0000003C', + "Ntilde": '\U000000D1', + "Oacute": '\U000000D3', + "Ocirc": '\U000000D4', + "Ograve": '\U000000D2', + "Oslash": '\U000000D8', + "Otilde": '\U000000D5', + "Ouml": '\U000000D6', + "QUOT": '\U00000022', + "REG": '\U000000AE', + "THORN": '\U000000DE', + "Uacute": '\U000000DA', + "Ucirc": '\U000000DB', + "Ugrave": '\U000000D9', + "Uuml": '\U000000DC', + "Yacute": '\U000000DD', + "aacute": '\U000000E1', + "acirc": '\U000000E2', + "acute": '\U000000B4', + "aelig": '\U000000E6', + "agrave": '\U000000E0', + "amp": '\U00000026', + "aring": '\U000000E5', + "atilde": '\U000000E3', + "auml": '\U000000E4', + "brvbar": '\U000000A6', + "ccedil": '\U000000E7', + "cedil": '\U000000B8', + "cent": '\U000000A2', + "copy": '\U000000A9', + "curren": '\U000000A4', + "deg": '\U000000B0', + "divide": '\U000000F7', + "eacute": '\U000000E9', + "ecirc": '\U000000EA', + "egrave": '\U000000E8', + "eth": '\U000000F0', + "euml": '\U000000EB', + "frac12": '\U000000BD', + "frac14": '\U000000BC', + "frac34": '\U000000BE', + "gt": '\U0000003E', + "iacute": '\U000000ED', + "icirc": '\U000000EE', + "iexcl": '\U000000A1', + "igrave": '\U000000EC', + "iquest": '\U000000BF', + "iuml": '\U000000EF', + "laquo": '\U000000AB', + "lt": '\U0000003C', + "macr": '\U000000AF', + "micro": '\U000000B5', + "middot": '\U000000B7', + "nbsp": '\U000000A0', + "not": '\U000000AC', + "ntilde": '\U000000F1', + "oacute": '\U000000F3', + "ocirc": '\U000000F4', + "ograve": '\U000000F2', + "ordf": '\U000000AA', + "ordm": '\U000000BA', + "oslash": '\U000000F8', + "otilde": '\U000000F5', + "ouml": '\U000000F6', + "para": '\U000000B6', + "plusmn": '\U000000B1', + "pound": '\U000000A3', + "quot": '\U00000022', + "raquo": '\U000000BB', + "reg": '\U000000AE', + "sect": '\U000000A7', + "shy": '\U000000AD', + "sup1": '\U000000B9', + "sup2": '\U000000B2', + "sup3": '\U000000B3', + "szlig": '\U000000DF', + "thorn": '\U000000FE', + "times": '\U000000D7', + "uacute": '\U000000FA', + "ucirc": '\U000000FB', + "ugrave": '\U000000F9', + "uml": '\U000000A8', + "uuml": '\U000000FC', + "yacute": '\U000000FD', + "yen": '\U000000A5', + "yuml": '\U000000FF', +} + +// HTML entities that are two unicode codepoints. +var entity2 = map[string][2]rune{ + // TODO(nigeltao): Handle replacements that are wider than their names. + // "nLt;": {'\u226A', '\u20D2'}, + // "nGt;": {'\u226B', '\u20D2'}, + "NotEqualTilde;": {'\u2242', '\u0338'}, + "NotGreaterFullEqual;": {'\u2267', '\u0338'}, + "NotGreaterGreater;": {'\u226B', '\u0338'}, + "NotGreaterSlantEqual;": {'\u2A7E', '\u0338'}, + "NotHumpDownHump;": {'\u224E', '\u0338'}, + "NotHumpEqual;": {'\u224F', '\u0338'}, + "NotLeftTriangleBar;": {'\u29CF', '\u0338'}, + "NotLessLess;": {'\u226A', '\u0338'}, + "NotLessSlantEqual;": {'\u2A7D', '\u0338'}, + "NotNestedGreaterGreater;": {'\u2AA2', '\u0338'}, + "NotNestedLessLess;": {'\u2AA1', '\u0338'}, + "NotPrecedesEqual;": {'\u2AAF', '\u0338'}, + "NotRightTriangleBar;": {'\u29D0', '\u0338'}, + "NotSquareSubset;": {'\u228F', '\u0338'}, + "NotSquareSuperset;": {'\u2290', '\u0338'}, + "NotSubset;": {'\u2282', '\u20D2'}, + "NotSucceedsEqual;": {'\u2AB0', '\u0338'}, + "NotSucceedsTilde;": {'\u227F', '\u0338'}, + "NotSuperset;": {'\u2283', '\u20D2'}, + "ThickSpace;": {'\u205F', '\u200A'}, + "acE;": {'\u223E', '\u0333'}, + "bne;": {'\u003D', '\u20E5'}, + "bnequiv;": {'\u2261', '\u20E5'}, + "caps;": {'\u2229', '\uFE00'}, + "cups;": {'\u222A', '\uFE00'}, + "fjlig;": {'\u0066', '\u006A'}, + "gesl;": {'\u22DB', '\uFE00'}, + "gvertneqq;": {'\u2269', '\uFE00'}, + "gvnE;": {'\u2269', '\uFE00'}, + "lates;": {'\u2AAD', '\uFE00'}, + "lesg;": {'\u22DA', '\uFE00'}, + "lvertneqq;": {'\u2268', '\uFE00'}, + "lvnE;": {'\u2268', '\uFE00'}, + "nGg;": {'\u22D9', '\u0338'}, + "nGtv;": {'\u226B', '\u0338'}, + "nLl;": {'\u22D8', '\u0338'}, + "nLtv;": {'\u226A', '\u0338'}, + "nang;": {'\u2220', '\u20D2'}, + "napE;": {'\u2A70', '\u0338'}, + "napid;": {'\u224B', '\u0338'}, + "nbump;": {'\u224E', '\u0338'}, + "nbumpe;": {'\u224F', '\u0338'}, + "ncongdot;": {'\u2A6D', '\u0338'}, + "nedot;": {'\u2250', '\u0338'}, + "nesim;": {'\u2242', '\u0338'}, + "ngE;": {'\u2267', '\u0338'}, + "ngeqq;": {'\u2267', '\u0338'}, + "ngeqslant;": {'\u2A7E', '\u0338'}, + "nges;": {'\u2A7E', '\u0338'}, + "nlE;": {'\u2266', '\u0338'}, + "nleqq;": {'\u2266', '\u0338'}, + "nleqslant;": {'\u2A7D', '\u0338'}, + "nles;": {'\u2A7D', '\u0338'}, + "notinE;": {'\u22F9', '\u0338'}, + "notindot;": {'\u22F5', '\u0338'}, + "nparsl;": {'\u2AFD', '\u20E5'}, + "npart;": {'\u2202', '\u0338'}, + "npre;": {'\u2AAF', '\u0338'}, + "npreceq;": {'\u2AAF', '\u0338'}, + "nrarrc;": {'\u2933', '\u0338'}, + "nrarrw;": {'\u219D', '\u0338'}, + "nsce;": {'\u2AB0', '\u0338'}, + "nsubE;": {'\u2AC5', '\u0338'}, + "nsubset;": {'\u2282', '\u20D2'}, + "nsubseteqq;": {'\u2AC5', '\u0338'}, + "nsucceq;": {'\u2AB0', '\u0338'}, + "nsupE;": {'\u2AC6', '\u0338'}, + "nsupset;": {'\u2283', '\u20D2'}, + "nsupseteqq;": {'\u2AC6', '\u0338'}, + "nvap;": {'\u224D', '\u20D2'}, + "nvge;": {'\u2265', '\u20D2'}, + "nvgt;": {'\u003E', '\u20D2'}, + "nvle;": {'\u2264', '\u20D2'}, + "nvlt;": {'\u003C', '\u20D2'}, + "nvltrie;": {'\u22B4', '\u20D2'}, + "nvrtrie;": {'\u22B5', '\u20D2'}, + "nvsim;": {'\u223C', '\u20D2'}, + "race;": {'\u223D', '\u0331'}, + "smtes;": {'\u2AAC', '\uFE00'}, + "sqcaps;": {'\u2293', '\uFE00'}, + "sqcups;": {'\u2294', '\uFE00'}, + "varsubsetneq;": {'\u228A', '\uFE00'}, + "varsubsetneqq;": {'\u2ACB', '\uFE00'}, + "varsupsetneq;": {'\u228B', '\uFE00'}, + "varsupsetneqq;": {'\u2ACC', '\uFE00'}, + "vnsub;": {'\u2282', '\u20D2'}, + "vnsup;": {'\u2283', '\u20D2'}, + "vsubnE;": {'\u2ACB', '\uFE00'}, + "vsubne;": {'\u228A', '\uFE00'}, + "vsupnE;": {'\u2ACC', '\uFE00'}, + "vsupne;": {'\u228B', '\uFE00'}, +} diff --git a/vendor/golang.org/x/net/html/escape.go b/vendor/golang.org/x/net/html/escape.go new file mode 100644 index 00000000..d8561396 --- /dev/null +++ b/vendor/golang.org/x/net/html/escape.go @@ -0,0 +1,258 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "bytes" + "strings" + "unicode/utf8" +) + +// These replacements permit compatibility with old numeric entities that +// assumed Windows-1252 encoding. +// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference +var replacementTable = [...]rune{ + '\u20AC', // First entry is what 0x80 should be replaced with. + '\u0081', + '\u201A', + '\u0192', + '\u201E', + '\u2026', + '\u2020', + '\u2021', + '\u02C6', + '\u2030', + '\u0160', + '\u2039', + '\u0152', + '\u008D', + '\u017D', + '\u008F', + '\u0090', + '\u2018', + '\u2019', + '\u201C', + '\u201D', + '\u2022', + '\u2013', + '\u2014', + '\u02DC', + '\u2122', + '\u0161', + '\u203A', + '\u0153', + '\u009D', + '\u017E', + '\u0178', // Last entry is 0x9F. + // 0x00->'\uFFFD' is handled programmatically. + // 0x0D->'\u000D' is a no-op. +} + +// unescapeEntity reads an entity like "<" from b[src:] and writes the +// corresponding "<" to b[dst:], returning the incremented dst and src cursors. +// Precondition: b[src] == '&' && dst <= src. +// attribute should be true if parsing an attribute value. +func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) { + // https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference + + // i starts at 1 because we already know that s[0] == '&'. + i, s := 1, b[src:] + + if len(s) <= 1 { + b[dst] = b[src] + return dst + 1, src + 1 + } + + if s[i] == '#' { + if len(s) <= 3 { // We need to have at least "&#.". + b[dst] = b[src] + return dst + 1, src + 1 + } + i++ + c := s[i] + hex := false + if c == 'x' || c == 'X' { + hex = true + i++ + } + + x := '\x00' + for i < len(s) { + c = s[i] + i++ + if hex { + if '0' <= c && c <= '9' { + x = 16*x + rune(c) - '0' + continue + } else if 'a' <= c && c <= 'f' { + x = 16*x + rune(c) - 'a' + 10 + continue + } else if 'A' <= c && c <= 'F' { + x = 16*x + rune(c) - 'A' + 10 + continue + } + } else if '0' <= c && c <= '9' { + x = 10*x + rune(c) - '0' + continue + } + if c != ';' { + i-- + } + break + } + + if i <= 3 { // No characters matched. + b[dst] = b[src] + return dst + 1, src + 1 + } + + if 0x80 <= x && x <= 0x9F { + // Replace characters from Windows-1252 with UTF-8 equivalents. + x = replacementTable[x-0x80] + } else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF { + // Replace invalid characters with the replacement character. + x = '\uFFFD' + } + + return dst + utf8.EncodeRune(b[dst:], x), src + i + } + + // Consume the maximum number of characters possible, with the + // consumed characters matching one of the named references. + + for i < len(s) { + c := s[i] + i++ + // Lower-cased characters are more common in entities, so we check for them first. + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { + continue + } + if c != ';' { + i-- + } + break + } + + entityName := string(s[1:i]) + if entityName == "" { + // No-op. + } else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' { + // No-op. + } else if x := entity[entityName]; x != 0 { + return dst + utf8.EncodeRune(b[dst:], x), src + i + } else if x := entity2[entityName]; x[0] != 0 { + dst1 := dst + utf8.EncodeRune(b[dst:], x[0]) + return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i + } else if !attribute { + maxLen := len(entityName) - 1 + if maxLen > longestEntityWithoutSemicolon { + maxLen = longestEntityWithoutSemicolon + } + for j := maxLen; j > 1; j-- { + if x := entity[entityName[:j]]; x != 0 { + return dst + utf8.EncodeRune(b[dst:], x), src + j + 1 + } + } + } + + dst1, src1 = dst+i, src+i + copy(b[dst:dst1], b[src:src1]) + return dst1, src1 +} + +// unescape unescapes b's entities in-place, so that "a<b" becomes "a': + esc = ">" + case '"': + // """ is shorter than """. + esc = """ + case '\r': + esc = " " + default: + panic("unrecognized escape character") + } + s = s[i+1:] + if _, err := w.WriteString(esc); err != nil { + return err + } + i = strings.IndexAny(s, escapedChars) + } + _, err := w.WriteString(s) + return err +} + +// EscapeString escapes special characters like "<" to become "<". It +// escapes only five such characters: <, >, &, ' and ". +// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't +// always true. +func EscapeString(s string) string { + if strings.IndexAny(s, escapedChars) == -1 { + return s + } + var buf bytes.Buffer + escape(&buf, s) + return buf.String() +} + +// UnescapeString unescapes entities like "<" to become "<". It unescapes a +// larger range of entities than EscapeString escapes. For example, "á" +// unescapes to "á", as does "á" and "&xE1;". +// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't +// always true. +func UnescapeString(s string) string { + for _, c := range s { + if c == '&' { + return string(unescape([]byte(s), false)) + } + } + return s +} diff --git a/vendor/golang.org/x/net/html/foreign.go b/vendor/golang.org/x/net/html/foreign.go new file mode 100644 index 00000000..9da9e9dc --- /dev/null +++ b/vendor/golang.org/x/net/html/foreign.go @@ -0,0 +1,222 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "strings" +) + +func adjustAttributeNames(aa []Attribute, nameMap map[string]string) { + for i := range aa { + if newName, ok := nameMap[aa[i].Key]; ok { + aa[i].Key = newName + } + } +} + +func adjustForeignAttributes(aa []Attribute) { + for i, a := range aa { + if a.Key == "" || a.Key[0] != 'x' { + continue + } + switch a.Key { + case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show", + "xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink": + j := strings.Index(a.Key, ":") + aa[i].Namespace = a.Key[:j] + aa[i].Key = a.Key[j+1:] + } + } +} + +func htmlIntegrationPoint(n *Node) bool { + if n.Type != ElementNode { + return false + } + switch n.Namespace { + case "math": + if n.Data == "annotation-xml" { + for _, a := range n.Attr { + if a.Key == "encoding" { + val := strings.ToLower(a.Val) + if val == "text/html" || val == "application/xhtml+xml" { + return true + } + } + } + } + case "svg": + switch n.Data { + case "desc", "foreignObject", "title": + return true + } + } + return false +} + +func mathMLTextIntegrationPoint(n *Node) bool { + if n.Namespace != "math" { + return false + } + switch n.Data { + case "mi", "mo", "mn", "ms", "mtext": + return true + } + return false +} + +// Section 12.2.6.5. +var breakout = map[string]bool{ + "b": true, + "big": true, + "blockquote": true, + "body": true, + "br": true, + "center": true, + "code": true, + "dd": true, + "div": true, + "dl": true, + "dt": true, + "em": true, + "embed": true, + "h1": true, + "h2": true, + "h3": true, + "h4": true, + "h5": true, + "h6": true, + "head": true, + "hr": true, + "i": true, + "img": true, + "li": true, + "listing": true, + "menu": true, + "meta": true, + "nobr": true, + "ol": true, + "p": true, + "pre": true, + "ruby": true, + "s": true, + "small": true, + "span": true, + "strong": true, + "strike": true, + "sub": true, + "sup": true, + "table": true, + "tt": true, + "u": true, + "ul": true, + "var": true, +} + +// Section 12.2.6.5. +var svgTagNameAdjustments = map[string]string{ + "altglyph": "altGlyph", + "altglyphdef": "altGlyphDef", + "altglyphitem": "altGlyphItem", + "animatecolor": "animateColor", + "animatemotion": "animateMotion", + "animatetransform": "animateTransform", + "clippath": "clipPath", + "feblend": "feBlend", + "fecolormatrix": "feColorMatrix", + "fecomponenttransfer": "feComponentTransfer", + "fecomposite": "feComposite", + "feconvolvematrix": "feConvolveMatrix", + "fediffuselighting": "feDiffuseLighting", + "fedisplacementmap": "feDisplacementMap", + "fedistantlight": "feDistantLight", + "feflood": "feFlood", + "fefunca": "feFuncA", + "fefuncb": "feFuncB", + "fefuncg": "feFuncG", + "fefuncr": "feFuncR", + "fegaussianblur": "feGaussianBlur", + "feimage": "feImage", + "femerge": "feMerge", + "femergenode": "feMergeNode", + "femorphology": "feMorphology", + "feoffset": "feOffset", + "fepointlight": "fePointLight", + "fespecularlighting": "feSpecularLighting", + "fespotlight": "feSpotLight", + "fetile": "feTile", + "feturbulence": "feTurbulence", + "foreignobject": "foreignObject", + "glyphref": "glyphRef", + "lineargradient": "linearGradient", + "radialgradient": "radialGradient", + "textpath": "textPath", +} + +// Section 12.2.6.1 +var mathMLAttributeAdjustments = map[string]string{ + "definitionurl": "definitionURL", +} + +var svgAttributeAdjustments = map[string]string{ + "attributename": "attributeName", + "attributetype": "attributeType", + "basefrequency": "baseFrequency", + "baseprofile": "baseProfile", + "calcmode": "calcMode", + "clippathunits": "clipPathUnits", + "diffuseconstant": "diffuseConstant", + "edgemode": "edgeMode", + "filterunits": "filterUnits", + "glyphref": "glyphRef", + "gradienttransform": "gradientTransform", + "gradientunits": "gradientUnits", + "kernelmatrix": "kernelMatrix", + "kernelunitlength": "kernelUnitLength", + "keypoints": "keyPoints", + "keysplines": "keySplines", + "keytimes": "keyTimes", + "lengthadjust": "lengthAdjust", + "limitingconeangle": "limitingConeAngle", + "markerheight": "markerHeight", + "markerunits": "markerUnits", + "markerwidth": "markerWidth", + "maskcontentunits": "maskContentUnits", + "maskunits": "maskUnits", + "numoctaves": "numOctaves", + "pathlength": "pathLength", + "patterncontentunits": "patternContentUnits", + "patterntransform": "patternTransform", + "patternunits": "patternUnits", + "pointsatx": "pointsAtX", + "pointsaty": "pointsAtY", + "pointsatz": "pointsAtZ", + "preservealpha": "preserveAlpha", + "preserveaspectratio": "preserveAspectRatio", + "primitiveunits": "primitiveUnits", + "refx": "refX", + "refy": "refY", + "repeatcount": "repeatCount", + "repeatdur": "repeatDur", + "requiredextensions": "requiredExtensions", + "requiredfeatures": "requiredFeatures", + "specularconstant": "specularConstant", + "specularexponent": "specularExponent", + "spreadmethod": "spreadMethod", + "startoffset": "startOffset", + "stddeviation": "stdDeviation", + "stitchtiles": "stitchTiles", + "surfacescale": "surfaceScale", + "systemlanguage": "systemLanguage", + "tablevalues": "tableValues", + "targetx": "targetX", + "targety": "targetY", + "textlength": "textLength", + "viewbox": "viewBox", + "viewtarget": "viewTarget", + "xchannelselector": "xChannelSelector", + "ychannelselector": "yChannelSelector", + "zoomandpan": "zoomAndPan", +} diff --git a/vendor/golang.org/x/net/html/node.go b/vendor/golang.org/x/net/html/node.go new file mode 100644 index 00000000..1350eef2 --- /dev/null +++ b/vendor/golang.org/x/net/html/node.go @@ -0,0 +1,225 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "golang.org/x/net/html/atom" +) + +// A NodeType is the type of a Node. +type NodeType uint32 + +const ( + ErrorNode NodeType = iota + TextNode + DocumentNode + ElementNode + CommentNode + DoctypeNode + // RawNode nodes are not returned by the parser, but can be part of the + // Node tree passed to func Render to insert raw HTML (without escaping). + // If so, this package makes no guarantee that the rendered HTML is secure + // (from e.g. Cross Site Scripting attacks) or well-formed. + RawNode + scopeMarkerNode +) + +// Section 12.2.4.3 says "The markers are inserted when entering applet, +// object, marquee, template, td, th, and caption elements, and are used +// to prevent formatting from "leaking" into applet, object, marquee, +// template, td, th, and caption elements". +var scopeMarker = Node{Type: scopeMarkerNode} + +// A Node consists of a NodeType and some Data (tag name for element nodes, +// content for text) and are part of a tree of Nodes. Element nodes may also +// have a Namespace and contain a slice of Attributes. Data is unescaped, so +// that it looks like "a 0 { + return (*s)[i-1] + } + return nil +} + +// index returns the index of the top-most occurrence of n in the stack, or -1 +// if n is not present. +func (s *nodeStack) index(n *Node) int { + for i := len(*s) - 1; i >= 0; i-- { + if (*s)[i] == n { + return i + } + } + return -1 +} + +// contains returns whether a is within s. +func (s *nodeStack) contains(a atom.Atom) bool { + for _, n := range *s { + if n.DataAtom == a && n.Namespace == "" { + return true + } + } + return false +} + +// insert inserts a node at the given index. +func (s *nodeStack) insert(i int, n *Node) { + (*s) = append(*s, nil) + copy((*s)[i+1:], (*s)[i:]) + (*s)[i] = n +} + +// remove removes a node from the stack. It is a no-op if n is not present. +func (s *nodeStack) remove(n *Node) { + i := s.index(n) + if i == -1 { + return + } + copy((*s)[i:], (*s)[i+1:]) + j := len(*s) - 1 + (*s)[j] = nil + *s = (*s)[:j] +} + +type insertionModeStack []insertionMode + +func (s *insertionModeStack) pop() (im insertionMode) { + i := len(*s) + im = (*s)[i-1] + *s = (*s)[:i-1] + return im +} + +func (s *insertionModeStack) top() insertionMode { + if i := len(*s); i > 0 { + return (*s)[i-1] + } + return nil +} diff --git a/vendor/golang.org/x/net/html/parse.go b/vendor/golang.org/x/net/html/parse.go new file mode 100644 index 00000000..038941d7 --- /dev/null +++ b/vendor/golang.org/x/net/html/parse.go @@ -0,0 +1,2460 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "errors" + "fmt" + "io" + "strings" + + a "golang.org/x/net/html/atom" +) + +// A parser implements the HTML5 parsing algorithm: +// https://html.spec.whatwg.org/multipage/syntax.html#tree-construction +type parser struct { + // tokenizer provides the tokens for the parser. + tokenizer *Tokenizer + // tok is the most recently read token. + tok Token + // Self-closing tags like
are treated as start tags, except that + // hasSelfClosingToken is set while they are being processed. + hasSelfClosingToken bool + // doc is the document root element. + doc *Node + // The stack of open elements (section 12.2.4.2) and active formatting + // elements (section 12.2.4.3). + oe, afe nodeStack + // Element pointers (section 12.2.4.4). + head, form *Node + // Other parsing state flags (section 12.2.4.5). + scripting, framesetOK bool + // The stack of template insertion modes + templateStack insertionModeStack + // im is the current insertion mode. + im insertionMode + // originalIM is the insertion mode to go back to after completing a text + // or inTableText insertion mode. + originalIM insertionMode + // fosterParenting is whether new elements should be inserted according to + // the foster parenting rules (section 12.2.6.1). + fosterParenting bool + // quirks is whether the parser is operating in "quirks mode." + quirks bool + // fragment is whether the parser is parsing an HTML fragment. + fragment bool + // context is the context element when parsing an HTML fragment + // (section 12.4). + context *Node +} + +func (p *parser) top() *Node { + if n := p.oe.top(); n != nil { + return n + } + return p.doc +} + +// Stop tags for use in popUntil. These come from section 12.2.4.2. +var ( + defaultScopeStopTags = map[string][]a.Atom{ + "": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template}, + "math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext}, + "svg": {a.Desc, a.ForeignObject, a.Title}, + } +) + +type scope int + +const ( + defaultScope scope = iota + listItemScope + buttonScope + tableScope + tableRowScope + tableBodyScope + selectScope +) + +// popUntil pops the stack of open elements at the highest element whose tag +// is in matchTags, provided there is no higher element in the scope's stop +// tags (as defined in section 12.2.4.2). It returns whether or not there was +// such an element. If there was not, popUntil leaves the stack unchanged. +// +// For example, the set of stop tags for table scope is: "html", "table". If +// the stack was: +// ["html", "body", "font", "table", "b", "i", "u"] +// then popUntil(tableScope, "font") would return false, but +// popUntil(tableScope, "i") would return true and the stack would become: +// ["html", "body", "font", "table", "b"] +// +// If an element's tag is in both the stop tags and matchTags, then the stack +// will be popped and the function returns true (provided, of course, there was +// no higher element in the stack that was also in the stop tags). For example, +// popUntil(tableScope, "table") returns true and leaves: +// ["html", "body", "font"] +func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool { + if i := p.indexOfElementInScope(s, matchTags...); i != -1 { + p.oe = p.oe[:i] + return true + } + return false +} + +// indexOfElementInScope returns the index in p.oe of the highest element whose +// tag is in matchTags that is in scope. If no matching element is in scope, it +// returns -1. +func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + if p.oe[i].Namespace == "" { + for _, t := range matchTags { + if t == tagAtom { + return i + } + } + switch s { + case defaultScope: + // No-op. + case listItemScope: + if tagAtom == a.Ol || tagAtom == a.Ul { + return -1 + } + case buttonScope: + if tagAtom == a.Button { + return -1 + } + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table || tagAtom == a.Template { + return -1 + } + case selectScope: + if tagAtom != a.Optgroup && tagAtom != a.Option { + return -1 + } + default: + panic("unreachable") + } + } + switch s { + case defaultScope, listItemScope, buttonScope: + for _, t := range defaultScopeStopTags[p.oe[i].Namespace] { + if t == tagAtom { + return -1 + } + } + } + } + return -1 +} + +// elementInScope is like popUntil, except that it doesn't modify the stack of +// open elements. +func (p *parser) elementInScope(s scope, matchTags ...a.Atom) bool { + return p.indexOfElementInScope(s, matchTags...) != -1 +} + +// clearStackToContext pops elements off the stack of open elements until a +// scope-defined element is found. +func (p *parser) clearStackToContext(s scope) { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + switch s { + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table || tagAtom == a.Template { + p.oe = p.oe[:i+1] + return + } + case tableRowScope: + if tagAtom == a.Html || tagAtom == a.Tr || tagAtom == a.Template { + p.oe = p.oe[:i+1] + return + } + case tableBodyScope: + if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead || tagAtom == a.Template { + p.oe = p.oe[:i+1] + return + } + default: + panic("unreachable") + } + } +} + +// parseGenericRawTextElements implements the generic raw text element parsing +// algorithm defined in 12.2.6.2. +// https://html.spec.whatwg.org/multipage/parsing.html#parsing-elements-that-contain-only-text +// TODO: Since both RAWTEXT and RCDATA states are treated as tokenizer's part +// officially, need to make tokenizer consider both states. +func (p *parser) parseGenericRawTextElement() { + p.addElement() + p.originalIM = p.im + p.im = textIM +} + +// generateImpliedEndTags pops nodes off the stack of open elements as long as +// the top node has a tag name of dd, dt, li, optgroup, option, p, rb, rp, rt or rtc. +// If exceptions are specified, nodes with that name will not be popped off. +func (p *parser) generateImpliedEndTags(exceptions ...string) { + var i int +loop: + for i = len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if n.Type != ElementNode { + break + } + switch n.DataAtom { + case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc: + for _, except := range exceptions { + if n.Data == except { + break loop + } + } + continue + } + break + } + + p.oe = p.oe[:i+1] +} + +// addChild adds a child node n to the top element, and pushes n onto the stack +// of open elements if it is an element node. +func (p *parser) addChild(n *Node) { + if p.shouldFosterParent() { + p.fosterParent(n) + } else { + p.top().AppendChild(n) + } + + if n.Type == ElementNode { + p.oe = append(p.oe, n) + } +} + +// shouldFosterParent returns whether the next node to be added should be +// foster parented. +func (p *parser) shouldFosterParent() bool { + if p.fosterParenting { + switch p.top().DataAtom { + case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: + return true + } + } + return false +} + +// fosterParent adds a child node according to the foster parenting rules. +// Section 12.2.6.1, "foster parenting". +func (p *parser) fosterParent(n *Node) { + var table, parent, prev, template *Node + var i int + for i = len(p.oe) - 1; i >= 0; i-- { + if p.oe[i].DataAtom == a.Table { + table = p.oe[i] + break + } + } + + var j int + for j = len(p.oe) - 1; j >= 0; j-- { + if p.oe[j].DataAtom == a.Template { + template = p.oe[j] + break + } + } + + if template != nil && (table == nil || j > i) { + template.AppendChild(n) + return + } + + if table == nil { + // The foster parent is the html element. + parent = p.oe[0] + } else { + parent = table.Parent + } + if parent == nil { + parent = p.oe[i-1] + } + + if table != nil { + prev = table.PrevSibling + } else { + prev = parent.LastChild + } + if prev != nil && prev.Type == TextNode && n.Type == TextNode { + prev.Data += n.Data + return + } + + parent.InsertBefore(n, table) +} + +// addText adds text to the preceding node if it is a text node, or else it +// calls addChild with a new text node. +func (p *parser) addText(text string) { + if text == "" { + return + } + + if p.shouldFosterParent() { + p.fosterParent(&Node{ + Type: TextNode, + Data: text, + }) + return + } + + t := p.top() + if n := t.LastChild; n != nil && n.Type == TextNode { + n.Data += text + return + } + p.addChild(&Node{ + Type: TextNode, + Data: text, + }) +} + +// addElement adds a child element based on the current token. +func (p *parser) addElement() { + p.addChild(&Node{ + Type: ElementNode, + DataAtom: p.tok.DataAtom, + Data: p.tok.Data, + Attr: p.tok.Attr, + }) +} + +// Section 12.2.4.3. +func (p *parser) addFormattingElement() { + tagAtom, attr := p.tok.DataAtom, p.tok.Attr + p.addElement() + + // Implement the Noah's Ark clause, but with three per family instead of two. + identicalElements := 0 +findIdenticalElements: + for i := len(p.afe) - 1; i >= 0; i-- { + n := p.afe[i] + if n.Type == scopeMarkerNode { + break + } + if n.Type != ElementNode { + continue + } + if n.Namespace != "" { + continue + } + if n.DataAtom != tagAtom { + continue + } + if len(n.Attr) != len(attr) { + continue + } + compareAttributes: + for _, t0 := range n.Attr { + for _, t1 := range attr { + if t0.Key == t1.Key && t0.Namespace == t1.Namespace && t0.Val == t1.Val { + // Found a match for this attribute, continue with the next attribute. + continue compareAttributes + } + } + // If we get here, there is no attribute that matches a. + // Therefore the element is not identical to the new one. + continue findIdenticalElements + } + + identicalElements++ + if identicalElements >= 3 { + p.afe.remove(n) + } + } + + p.afe = append(p.afe, p.top()) +} + +// Section 12.2.4.3. +func (p *parser) clearActiveFormattingElements() { + for { + if n := p.afe.pop(); len(p.afe) == 0 || n.Type == scopeMarkerNode { + return + } + } +} + +// Section 12.2.4.3. +func (p *parser) reconstructActiveFormattingElements() { + n := p.afe.top() + if n == nil { + return + } + if n.Type == scopeMarkerNode || p.oe.index(n) != -1 { + return + } + i := len(p.afe) - 1 + for n.Type != scopeMarkerNode && p.oe.index(n) == -1 { + if i == 0 { + i = -1 + break + } + i-- + n = p.afe[i] + } + for { + i++ + clone := p.afe[i].clone() + p.addChild(clone) + p.afe[i] = clone + if i == len(p.afe)-1 { + break + } + } +} + +// Section 12.2.5. +func (p *parser) acknowledgeSelfClosingTag() { + p.hasSelfClosingToken = false +} + +// An insertion mode (section 12.2.4.1) is the state transition function from +// a particular state in the HTML5 parser's state machine. It updates the +// parser's fields depending on parser.tok (where ErrorToken means EOF). +// It returns whether the token was consumed. +type insertionMode func(*parser) bool + +// setOriginalIM sets the insertion mode to return to after completing a text or +// inTableText insertion mode. +// Section 12.2.4.1, "using the rules for". +func (p *parser) setOriginalIM() { + if p.originalIM != nil { + panic("html: bad parser state: originalIM was set twice") + } + p.originalIM = p.im +} + +// Section 12.2.4.1, "reset the insertion mode". +func (p *parser) resetInsertionMode() { + for i := len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + last := i == 0 + if last && p.context != nil { + n = p.context + } + + switch n.DataAtom { + case a.Select: + if !last { + for ancestor, first := n, p.oe[0]; ancestor != first; { + ancestor = p.oe[p.oe.index(ancestor)-1] + switch ancestor.DataAtom { + case a.Template: + p.im = inSelectIM + return + case a.Table: + p.im = inSelectInTableIM + return + } + } + } + p.im = inSelectIM + case a.Td, a.Th: + // TODO: remove this divergence from the HTML5 spec. + // + // See https://bugs.chromium.org/p/chromium/issues/detail?id=829668 + p.im = inCellIM + case a.Tr: + p.im = inRowIM + case a.Tbody, a.Thead, a.Tfoot: + p.im = inTableBodyIM + case a.Caption: + p.im = inCaptionIM + case a.Colgroup: + p.im = inColumnGroupIM + case a.Table: + p.im = inTableIM + case a.Template: + // TODO: remove this divergence from the HTML5 spec. + if n.Namespace != "" { + continue + } + p.im = p.templateStack.top() + case a.Head: + // TODO: remove this divergence from the HTML5 spec. + // + // See https://bugs.chromium.org/p/chromium/issues/detail?id=829668 + p.im = inHeadIM + case a.Body: + p.im = inBodyIM + case a.Frameset: + p.im = inFramesetIM + case a.Html: + if p.head == nil { + p.im = beforeHeadIM + } else { + p.im = afterHeadIM + } + default: + if last { + p.im = inBodyIM + return + } + continue + } + return + } +} + +const whitespace = " \t\r\n\f" + +// Section 12.2.6.4.1. +func initialIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + n, quirks := parseDoctype(p.tok.Data) + p.doc.AppendChild(n) + p.quirks = quirks + p.im = beforeHTMLIM + return true + } + p.quirks = true + p.im = beforeHTMLIM + return false +} + +// Section 12.2.6.4.2. +func beforeHTMLIM(p *parser) bool { + switch p.tok.Type { + case DoctypeToken: + // Ignore the token. + return true + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + if p.tok.DataAtom == a.Html { + p.addElement() + p.im = beforeHeadIM + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + } + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false +} + +// Section 12.2.6.4.3. +func beforeHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Head: + p.addElement() + p.head = p.top() + p.im = inHeadIM + return true + case a.Html: + return inBodyIM(p) + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.6.4.4. +func inHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta: + p.addElement() + p.oe.pop() + p.acknowledgeSelfClosingTag() + return true + case a.Noscript: + if p.scripting { + p.parseGenericRawTextElement() + return true + } + p.addElement() + p.im = inHeadNoscriptIM + // Don't let the tokenizer go into raw text mode when scripting is disabled. + p.tokenizer.NextIsNotRawText() + return true + case a.Script, a.Title: + p.addElement() + p.setOriginalIM() + p.im = textIM + return true + case a.Noframes, a.Style: + p.parseGenericRawTextElement() + return true + case a.Head: + // Ignore the token. + return true + case a.Template: + // TODO: remove this divergence from the HTML5 spec. + // + // We don't handle all of the corner cases when mixing foreign + // content (i.e. or ) with tag. + case a.Template: + return inHeadIM(p) + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Body, a.Body.String()) + p.framesetOK = true + return false +} + +// copyAttributes copies attributes of src not found on dst to dst. +func copyAttributes(dst *Node, src Token) { + if len(src.Attr) == 0 { + return + } + attr := map[string]string{} + for _, t := range dst.Attr { + attr[t.Key] = t.Val + } + for _, t := range src.Attr { + if _, ok := attr[t.Key]; !ok { + dst.Attr = append(dst.Attr, t) + attr[t.Key] = t.Val + } + } +} + +// Section 12.2.6.4.7. +func inBodyIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + d := p.tok.Data + switch n := p.oe.top(); n.DataAtom { + case a.Pre, a.Listing: + if n.FirstChild == nil { + // Ignore a newline at the start of a
 block.
+				if d != "" && d[0] == '\r' {
+					d = d[1:]
+				}
+				if d != "" && d[0] == '\n' {
+					d = d[1:]
+				}
+			}
+		}
+		d = strings.Replace(d, "\x00", "", -1)
+		if d == "" {
+			return true
+		}
+		p.reconstructActiveFormattingElements()
+		p.addText(d)
+		if p.framesetOK && strings.TrimLeft(d, whitespace) != "" {
+			// There were non-whitespace characters inserted.
+			p.framesetOK = false
+		}
+	case StartTagToken:
+		switch p.tok.DataAtom {
+		case a.Html:
+			if p.oe.contains(a.Template) {
+				return true
+			}
+			copyAttributes(p.oe[0], p.tok)
+		case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
+			return inHeadIM(p)
+		case a.Body:
+			if p.oe.contains(a.Template) {
+				return true
+			}
+			if len(p.oe) >= 2 {
+				body := p.oe[1]
+				if body.Type == ElementNode && body.DataAtom == a.Body {
+					p.framesetOK = false
+					copyAttributes(body, p.tok)
+				}
+			}
+		case a.Frameset:
+			if !p.framesetOK || len(p.oe) < 2 || p.oe[1].DataAtom != a.Body {
+				// Ignore the token.
+				return true
+			}
+			body := p.oe[1]
+			if body.Parent != nil {
+				body.Parent.RemoveChild(body)
+			}
+			p.oe = p.oe[:1]
+			p.addElement()
+			p.im = inFramesetIM
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dialog, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Main, a.Menu, a.Nav, a.Ol, a.P, a.Section, a.Summary, a.Ul:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(buttonScope, a.P)
+			switch n := p.top(); n.DataAtom {
+			case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+				p.oe.pop()
+			}
+			p.addElement()
+		case a.Pre, a.Listing:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			// The newline, if any, will be dealt with by the TextToken case.
+			p.framesetOK = false
+		case a.Form:
+			if p.form != nil && !p.oe.contains(a.Template) {
+				// Ignore the token
+				return true
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			if !p.oe.contains(a.Template) {
+				p.form = p.top()
+			}
+		case a.Li:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Li:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Dd, a.Dt:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Dd, a.Dt:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Plaintext:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Button:
+			p.popUntil(defaultScope, a.Button)
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+		case a.A:
+			for i := len(p.afe) - 1; i >= 0 && p.afe[i].Type != scopeMarkerNode; i-- {
+				if n := p.afe[i]; n.Type == ElementNode && n.DataAtom == a.A {
+					p.inBodyEndTagFormatting(a.A, "a")
+					p.oe.remove(n)
+					p.afe.remove(n)
+					break
+				}
+			}
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.Nobr:
+			p.reconstructActiveFormattingElements()
+			if p.elementInScope(defaultScope, a.Nobr) {
+				p.inBodyEndTagFormatting(a.Nobr, "nobr")
+				p.reconstructActiveFormattingElements()
+			}
+			p.addFormattingElement()
+		case a.Applet, a.Marquee, a.Object:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.afe = append(p.afe, &scopeMarker)
+			p.framesetOK = false
+		case a.Table:
+			if !p.quirks {
+				p.popUntil(buttonScope, a.P)
+			}
+			p.addElement()
+			p.framesetOK = false
+			p.im = inTableIM
+			return true
+		case a.Area, a.Br, a.Embed, a.Img, a.Input, a.Keygen, a.Wbr:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			if p.tok.DataAtom == a.Input {
+				for _, t := range p.tok.Attr {
+					if t.Key == "type" {
+						if strings.ToLower(t.Val) == "hidden" {
+							// Skip setting framesetOK = false
+							return true
+						}
+					}
+				}
+			}
+			p.framesetOK = false
+		case a.Param, a.Source, a.Track:
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+		case a.Hr:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			p.framesetOK = false
+		case a.Image:
+			p.tok.DataAtom = a.Img
+			p.tok.Data = a.Img.String()
+			return false
+		case a.Textarea:
+			p.addElement()
+			p.setOriginalIM()
+			p.framesetOK = false
+			p.im = textIM
+		case a.Xmp:
+			p.popUntil(buttonScope, a.P)
+			p.reconstructActiveFormattingElements()
+			p.framesetOK = false
+			p.parseGenericRawTextElement()
+		case a.Iframe:
+			p.framesetOK = false
+			p.parseGenericRawTextElement()
+		case a.Noembed:
+			p.parseGenericRawTextElement()
+		case a.Noscript:
+			if p.scripting {
+				p.parseGenericRawTextElement()
+				return true
+			}
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			// Don't let the tokenizer go into raw text mode when scripting is disabled.
+			p.tokenizer.NextIsNotRawText()
+		case a.Select:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+			p.im = inSelectIM
+			return true
+		case a.Optgroup, a.Option:
+			if p.top().DataAtom == a.Option {
+				p.oe.pop()
+			}
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		case a.Rb, a.Rtc:
+			if p.elementInScope(defaultScope, a.Ruby) {
+				p.generateImpliedEndTags()
+			}
+			p.addElement()
+		case a.Rp, a.Rt:
+			if p.elementInScope(defaultScope, a.Ruby) {
+				p.generateImpliedEndTags("rtc")
+			}
+			p.addElement()
+		case a.Math, a.Svg:
+			p.reconstructActiveFormattingElements()
+			if p.tok.DataAtom == a.Math {
+				adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments)
+			} else {
+				adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments)
+			}
+			adjustForeignAttributes(p.tok.Attr)
+			p.addElement()
+			p.top().Namespace = p.tok.Data
+			if p.hasSelfClosingToken {
+				p.oe.pop()
+				p.acknowledgeSelfClosingTag()
+			}
+			return true
+		case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
+			// Ignore the token.
+		default:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		}
+	case EndTagToken:
+		switch p.tok.DataAtom {
+		case a.Body:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.im = afterBodyIM
+			}
+		case a.Html:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.parseImpliedToken(EndTagToken, a.Body, a.Body.String())
+				return false
+			}
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dialog, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Main, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.Form:
+			if p.oe.contains(a.Template) {
+				i := p.indexOfElementInScope(defaultScope, a.Form)
+				if i == -1 {
+					// Ignore the token.
+					return true
+				}
+				p.generateImpliedEndTags()
+				if p.oe[i].DataAtom != a.Form {
+					// Ignore the token.
+					return true
+				}
+				p.popUntil(defaultScope, a.Form)
+			} else {
+				node := p.form
+				p.form = nil
+				i := p.indexOfElementInScope(defaultScope, a.Form)
+				if node == nil || i == -1 || p.oe[i] != node {
+					// Ignore the token.
+					return true
+				}
+				p.generateImpliedEndTags()
+				p.oe.remove(node)
+			}
+		case a.P:
+			if !p.elementInScope(buttonScope, a.P) {
+				p.parseImpliedToken(StartTagToken, a.P, a.P.String())
+			}
+			p.popUntil(buttonScope, a.P)
+		case a.Li:
+			p.popUntil(listItemScope, a.Li)
+		case a.Dd, a.Dt:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(defaultScope, a.H1, a.H2, a.H3, a.H4, a.H5, a.H6)
+		case a.A, a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.Nobr, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.inBodyEndTagFormatting(p.tok.DataAtom, p.tok.Data)
+		case a.Applet, a.Marquee, a.Object:
+			if p.popUntil(defaultScope, p.tok.DataAtom) {
+				p.clearActiveFormattingElements()
+			}
+		case a.Br:
+			p.tok.Type = StartTagToken
+			return false
+		case a.Template:
+			return inHeadIM(p)
+		default:
+			p.inBodyEndTagOther(p.tok.DataAtom, p.tok.Data)
+		}
+	case CommentToken:
+		p.addChild(&Node{
+			Type: CommentNode,
+			Data: p.tok.Data,
+		})
+	case ErrorToken:
+		// TODO: remove this divergence from the HTML5 spec.
+		if len(p.templateStack) > 0 {
+			p.im = inTemplateIM
+			return false
+		}
+		for _, e := range p.oe {
+			switch e.DataAtom {
+			case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc, a.Tbody, a.Td, a.Tfoot, a.Th,
+				a.Thead, a.Tr, a.Body, a.Html:
+			default:
+				return true
+			}
+		}
+	}
+
+	return true
+}
+
+func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom, tagName string) {
+	// This is the "adoption agency" algorithm, described at
+	// https://html.spec.whatwg.org/multipage/syntax.html#adoptionAgency
+
+	// TODO: this is a fairly literal line-by-line translation of that algorithm.
+	// Once the code successfully parses the comprehensive test suite, we should
+	// refactor this code to be more idiomatic.
+
+	// Steps 1-2
+	if current := p.oe.top(); current.Data == tagName && p.afe.index(current) == -1 {
+		p.oe.pop()
+		return
+	}
+
+	// Steps 3-5. The outer loop.
+	for i := 0; i < 8; i++ {
+		// Step 6. Find the formatting element.
+		var formattingElement *Node
+		for j := len(p.afe) - 1; j >= 0; j-- {
+			if p.afe[j].Type == scopeMarkerNode {
+				break
+			}
+			if p.afe[j].DataAtom == tagAtom {
+				formattingElement = p.afe[j]
+				break
+			}
+		}
+		if formattingElement == nil {
+			p.inBodyEndTagOther(tagAtom, tagName)
+			return
+		}
+
+		// Step 7. Ignore the tag if formatting element is not in the stack of open elements.
+		feIndex := p.oe.index(formattingElement)
+		if feIndex == -1 {
+			p.afe.remove(formattingElement)
+			return
+		}
+		// Step 8. Ignore the tag if formatting element is not in the scope.
+		if !p.elementInScope(defaultScope, tagAtom) {
+			// Ignore the tag.
+			return
+		}
+
+		// Step 9. This step is omitted because it's just a parse error but no need to return.
+
+		// Steps 10-11. Find the furthest block.
+		var furthestBlock *Node
+		for _, e := range p.oe[feIndex:] {
+			if isSpecialElement(e) {
+				furthestBlock = e
+				break
+			}
+		}
+		if furthestBlock == nil {
+			e := p.oe.pop()
+			for e != formattingElement {
+				e = p.oe.pop()
+			}
+			p.afe.remove(e)
+			return
+		}
+
+		// Steps 12-13. Find the common ancestor and bookmark node.
+		commonAncestor := p.oe[feIndex-1]
+		bookmark := p.afe.index(formattingElement)
+
+		// Step 14. The inner loop. Find the lastNode to reparent.
+		lastNode := furthestBlock
+		node := furthestBlock
+		x := p.oe.index(node)
+		// Step 14.1.
+		j := 0
+		for {
+			// Step 14.2.
+			j++
+			// Step. 14.3.
+			x--
+			node = p.oe[x]
+			// Step 14.4. Go to the next step if node is formatting element.
+			if node == formattingElement {
+				break
+			}
+			// Step 14.5. Remove node from the list of active formatting elements if
+			// inner loop counter is greater than three and node is in the list of
+			// active formatting elements.
+			if ni := p.afe.index(node); j > 3 && ni > -1 {
+				p.afe.remove(node)
+				// If any element of the list of active formatting elements is removed,
+				// we need to take care whether bookmark should be decremented or not.
+				// This is because the value of bookmark may exceed the size of the
+				// list by removing elements from the list.
+				if ni <= bookmark {
+					bookmark--
+				}
+				continue
+			}
+			// Step 14.6. Continue the next inner loop if node is not in the list of
+			// active formatting elements.
+			if p.afe.index(node) == -1 {
+				p.oe.remove(node)
+				continue
+			}
+			// Step 14.7.
+			clone := node.clone()
+			p.afe[p.afe.index(node)] = clone
+			p.oe[p.oe.index(node)] = clone
+			node = clone
+			// Step 14.8.
+			if lastNode == furthestBlock {
+				bookmark = p.afe.index(node) + 1
+			}
+			// Step 14.9.
+			if lastNode.Parent != nil {
+				lastNode.Parent.RemoveChild(lastNode)
+			}
+			node.AppendChild(lastNode)
+			// Step 14.10.
+			lastNode = node
+		}
+
+		// Step 15. Reparent lastNode to the common ancestor,
+		// or for misnested table nodes, to the foster parent.
+		if lastNode.Parent != nil {
+			lastNode.Parent.RemoveChild(lastNode)
+		}
+		switch commonAncestor.DataAtom {
+		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
+			p.fosterParent(lastNode)
+		default:
+			commonAncestor.AppendChild(lastNode)
+		}
+
+		// Steps 16-18. Reparent nodes from the furthest block's children
+		// to a clone of the formatting element.
+		clone := formattingElement.clone()
+		reparentChildren(clone, furthestBlock)
+		furthestBlock.AppendChild(clone)
+
+		// Step 19. Fix up the list of active formatting elements.
+		if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
+			// Move the bookmark with the rest of the list.
+			bookmark--
+		}
+		p.afe.remove(formattingElement)
+		p.afe.insert(bookmark, clone)
+
+		// Step 20. Fix up the stack of open elements.
+		p.oe.remove(formattingElement)
+		p.oe.insert(p.oe.index(furthestBlock)+1, clone)
+	}
+}
+
+// inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM.
+// "Any other end tag" handling from 12.2.6.5 The rules for parsing tokens in foreign content
+// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign
+func (p *parser) inBodyEndTagOther(tagAtom a.Atom, tagName string) {
+	for i := len(p.oe) - 1; i >= 0; i-- {
+		// Two element nodes have the same tag if they have the same Data (a
+		// string-typed field). As an optimization, for common HTML tags, each
+		// Data string is assigned a unique, non-zero DataAtom (a uint32-typed
+		// field), since integer comparison is faster than string comparison.
+		// Uncommon (custom) tags get a zero DataAtom.
+		//
+		// The if condition here is equivalent to (p.oe[i].Data == tagName).
+		if (p.oe[i].DataAtom == tagAtom) &&
+			((tagAtom != 0) || (p.oe[i].Data == tagName)) {
+			p.oe = p.oe[:i]
+			break
+		}
+		if isSpecialElement(p.oe[i]) {
+			break
+		}
+	}
+}
+
+// Section 12.2.6.4.8.
+func textIM(p *parser) bool {
+	switch p.tok.Type {
+	case ErrorToken:
+		p.oe.pop()
+	case TextToken:
+		d := p.tok.Data
+		if n := p.oe.top(); n.DataAtom == a.Textarea && n.FirstChild == nil {
+			// Ignore a newline at the start of a