table.go 19.9 KB
Newer Older
1
// Copyright 2016 - 2024 The excelize Authors. All rights reserved. Use of
xurime's avatar
xurime 已提交
2 3 4
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
5 6 7 8 9
// 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
xurime's avatar
xurime 已提交
10
// data. This library needs Go version 1.16 or later.
xurime's avatar
xurime 已提交
11

xurime's avatar
xurime 已提交
12 13 14
package excelize

import (
xurime's avatar
xurime 已提交
15
	"bytes"
xurime's avatar
xurime 已提交
16
	"encoding/xml"
17
	"fmt"
xurime's avatar
xurime 已提交
18
	"io"
19
	"regexp"
xurime's avatar
xurime 已提交
20 21
	"strconv"
	"strings"
22 23
	"unicode"
	"unicode/utf8"
xurime's avatar
xurime 已提交
24 25
)

26 27 28 29 30 31 32
var (
	expressionFormat = regexp.MustCompile(`"(?:[^"]|"")*"|\S+`)
	conditionFormat  = regexp.MustCompile(`(or|\|\|)`)
	blankFormat      = regexp.MustCompile("blanks|nonblanks")
	matchFormat      = regexp.MustCompile("[*?]")
)

33
// parseTableOptions provides a function to parse the format settings of the
xurime's avatar
xurime 已提交
34
// table with default value.
35
func parseTableOptions(opts *Table) (*Table, error) {
36
	var err error
37
	if opts == nil {
38
		return &Table{ShowRowStripes: boolPtr(true)}, err
39 40 41 42
	}
	if opts.ShowRowStripes == nil {
		opts.ShowRowStripes = boolPtr(true)
	}
43
	if err = checkDefinedName(opts.Name); err != nil {
44 45 46
		return opts, err
	}
	return opts, err
xurime's avatar
xurime 已提交
47 48
}

xurime's avatar
xurime 已提交
49
// AddTable provides the method to add table in a worksheet by given worksheet
50
// name, range reference and format set. For example, create a table of A1:D5
xurime's avatar
xurime 已提交
51 52
// on Sheet1:
//
53
//	err := f.AddTable("Sheet1", &excelize.Table{Range: "A1:D5"})
xurime's avatar
xurime 已提交
54 55 56
//
// Create a table of F2:H6 on Sheet2 with format set:
//
xurime's avatar
xurime 已提交
57
//	disable := false
58 59
//	err := f.AddTable("Sheet2", &excelize.Table{
//	    Range:             "F2:H6",
60 61 62 63 64 65 66
//	    Name:              "table",
//	    StyleName:         "TableStyleMedium2",
//	    ShowFirstColumn:   true,
//	    ShowLastColumn:    true,
//	    ShowRowStripes:    &disable,
//	    ShowColumnStripes: true,
//	})
xurime's avatar
xurime 已提交
67
//
68 69 70
// 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
xurime's avatar
xurime 已提交
71
// tables range reference that can't have an intersection.
72
//
73 74 75
// Name: The name of the table, in the same worksheet name of the table should
// be unique, starts with a letter or underscore (_), doesn't include a
// space or character, and should be no more than 255 characters
xurime's avatar
xurime 已提交
76
//
77
// StyleName: The built-in table style names
xurime's avatar
xurime 已提交
78
//
79 80 81
//	TableStyleLight1 - TableStyleLight21
//	TableStyleMedium1 - TableStyleMedium28
//	TableStyleDark1 - TableStyleDark11
82 83
func (f *File) AddTable(sheet string, table *Table) error {
	options, err := parseTableOptions(table)
84 85 86
	if err != nil {
		return err
	}
xurime's avatar
xurime 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
	var exist bool
	f.Pkg.Range(func(k, v interface{}) bool {
		if strings.Contains(k.(string), "xl/tables/table") {
			var t xlsxTable
			if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
				Decode(&t); err != nil && err != io.EOF {
				return true
			}
			if exist = t.Name == options.Name; exist {
				return false
			}
		}
		return true
	})
	if exist {
		return ErrExistsTableName
	}
xurime's avatar
xurime 已提交
104
	// Coordinate conversion, convert C1:B3 to 2,0,1,2.
105
	coordinates, err := rangeRefToCoordinates(options.Range)
106 107 108
	if err != nil {
		return err
	}
109 110
	// Correct table reference range, such correct C1:B3 to B1:C3.
	_ = sortCoordinates(coordinates)
xurime's avatar
xurime 已提交
111 112
	tableID := f.countTables() + 1
	sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
113
	tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
xurime's avatar
xurime 已提交
114
	// Add first table for given sheet.
115 116
	sheetXMLPath, _ := f.getSheetXMLPath(sheet)
	sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
xurime's avatar
xurime 已提交
117
	rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
118 119 120 121
	if err = f.addSheetTable(sheet, rID); err != nil {
		return err
	}
	f.addSheetNameSpace(sheet, SourceRelationship)
122
	if err = f.addTable(sheet, tableXML, coordinates[0], coordinates[1], coordinates[2], coordinates[3], tableID, options); err != nil {
123 124
		return err
	}
125
	return f.addContentTypePart(tableID, "table")
xurime's avatar
xurime 已提交
126 127
}

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
// GetTables provides the method to get all tables in a worksheet by given
// worksheet name.
func (f *File) GetTables(sheet string) ([]Table, error) {
	var tables []Table
	ws, err := f.workSheetReader(sheet)
	if err != nil {
		return tables, err
	}
	if ws.TableParts == nil {
		return tables, err
	}
	for _, tbl := range ws.TableParts.TableParts {
		if tbl != nil {
			target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
			tableXML := strings.ReplaceAll(target, "..", "xl")
			content, ok := f.Pkg.Load(tableXML)
			if !ok {
				continue
			}
			var t xlsxTable
			if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
				Decode(&t); err != nil && err != io.EOF {
				return tables, err
			}
			table := Table{
153 154 155 156 157
				rID:      tbl.RID,
				tID:      t.ID,
				tableXML: tableXML,
				Range:    t.Ref,
				Name:     t.Name,
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
			}
			if t.TableStyleInfo != nil {
				table.StyleName = t.TableStyleInfo.Name
				table.ShowColumnStripes = t.TableStyleInfo.ShowColumnStripes
				table.ShowFirstColumn = t.TableStyleInfo.ShowFirstColumn
				table.ShowLastColumn = t.TableStyleInfo.ShowLastColumn
				table.ShowRowStripes = &t.TableStyleInfo.ShowRowStripes
			}
			tables = append(tables, table)
		}
	}
	return tables, err
}

// DeleteTable provides the method to delete table by given table name.
func (f *File) DeleteTable(name string) error {
	if err := checkDefinedName(name); err != nil {
		return err
	}
	for _, sheet := range f.GetSheetList() {
		tables, err := f.GetTables(sheet)
		if err != nil {
			return err
		}
		for _, table := range tables {
			if table.Name != name {
				continue
			}
			ws, _ := f.workSheetReader(sheet)
			for i, tbl := range ws.TableParts.TableParts {
				if tbl.RID == table.rID {
					ws.TableParts.TableParts = append(ws.TableParts.TableParts[:i], ws.TableParts.TableParts[i+1:]...)
190 191
					f.Pkg.Delete(table.tableXML)
					_ = f.removeContentTypesPart(ContentTypeSpreadSheetMLTable, "/"+table.tableXML)
192 193 194 195 196 197 198 199 200 201 202 203 204
					f.deleteSheetRelationships(sheet, tbl.RID)
					break
				}
			}
			if ws.TableParts.Count = len(ws.TableParts.TableParts); ws.TableParts.Count == 0 {
				ws.TableParts = nil
			}
			return err
		}
	}
	return newNoExistTableError(name)
}

xurime's avatar
xurime 已提交
205 206
// countTables provides a function to get table files count storage in the
// folder xl/tables.
xurime's avatar
xurime 已提交
207 208
func (f *File) countTables() int {
	count := 0
209
	f.Pkg.Range(func(k, v interface{}) bool {
210
		if strings.Contains(k.(string), "xl/tables/tableSingleCells") {
211
			var cells xlsxSingleXMLCells
212 213 214 215 216 217 218 219 220 221 222
			if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
				Decode(&cells); err != nil && err != io.EOF {
				count++
				return true
			}
			for _, cell := range cells.SingleXmlCell {
				if count < cell.ID {
					count = cell.ID
				}
			}
		}
223
		if strings.Contains(k.(string), "xl/tables/table") {
224 225 226 227 228 229 230 231 232
			var t xlsxTable
			if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
				Decode(&t); err != nil && err != io.EOF {
				count++
				return true
			}
			if count < t.ID {
				count = t.ID
			}
xurime's avatar
xurime 已提交
233
		}
234 235
		return true
	})
xurime's avatar
xurime 已提交
236 237 238
	return count
}

xurime's avatar
xurime 已提交
239
// addSheetTable provides a function to add tablePart element to
240
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
241 242 243 244 245
func (f *File) addSheetTable(sheet string, rID int) error {
	ws, err := f.workSheetReader(sheet)
	if err != nil {
		return err
	}
xurime's avatar
xurime 已提交
246 247 248
	table := &xlsxTablePart{
		RID: "rId" + strconv.Itoa(rID),
	}
249 250
	if ws.TableParts == nil {
		ws.TableParts = &xlsxTableParts{}
xurime's avatar
xurime 已提交
251
	}
252 253 254
	ws.TableParts.Count++
	ws.TableParts.TableParts = append(ws.TableParts.TableParts, table)
	return err
xurime's avatar
xurime 已提交
255 256
}

257
// setTableColumns provides a function to set cells value in header row for the
258
// table.
259
func (f *File) setTableColumns(sheet string, showHeaderRow bool, x1, y1, x2 int, tbl *xlsxTable) error {
260
	var (
261 262 263 264 265 266 267 268 269 270 271 272 273
		idx            int
		header         []string
		tableColumns   []*xlsxTableColumn
		getTableColumn = func(name string) *xlsxTableColumn {
			if tbl != nil && tbl.TableColumns != nil {
				for _, column := range tbl.TableColumns.TableColumn {
					if column.Name == name {
						return column
					}
				}
			}
			return nil
		}
274
	)
275
	for i := x1; i <= x2; i++ {
xurime's avatar
xurime 已提交
276
		idx++
277
		cell, err := CoordinatesToCellName(i, y1)
278
		if err != nil {
279
			return err
280
		}
281
		name, _ := f.GetCellValue(sheet, cell, Options{RawCellValue: true})
xurime's avatar
xurime 已提交
282
		if _, err := strconv.Atoi(name); err == nil {
283 284 285
			if showHeaderRow {
				_ = f.SetCellStr(sheet, cell, name)
			}
xurime's avatar
xurime 已提交
286
		}
287
		if name == "" || inStrSlice(header, name, true) != -1 {
xurime's avatar
xurime 已提交
288
			name = "Column" + strconv.Itoa(idx)
289 290 291
			if showHeaderRow {
				_ = f.SetCellStr(sheet, cell, name)
			}
xurime's avatar
xurime 已提交
292
		}
293 294
		header = append(header, name)
		if column := getTableColumn(name); column != nil {
295
			column.ID, column.DataDxfID, column.QueryTableFieldID = idx, 0, 0
296 297 298
			tableColumns = append(tableColumns, column)
			continue
		}
299
		tableColumns = append(tableColumns, &xlsxTableColumn{
xurime's avatar
xurime 已提交
300 301 302 303
			ID:   idx,
			Name: name,
		})
	}
304 305 306 307 308
	tbl.TableColumns = &xlsxTableColumns{
		Count:       len(tableColumns),
		TableColumn: tableColumns,
	}
	return nil
309 310
}

311 312
// checkDefinedName check whether there are illegal characters in the defined
// name or table name. Verify that the name:
313 314
// 1. Starts with a letter or underscore (_)
// 2. Doesn't include a space or character that isn't allowed
315
func checkDefinedName(name string) error {
316
	if utf8.RuneCountInString(name) > MaxFieldLength {
317
		return ErrNameLength
318 319 320 321 322 323 324 325
	}
	for i, c := range name {
		if string(c) == "_" {
			continue
		}
		if unicode.IsLetter(c) {
			continue
		}
M
Matthias Endler 已提交
326
		if i > 0 && (unicode.IsDigit(c) || c == '.') {
327 328
			continue
		}
329
		return newInvalidNameError(name)
330 331 332 333
	}
	return nil
}

334
// addTable provides a function to add table by given worksheet name,
335
// range reference and format set.
336
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Table) error {
337 338 339 340
	// Correct the minimum number of rows, the table at least two lines.
	if y1 == y2 {
		y2++
	}
341 342 343 344
	hideHeaderRow := opts != nil && opts.ShowHeaderRow != nil && !*opts.ShowHeaderRow
	if hideHeaderRow {
		y1++
	}
345
	// Correct table range reference, such correct C1:B3 to B1:C3.
xurime's avatar
xurime 已提交
346
	ref, err := f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
347 348 349
	if err != nil {
		return err
	}
350
	name := opts.Name
351 352 353
	if name == "" {
		name = "Table" + strconv.Itoa(i)
	}
xurime's avatar
xurime 已提交
354
	t := xlsxTable{
355
		XMLNS:       NameSpaceSpreadSheet.Value,
xurime's avatar
xurime 已提交
356 357 358 359 360 361 362 363
		ID:          i,
		Name:        name,
		DisplayName: name,
		Ref:         ref,
		AutoFilter: &xlsxAutoFilter{
			Ref: ref,
		},
		TableStyleInfo: &xlsxTableStyleInfo{
364
			Name:              opts.StyleName,
365 366
			ShowFirstColumn:   opts.ShowFirstColumn,
			ShowLastColumn:    opts.ShowLastColumn,
367
			ShowRowStripes:    *opts.ShowRowStripes,
368
			ShowColumnStripes: opts.ShowColumnStripes,
xurime's avatar
xurime 已提交
369 370
		},
	}
371
	_ = f.setTableColumns(sheet, !hideHeaderRow, x1, y1, x2, &t)
372 373 374 375
	if hideHeaderRow {
		t.AutoFilter = nil
		t.HeaderRowCount = intPtr(0)
	}
376
	table, err := xml.Marshal(t)
377
	f.saveFileList(tableXML, table)
378
	return err
xurime's avatar
xurime 已提交
379
}
380 381

// AutoFilter provides the method to add auto filter in a worksheet by given
382
// worksheet name, range reference and settings. An auto filter in Excel is a
xurime's avatar
xurime 已提交
383
// way of filtering a 2D range of data based on some simple criteria. For
384
// example applying an auto filter to a cell range A1:D4 in the Sheet1:
385
//
386
//	err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{})
387
//
388
// Filter data in an auto filter:
389
//
390 391
//	err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{
//	    {Column: "B", Expression: "x != blanks"},
392
//	})
393
//
394
// Column defines the filter columns in an auto filter range based on simple
395
// criteria
396
//
xurime's avatar
xurime 已提交
397 398
// 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
399
// the SetRowVisible function. Excelize can't filter rows automatically since
xurime's avatar
xurime 已提交
400
// this isn't part of the file format.
401 402 403
//
// Setting a filter criteria for a column:
//
404
// Expression defines the conditions, the following operators are available
xurime's avatar
xurime 已提交
405
// for setting the filter criteria:
406
//
407 408 409 410 411 412 413 414
//	==
//	!=
//	>
//	<
//	>=
//	<=
//	and
//	or
415
//
xurime's avatar
xurime 已提交
416 417
// An expression can comprise a single statement or two statements separated
// by the 'and' and 'or' operators. For example:
418
//
419 420 421 422 423
//	x <  2000
//	x >  2000
//	x == 2000
//	x >  2000 and x <  5000
//	x == 2000 or  x == 5000
424
//
425 426
// Filtering of blank or non-blank data can be achieved by using a value of
// Blanks or NonBlanks in the expression:
427
//
428 429
//	x == Blanks
//	x == NonBlanks
430 431 432
//
// Excel also allows some simple string matching operations:
//
433 434 435 436 437
//	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
438
//	x != *b*     // doesn't contain b
439
//
440 441 442 443
// 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 '~'.
444
//
445 446 447
// 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:
448
//
449 450 451
//	x     < 2000
//	col   < 2000
//	Price < 2000
452
func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) error {
xurime's avatar
xurime 已提交
453
	coordinates, err := rangeRefToCoordinates(rangeRef)
454 455 456
	if err != nil {
		return err
	}
457 458 459
	_ = sortCoordinates(coordinates)
	// Correct reference range, such correct C1:B3 to B1:C3.
	ref, _ := f.coordinatesToRangeRef(coordinates, true)
460 461 462 463
	wb, err := f.workbookReader()
	if err != nil {
		return err
	}
464 465 466 467
	sheetID, err := f.GetSheetIndex(sheet)
	if err != nil {
		return err
	}
468
	filterRange := fmt.Sprintf("'%s'!%s", sheet, ref)
469
	d := xlsxDefinedName{
470
		Name:         builtInDefinedNames[2],
471
		Hidden:       true,
472 473
		LocalSheetID: intPtr(sheetID),
		Data:         filterRange,
474
	}
475
	if wb.DefinedNames == nil {
476 477 478
		wb.DefinedNames = &xlsxDefinedNames{
			DefinedName: []xlsxDefinedName{d},
		}
479 480 481
	} else {
		var definedNameExists bool
		for idx := range wb.DefinedNames.DefinedName {
xurime's avatar
xurime 已提交
482 483 484 485 486
			definedName, localSheetID := wb.DefinedNames.DefinedName[idx], 0
			if definedName.LocalSheetID != nil {
				localSheetID = *definedName.LocalSheetID
			}
			if definedName.Name == builtInDefinedNames[2] && localSheetID == sheetID && definedName.Hidden {
487 488 489 490 491 492 493
				wb.DefinedNames.DefinedName[idx].Data = filterRange
				definedNameExists = true
			}
		}
		if !definedNameExists {
			wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
		}
494
	}
xurime's avatar
xurime 已提交
495 496
	columns := coordinates[2] - coordinates[0]
	return f.autoFilter(sheet, ref, columns, coordinates[0], opts)
497 498
}

xurime's avatar
xurime 已提交
499
// autoFilter provides a function to extract the tokens from the filter
500
// expression. The tokens are mainly non-whitespace groups.
501
func (f *File) autoFilter(sheet, ref string, columns, col int, opts []AutoFilterOptions) error {
502
	ws, err := f.workSheetReader(sheet)
xurime's avatar
xurime 已提交
503 504 505
	if err != nil {
		return err
	}
506 507
	if ws.SheetPr != nil {
		ws.SheetPr.FilterMode = true
508
	}
509
	ws.SheetPr = &xlsxSheetPr{FilterMode: true}
510 511 512
	filter := &xlsxAutoFilter{
		Ref: ref,
	}
513
	ws.AutoFilter = filter
514 515 516 517 518 519 520 521 522 523
	for _, opt := range opts {
		if opt.Column == "" || opt.Expression == "" {
			continue
		}
		fsCol, err := ColumnNameToNumber(opt.Column)
		if err != nil {
			return err
		}
		offset := fsCol - col
		if offset < 0 || offset > columns {
524
			return newInvalidAutoFilterColumnError(opt.Column)
525 526
		}
		fc := &xlsxFilterColumn{ColID: offset}
527
		token := expressionFormat.FindAllString(opt.Expression, -1)
528
		if len(token) != 3 && len(token) != 7 {
529
			return newInvalidAutoFilterExpError(opt.Expression)
530 531 532 533 534 535 536
		}
		expressions, tokens, err := f.parseFilterExpression(opt.Expression, token)
		if err != nil {
			return err
		}
		f.writeAutoFilter(fc, expressions, tokens)
		filter.FilterColumn = append(filter.FilterColumn, fc)
537
	}
538
	ws.AutoFilter = filter
539 540 541
	return nil
}

xurime's avatar
xurime 已提交
542 543
// writeAutoFilter provides a function to check for single or double custom
// filters as default filters and handle them accordingly.
544
func (f *File) writeAutoFilter(fc *xlsxFilterColumn, exp []int, tokens []string) {
545 546
	if len(exp) == 1 && exp[0] == 2 {
		// Single equality.
547
		var filters []*xlsxFilter
548
		filters = append(filters, &xlsxFilter{Val: tokens[0]})
549
		fc.Filters = &xlsxFilters{Filter: filters}
550 551 552
		return
	}
	if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
553
		// Double equality with "or" operator.
554
		var filters []*xlsxFilter
555 556 557
		for _, v := range tokens {
			filters = append(filters, &xlsxFilter{Val: v})
		}
558
		fc.Filters = &xlsxFilters{Filter: filters}
559 560 561 562 563 564 565 566
		return
	}
	// Non default custom filter.
	expRel, andRel := map[int]int{0: 0, 1: 2}, map[int]bool{0: true, 1: false}
	for k, v := range tokens {
		f.writeCustomFilter(fc, exp[expRel[k]], v)
		if k == 1 {
			fc.CustomFilters.And = andRel[exp[k]]
567 568 569 570
		}
	}
}

xurime's avatar
xurime 已提交
571
// writeCustomFilter provides a function to write the <customFilter> element.
572
func (f *File) writeCustomFilter(fc *xlsxFilterColumn, operator int, val string) {
573 574 575 576 577 578 579 580 581 582 583 584 585
	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,
	}
586 587
	if fc.CustomFilters != nil {
		fc.CustomFilters.CustomFilter = append(fc.CustomFilters.CustomFilter, &customFilter)
588
		return
589
	}
590 591 592
	var customFilters []*xlsxCustomFilter
	customFilters = append(customFilters, &customFilter)
	fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
593 594
}

xurime's avatar
xurime 已提交
595 596 597
// parseFilterExpression provides a function to converts the tokens of a
// possibly conditional expression into 1 or 2 sub expressions for further
// parsing.
598 599 600
//
// Examples:
//
601 602
//	('x', '==', 2000) -> exp1
//	('x', '>',  2000, 'and', 'x', '<', 5000) -> exp1 and exp2
603
func (f *File) parseFilterExpression(expression string, tokens []string) ([]int, []string, error) {
604 605
	var expressions []int
	var t []string
606 607 608
	if len(tokens) == 7 {
		// The number of tokens will be either 3 (for 1 expression) or 7 (for 2
		// expressions).
609
		conditional, c := 0, tokens[3]
610
		if conditionFormat.MatchString(c) {
611 612
			conditional = 1
		}
xurime's avatar
xurime 已提交
613
		expression1, token1, err := f.parseFilterTokens(expression, tokens[:3])
614 615 616 617 618 619 620
		if err != nil {
			return expressions, t, err
		}
		expression2, token2, err := f.parseFilterTokens(expression, tokens[4:7])
		if err != nil {
			return expressions, t, err
		}
621 622 623 624 625
		return []int{expression1[0], conditional, expression2[0]}, []string{token1, token2}, nil
	}
	exp, token, err := f.parseFilterTokens(expression, tokens)
	if err != nil {
		return expressions, t, err
626
	}
627
	return exp, []string{token}, nil
628 629
}

xurime's avatar
xurime 已提交
630
// parseFilterTokens provides a function to parse the 3 tokens of a filter
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
// 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.
650
		return []int{}, "", newUnknownFilterTokenError(tokens[1])
651 652 653
	}
	token := tokens[2]
	// Special handling for Blanks/NonBlanks.
654
	re := blankFormat.MatchString(strings.ToLower(token))
655 656 657
	if re {
		// Only allow Equals or NotEqual in this context.
		if operator != 2 && operator != 5 {
658
			return []int{operator}, token, newInvalidAutoFilterOperatorError(tokens[1], expression)
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
		}
		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 = " "
			}
		}
	}
677
	// If the string token contains an Excel match character then change the
678
	// operator type to indicate a non "simple" equality.
679
	if re = matchFormat.MatchString(token); operator == 2 && re {
680 681 682 683
		operator = 22
	}
	return []int{operator}, token, nil
}