| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- // Copyright 2016 - 2023 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.16 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 (dv *DataValidation) SetError(style DataValidationErrorStyle, title, msg string) {
- dv.Error = &msg
- dv.ErrorTitle = &title
- strStyle := styleStop
- switch style {
- case DataValidationErrorStyleStop:
- strStyle = styleStop
- case DataValidationErrorStyleWarning:
- strStyle = styleWarning
- case DataValidationErrorStyleInformation:
- strStyle = styleInformation
- }
- dv.ShowErrorMessage = true
- dv.ErrorStyle = &strStyle
- }
- // SetInput set prompt notice.
- func (dv *DataValidation) SetInput(title, msg string) {
- dv.ShowInputMessage = true
- dv.PromptTitle = &title
- dv.Prompt = &msg
- }
- // SetDropList data validation list.
- func (dv *DataValidation) SetDropList(keys []string) error {
- formula := strings.Join(keys, ",")
- if MaxFieldLength < len(utf16.Encode([]rune(formula))) {
- return ErrDataValidationFormulaLength
- }
- dv.Formula1 = fmt.Sprintf(`<formula1>"%s"</formula1>`, formulaEscaper.Replace(formula))
- dv.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 (dv *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o DataValidationOperator) error {
- var formula1, formula2 string
- switch v := f1.(type) {
- case int:
- formula1 = fmt.Sprintf("<formula1>%d</formula1>", v)
- case float64:
- if math.Abs(v) > math.MaxFloat32 {
- return ErrDataValidationRange
- }
- formula1 = fmt.Sprintf("<formula1>%.17g</formula1>", v)
- case string:
- formula1 = fmt.Sprintf("<formula1>%s</formula1>", v)
- default:
- return ErrParameterInvalid
- }
- switch v := f2.(type) {
- case int:
- formula2 = fmt.Sprintf("<formula2>%d</formula2>", v)
- case float64:
- if math.Abs(v) > math.MaxFloat32 {
- return ErrDataValidationRange
- }
- formula2 = fmt.Sprintf("<formula2>%.17g</formula2>", v)
- case string:
- formula2 = fmt.Sprintf("<formula2>%s</formula2>", v)
- default:
- return ErrParameterInvalid
- }
- dv.Formula1, dv.Formula2 = formula1, formula2
- dv.Type = convDataValidationType(t)
- dv.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:
- //
- // dv := excelize.NewDataValidation(true)
- // dv.Sqref = "A7:B8"
- // dv.SetSqrefDropList("$E$1:$E$3")
- // err := f.AddDataValidation("Sheet1", dv)
- func (dv *DataValidation) SetSqrefDropList(sqref string) {
- dv.Formula1 = fmt.Sprintf("<formula1>%s</formula1>", sqref)
- dv.Type = convDataValidationType(typeList)
- }
- // SetSqref provides function to set data validation range in drop list.
- func (dv *DataValidation) SetSqref(sqref string) {
- if dv.Sqref == "" {
- dv.Sqref = sqref
- } else {
- dv.Sqref = fmt.Sprintf("%s %s", dv.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":
- //
- // dv := excelize.NewDataValidation(true)
- // dv.Sqref = "A1:B2"
- // dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorBetween)
- // dv.SetError(excelize.DataValidationErrorStyleStop, "error title", "error body")
- // err := f.AddDataValidation("Sheet1", dv)
- //
- // Example 2, set data validation on Sheet1!A3:B4 with validation criteria
- // settings, and show input message when cell is selected:
- //
- // dv = excelize.NewDataValidation(true)
- // dv.Sqref = "A3:B4"
- // dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorGreaterThan)
- // dv.SetInput("input title", "input body")
- // err = f.AddDataValidation("Sheet1", dv)
- //
- // Example 3, set data validation on Sheet1!A5:B6 with validation criteria
- // settings, create in-cell dropdown by allowing list source:
- //
- // dv = excelize.NewDataValidation(true)
- // dv.Sqref = "A5:B6"
- // dv.SetDropList([]string{"1", "2", "3"})
- // err = f.AddDataValidation("Sheet1", dv)
- 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
- }
- // GetDataValidations returns data validations list by given worksheet name.
- func (f *File) GetDataValidations(sheet string) ([]*DataValidation, error) {
- ws, err := f.workSheetReader(sheet)
- if err != nil {
- return nil, err
- }
- if ws.DataValidations == nil || len(ws.DataValidations.DataValidation) == 0 {
- return nil, err
- }
- return ws.DataValidations.DataValidation, err
- }
- // DeleteDataValidation delete data validation by given worksheet name and
- // reference sequence. All data validations in the worksheet will be deleted
- // if not specify reference sequence parameter.
- func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
- ws, err := f.workSheetReader(sheet)
- if err != nil {
- return err
- }
- if ws.DataValidations == nil {
- return nil
- }
- if sqref == nil {
- ws.DataValidations = nil
- return nil
- }
- delCells, err := f.flatSqref(sqref[0])
- 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.coordinatesToRangeRef(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.coordinatesToRangeRef(append(cells[l], cells[r]...))
- if l == r {
- curr, _ = CoordinatesToCellName(cells[l][0], cells[l][1])
- }
- return append(res, curr)
- }
|