forked from p30928647/excelize
parent
be8fc0a4c5
commit
ecbc6e2fde
111
calc.go
111
calc.go
|
@ -664,6 +664,8 @@ type formulaFuncs struct {
|
|||
// TEXTJOIN
|
||||
// TIME
|
||||
// TIMEVALUE
|
||||
// T.INV
|
||||
// T.INV.2T
|
||||
// TODAY
|
||||
// TRANSPOSE
|
||||
// TRIM
|
||||
|
@ -1265,27 +1267,6 @@ func isOperand(token efp.Token) bool {
|
|||
return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -6647,14 +6628,16 @@ func hasChangeOfSign(u, w float64) bool {
|
|||
// calcInverseIterator directly maps the required parameters for inverse
|
||||
// distribution functions.
|
||||
type calcInverseIterator struct {
|
||||
name string
|
||||
fp, fDF float64
|
||||
name string
|
||||
fp, fDF, nT float64
|
||||
}
|
||||
|
||||
// chiSqDist implements inverse distribution with left tail for the Chi-Square
|
||||
// distribution.
|
||||
func (iterator *calcInverseIterator) chiSqDist(x float64) float64 {
|
||||
return iterator.fp - getChiSqDistCDF(x, iterator.fDF)
|
||||
// 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
|
||||
|
@ -6682,7 +6665,7 @@ func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx,
|
|||
bHasToInterpolate = true
|
||||
}
|
||||
fPx, fQx, fRx, fPy, fQy = fQx, fRx, fSx, fQy, fRy
|
||||
fRy = iterator.chiSqDist(fSx)
|
||||
fRy = iterator.callBack(fSx)
|
||||
if hasChangeOfSign(fAy, fRy) {
|
||||
fBx, fBy = fRx, fRy
|
||||
} else {
|
||||
|
@ -6697,7 +6680,7 @@ func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx,
|
|||
// calcIterateInverse function calculates the iteration for inverse
|
||||
// distributions.
|
||||
func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64 {
|
||||
fAy, fBy := iterator.chiSqDist(fAx), iterator.chiSqDist(fBx)
|
||||
fAy, fBy := iterator.callBack(fAx), iterator.callBack(fBx)
|
||||
var fTemp float64
|
||||
var nCount int
|
||||
for nCount = 0; nCount < 1000 && !hasChangeOfSign(fAy, fBy); nCount++ {
|
||||
|
@ -6709,13 +6692,13 @@ func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64
|
|||
}
|
||||
fBx = fTemp
|
||||
fBy = fAy
|
||||
fAy = iterator.chiSqDist(fAx)
|
||||
fAy = iterator.callBack(fAx)
|
||||
} else {
|
||||
fTemp = fBx
|
||||
fBx += 2 * (fBx - fAx)
|
||||
fAx = fTemp
|
||||
fAy = fBy
|
||||
fBy = iterator.chiSqDist(fBx)
|
||||
fBy = iterator.callBack(fBx)
|
||||
}
|
||||
}
|
||||
if fAy == 0 || fBy == 0 {
|
||||
|
@ -9152,6 +9135,72 @@ func (fn *formulaFuncs) TDIST(argsList *list.List) formulaArg {
|
|||
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))
|
||||
}
|
||||
|
||||
// TRIMMEAN function calculates the trimmed mean (or truncated mean) of a
|
||||
// supplied set of values. The syntax of the function is:
|
||||
//
|
||||
|
|
19
calc_test.go
19
calc_test.go
|
@ -1167,6 +1167,12 @@ func TestCalcCellValue(t *testing.T) {
|
|||
// TDIST
|
||||
"=TDIST(1,10,1)": "0.17044656615103",
|
||||
"=TDIST(1,10,2)": "0.34089313230206",
|
||||
// T.INV
|
||||
"=T.INV(0.25,10)": "-0.699812061312432",
|
||||
"=T.INV(0.75,10)": "0.699812061312432",
|
||||
// T.INV.2T
|
||||
"=T.INV.2T(1,10)": "0",
|
||||
"=T.INV.2T(0.5,10)": "0.699812061312432",
|
||||
// TRIMMEAN
|
||||
"=TRIMMEAN(A1:B4,10%)": "2.5",
|
||||
"=TRIMMEAN(A1:B4,70%)": "2.5",
|
||||
|
@ -3048,6 +3054,19 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=TDIST(-1,10,1)": "#NUM!",
|
||||
"=TDIST(1,0,1)": "#NUM!",
|
||||
"=TDIST(1,10,0)": "#NUM!",
|
||||
// T.INV
|
||||
"=T.INV()": "T.INV requires 2 arguments",
|
||||
"=T.INV(\"\",10)": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=T.INV(0.25,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=T.INV(0,10)": "#NUM!",
|
||||
"=T.INV(1,10)": "#NUM!",
|
||||
"=T.INV(0.25,0.5)": "#NUM!",
|
||||
// T.INV.2T
|
||||
"=T.INV.2T()": "T.INV.2T requires 2 arguments",
|
||||
"=T.INV.2T(\"\",10)": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=T.INV.2T(0.25,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=T.INV.2T(0,10)": "#NUM!",
|
||||
"=T.INV.2T(0.25,0.5)": "#NUM!",
|
||||
// TRIMMEAN
|
||||
"=TRIMMEAN()": "TRIMMEAN requires 2 arguments",
|
||||
"=TRIMMEAN(A1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
|
|
4
file.go
4
file.go
|
@ -63,7 +63,7 @@ func (f *File) Save() error {
|
|||
return f.SaveAs(f.Path)
|
||||
}
|
||||
|
||||
// SaveAs provides a function to create or update to an spreadsheet at the
|
||||
// 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 {
|
||||
|
@ -81,7 +81,7 @@ func (f *File) SaveAs(name string, opt ...Options) error {
|
|||
return ErrWorkbookExt
|
||||
}
|
||||
f.setContentTypePartProjectExtensions(contentType)
|
||||
file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o600)
|
||||
file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
47
lib.go
47
lib.go
|
@ -132,7 +132,7 @@ func (f *File) saveFileList(name string, content []byte) {
|
|||
f.Pkg.Store(name, append([]byte(xml.Header), content...))
|
||||
}
|
||||
|
||||
// Read file content as string in a archive file.
|
||||
// Read file content as string in an archive file.
|
||||
func readFile(file *zip.File) ([]byte, error) {
|
||||
rc, err := file.Open()
|
||||
if err != nil {
|
||||
|
@ -157,8 +157,8 @@ func SplitCellName(cell string) (string, int, error) {
|
|||
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 {
|
||||
col, rowStr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:]
|
||||
if row, err := strconv.Atoi(rowStr); err == nil && row > 0 {
|
||||
return col, row, nil
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ func JoinCellName(col string, row int) (string, error) {
|
|||
}
|
||||
|
||||
// ColumnNameToNumber provides a function to convert Excel sheet column name
|
||||
// to int. Column name case insensitive. The function returns an error if
|
||||
// to int. Column name case-insensitive. The function returns an error if
|
||||
// column name incorrect.
|
||||
//
|
||||
// Example:
|
||||
|
@ -248,14 +248,14 @@ func ColumnNumberToName(num int) (string, error) {
|
|||
// excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil
|
||||
//
|
||||
func CellNameToCoordinates(cell string) (int, int, error) {
|
||||
colname, row, err := SplitCellName(cell)
|
||||
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)
|
||||
col, err := ColumnNameToNumber(colName)
|
||||
return col, row, err
|
||||
}
|
||||
|
||||
|
@ -277,8 +277,8 @@ func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
|
|||
sign = "$"
|
||||
}
|
||||
}
|
||||
colname, err := ColumnNumberToName(col)
|
||||
return sign + colname + sign + strconv.Itoa(row), err
|
||||
colName, err := ColumnNumberToName(col)
|
||||
return sign + colName + sign + strconv.Itoa(row), err
|
||||
}
|
||||
|
||||
// areaRefToCoordinates provides a function to convert area reference to a
|
||||
|
@ -336,6 +336,27 @@ func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) {
|
|||
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
|
||||
|
@ -365,7 +386,7 @@ func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// inCoordinates provides a method to check if an coordinate is present in
|
||||
// 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 {
|
||||
|
@ -391,7 +412,7 @@ func inStrSlice(a []string, x string, caseSensitive bool) int {
|
|||
return -1
|
||||
}
|
||||
|
||||
// inFloat64Slice provides a method to check if an element is present in an
|
||||
// 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 {
|
||||
|
@ -405,7 +426,7 @@ func inFloat64Slice(a []float64, x float64) int {
|
|||
// boolPtr returns a pointer to a bool with the given value.
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
// intPtr returns a pointer to a int with the given value.
|
||||
// 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.
|
||||
|
@ -626,7 +647,7 @@ func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte
|
|||
return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1)
|
||||
}
|
||||
|
||||
// addNameSpaces provides a function to add a XML attribute by the given
|
||||
// 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
|
||||
|
@ -715,7 +736,7 @@ var (
|
|||
|
||||
// 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 characters
|
||||
// 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
|
||||
|
|
10
numfmt.go
10
numfmt.go
|
@ -33,9 +33,9 @@ type languageInfo struct {
|
|||
type numberFormat struct {
|
||||
section []nfp.Section
|
||||
t time.Time
|
||||
sectionIdx int
|
||||
isNumberic, hours, seconds bool
|
||||
number float64
|
||||
sectionIdx int
|
||||
isNumeric, hours, seconds bool
|
||||
number float64
|
||||
ap, afterPoint, beforePoint, localCode, result, value, valueSectionType string
|
||||
}
|
||||
|
||||
|
@ -279,7 +279,7 @@ var (
|
|||
// prepareNumberic split the number into two before and after parts by a
|
||||
// decimal point.
|
||||
func (nf *numberFormat) prepareNumberic(value string) {
|
||||
if nf.isNumberic, _ = isNumeric(value); !nf.isNumberic {
|
||||
if nf.isNumeric, _ = isNumeric(value); !nf.isNumeric {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -297,7 +297,7 @@ func format(value, numFmt string) string {
|
|||
if section.Type != nf.valueSectionType {
|
||||
continue
|
||||
}
|
||||
if nf.isNumberic {
|
||||
if nf.isNumeric {
|
||||
switch section.Type {
|
||||
case nfp.TokenSectionPositive:
|
||||
return nf.positiveHandler()
|
||||
|
|
Loading…
Reference in New Issue