sheet.go 59.6 KB
Newer Older
1
// Copyright 2016 - 2022 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 10
// 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.
xurime's avatar
xurime 已提交
11

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

import (
15
	"bytes"
xurime's avatar
xurime 已提交
16
	"encoding/json"
xurime's avatar
xurime 已提交
17
	"encoding/xml"
18
	"fmt"
19
	"io"
20
	"io/ioutil"
21
	"log"
22 23
	"os"
	"path"
24
	"path/filepath"
25
	"reflect"
R
r-uchino 已提交
26
	"regexp"
27
	"sort"
xurime's avatar
xurime 已提交
28 29
	"strconv"
	"strings"
30
	"unicode/utf16"
31
	"unicode/utf8"
32 33

	"github.com/mohae/deepcopy"
xurime's avatar
xurime 已提交
34 35
)

36 37
// NewSheet provides the function to create a new sheet by given a worksheet
// name and returns the index of the sheets in the workbook
38 39 40
// (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.
41
func (f *File) NewSheet(name string) int {
42
	// Check if the worksheet already exists
43 44 45
	index := f.GetSheetIndex(name)
	if index != -1 {
		return index
46
	}
47
	f.DeleteSheet(name)
48
	f.SheetCount++
49 50 51 52 53 54 55 56
	wb := f.workbookReader()
	sheetID := 0
	for _, v := range wb.Sheets.Sheet {
		if v.SheetID > sheetID {
			sheetID = v.SheetID
		}
	}
	sheetID++
xurime's avatar
xurime 已提交
57
	// Update docProps/app.xml
58
	f.setAppXML()
xurime's avatar
xurime 已提交
59
	// Update [Content_Types].xml
60
	f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet)
xurime's avatar
xurime 已提交
61
	// Create new sheet /xl/worksheets/sheet%d.xml
62
	f.setSheet(sheetID, name)
63
	// Update workbook.xml.rels
64
	rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipWorkSheet, fmt.Sprintf("/xl/worksheets/sheet%d.xml", sheetID), "")
65
	// Update workbook.xml
66
	f.setWorkbook(name, sheetID, rID)
67
	return f.GetSheetIndex(name)
xurime's avatar
xurime 已提交
68 69
}

xurime's avatar
xurime 已提交
70
// contentTypesReader provides a function to get the pointer to the
xurime's avatar
xurime 已提交
71 72
// [Content_Types].xml structure after deserialization.
func (f *File) contentTypesReader() *xlsxTypes {
73 74
	var err error

xurime's avatar
xurime 已提交
75
	if f.ContentTypes == nil {
76
		f.ContentTypes = new(xlsxTypes)
77 78
		f.ContentTypes.Lock()
		defer f.ContentTypes.Unlock()
79
		if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))).
80 81 82
			Decode(f.ContentTypes); err != nil && err != io.EOF {
			log.Printf("xml decode error: %s", err)
		}
xurime's avatar
xurime 已提交
83 84 85 86
	}
	return f.ContentTypes
}

xurime's avatar
xurime 已提交
87
// contentTypesWriter provides a function to save [Content_Types].xml after
xurime's avatar
xurime 已提交
88 89 90 91
// serialize structure.
func (f *File) contentTypesWriter() {
	if f.ContentTypes != nil {
		output, _ := xml.Marshal(f.ContentTypes)
92
		f.saveFileList(defaultXMLPathContentTypes, output)
xurime's avatar
xurime 已提交
93 94 95
	}
}

xurime's avatar
xurime 已提交
96 97 98 99 100 101 102 103 104 105 106 107 108
// 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
}

109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
// 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
	})
	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
}

140
// workSheetWriter provides a function to save xl/worksheets/sheet%d.xml after
xurime's avatar
xurime 已提交
141
// serialize structure.
142
func (f *File) workSheetWriter() {
T
Ted 已提交
143 144 145
	var arr []byte
	buffer := bytes.NewBuffer(arr)
	encoder := xml.NewEncoder(buffer)
146 147 148
	f.Sheet.Range(func(p, ws interface{}) bool {
		if ws != nil {
			sheet := ws.(*xlsxWorksheet)
T
three 已提交
149 150 151
			if sheet.MergeCells != nil && len(sheet.MergeCells.Cells) > 0 {
				_ = f.mergeOverlapCells(sheet)
			}
152 153 154
			if sheet.Cols != nil && len(sheet.Cols.Col) > 0 {
				f.mergeExpandedCols(sheet)
			}
155
			for k, v := range sheet.SheetData.Row {
156
				sheet.SheetData.Row[k].C = trimCell(v.C)
157
			}
158
			if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil {
159
				f.addNameSpaces(p.(string), SourceRelationship)
160
			}
T
Ted 已提交
161
			// reusing buffer
162
			_ = encoder.Encode(sheet)
163 164
			f.saveFileList(p.(string), replaceRelationshipsBytes(f.replaceNameSpaceBytes(p.(string), buffer.Bytes())))
			ok := f.checked[p.(string)]
165
			if ok {
166 167
				f.Sheet.Delete(p.(string))
				f.checked[p.(string)] = false
168
			}
T
Ted 已提交
169
			buffer.Reset()
xurime's avatar
xurime 已提交
170
		}
171 172
		return true
	})
xurime's avatar
xurime 已提交
173 174
}

H
Harris 已提交
175
// trimCell provides a function to trim blank cells which created by fillColumns.
176
func trimCell(column []xlsxC) []xlsxC {
H
Harris 已提交
177 178 179 180 181 182 183
	rowFull := true
	for i := range column {
		rowFull = column[i].hasValue() && rowFull
	}
	if rowFull {
		return column
	}
184 185
	col := make([]xlsxC, len(column))
	i := 0
186
	for _, c := range column {
H
Harris 已提交
187
		if c.hasValue() {
188 189
			col[i] = c
			i++
190 191
		}
	}
xurime's avatar
xurime 已提交
192
	return col[:i]
193 194
}

195
// setContentTypes provides a function to read and update property of contents
196
// type of the spreadsheet.
197
func (f *File) setContentTypes(partName, contentType string) {
xurime's avatar
xurime 已提交
198
	content := f.contentTypesReader()
xurime's avatar
xurime 已提交
199 200
	content.Lock()
	defer content.Unlock()
xurime's avatar
xurime 已提交
201
	content.Overrides = append(content.Overrides, xlsxOverride{
202 203
		PartName:    partName,
		ContentType: contentType,
xurime's avatar
xurime 已提交
204 205 206
	})
}

207
// setSheet provides a function to update sheet property by given index.
208
func (f *File) setSheet(index int, name string) {
209
	ws := xlsxWorksheet{
xurime's avatar
xurime 已提交
210
		Dimension: &xlsxDimension{Ref: "A1"},
211
		SheetViews: &xlsxSheetViews{
xurime's avatar
xurime 已提交
212 213 214
			SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
		},
	}
215
	path := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml"
216
	f.sheetMap[trimSheetName(name)] = path
217
	f.Sheet.Store(path, &ws)
218
	f.xmlAttr[path] = []xml.Attr{NameSpaceSpreadSheet}
xurime's avatar
xurime 已提交
219 220
}

xurime's avatar
xurime 已提交
221
// relsWriter provides a function to save relationships after
xurime's avatar
xurime 已提交
222
// serialize structure.
xurime's avatar
xurime 已提交
223
func (f *File) relsWriter() {
224
	f.Relationships.Range(func(path, rel interface{}) bool {
xurime's avatar
xurime 已提交
225
		if rel != nil {
226 227 228
			output, _ := xml.Marshal(rel.(*xlsxRelationships))
			if strings.HasPrefix(path.(string), "xl/worksheets/sheet/rels/sheet") {
				output = f.replaceNameSpaceBytes(path.(string), output)
xurime's avatar
xurime 已提交
229
			}
230
			f.saveFileList(path.(string), replaceRelationshipsBytes(output))
231
		}
232 233
		return true
	})
xurime's avatar
xurime 已提交
234 235
}

236
// setAppXML update docProps/app.xml file of XML.
237
func (f *File) setAppXML() {
238
	f.saveFileList(defaultXMLPathDocPropsApp, []byte(templateDocpropsApp))
xurime's avatar
xurime 已提交
239 240
}

241 242 243
// 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.
244
func replaceRelationshipsBytes(content []byte) []byte {
245 246
	oldXmlns := []byte(`xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships`)
	newXmlns := []byte("r")
xurime's avatar
xurime 已提交
247
	return bytesReplace(content, oldXmlns, newXmlns, -1)
248 249
}

250 251 252 253
// 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.
254
func (f *File) SetActiveSheet(index int) {
255 256
	if index < 0 {
		index = 0
xurime's avatar
xurime 已提交
257
	}
258
	wb := f.workbookReader()
259 260
	for activeTab := range wb.Sheets.Sheet {
		if activeTab == index {
xurime's avatar
xurime 已提交
261 262 263
			if wb.BookViews == nil {
				wb.BookViews = &xlsxBookViews{}
			}
264 265 266 267 268 269 270 271
			if len(wb.BookViews.WorkBookView) > 0 {
				wb.BookViews.WorkBookView[0].ActiveTab = activeTab
			} else {
				wb.BookViews.WorkBookView = append(wb.BookViews.WorkBookView, xlsxWorkBookView{
					ActiveTab: activeTab,
				})
			}
		}
xurime's avatar
xurime 已提交
272
	}
273
	for idx, name := range f.GetSheetList() {
274
		ws, err := f.workSheetReader(name)
275
		if err != nil {
276
			// Chartsheet or dialogsheet
277 278
			return
		}
279 280
		if ws.SheetViews == nil {
			ws.SheetViews = &xlsxSheetViews{
281 282 283
				SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
			}
		}
284 285
		if len(ws.SheetViews.SheetView) > 0 {
			ws.SheetViews.SheetView[0].TabSelected = false
286
		}
287
		if index == idx {
288 289
			if len(ws.SheetViews.SheetView) > 0 {
				ws.SheetViews.SheetView[0].TabSelected = true
xurime's avatar
xurime 已提交
290
			} else {
291
				ws.SheetViews.SheetView = append(ws.SheetViews.SheetView, xlsxSheetView{
xurime's avatar
xurime 已提交
292 293 294 295 296 297 298
					TabSelected: true,
				})
			}
		}
	}
}

xurime's avatar
xurime 已提交
299
// GetActiveSheetIndex provides a function to get active sheet index of the
300
// spreadsheet. If not found the active sheet will be return integer 0.
301
func (f *File) GetActiveSheetIndex() (index int) {
302
	sheetID := f.getActiveSheetID()
303 304 305 306 307
	wb := f.workbookReader()
	if wb != nil {
		for idx, sheet := range wb.Sheets.Sheet {
			if sheet.SheetID == sheetID {
				index = idx
308
				return
309 310 311 312 313 314
			}
		}
	}
	return
}

315
// getActiveSheetID provides a function to get active sheet ID of the
316
// spreadsheet. If not found the active sheet will be return integer 0.
317
func (f *File) getActiveSheetID() int {
318 319
	wb := f.workbookReader()
	if wb != nil {
xurime's avatar
xurime 已提交
320 321 322 323
		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
324
			}
325
		}
xurime's avatar
xurime 已提交
326
		if len(wb.Sheets.Sheet) >= 1 {
327 328
			return wb.Sheets.Sheet[0].SheetID
		}
329
	}
330
	return 0
331 332
}

333 334
// 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
335 336 337
// 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.
338
func (f *File) SetSheetName(oldName, newName string) {
339 340
	oldName = trimSheetName(oldName)
	newName = trimSheetName(newName)
341 342 343
	if newName == oldName {
		return
	}
xurime's avatar
xurime 已提交
344
	content := f.workbookReader()
345 346 347
	for k, v := range content.Sheets.Sheet {
		if v.Name == oldName {
			content.Sheets.Sheet[k].Name = newName
348 349
			f.sheetMap[newName] = f.sheetMap[oldName]
			delete(f.sheetMap, oldName)
350 351 352 353
		}
	}
}

354 355 356
// 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.
357 358 359 360
func (f *File) GetSheetName(index int) (name string) {
	for idx, sheet := range f.GetSheetList() {
		if idx == index {
			name = sheet
361
			return
362 363 364 365 366
		}
	}
	return
}

367 368 369
// 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.
370 371 372
func (f *File) getSheetID(name string) int {
	for sheetID, sheet := range f.GetSheetMap() {
		if sheet == trimSheetName(name) {
373
			return sheetID
374 375
		}
	}
376
	return -1
377 378
}

379
// GetSheetIndex provides a function to get a sheet index of the workbook by
380 381 382
// 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.
383
func (f *File) GetSheetIndex(name string) int {
384
	for index, sheet := range f.GetSheetList() {
385
		if strings.EqualFold(sheet, trimSheetName(name)) {
386
			return index
387 388
		}
	}
389
	return -1
390 391
}

392 393
// GetSheetMap provides a function to get worksheets, chart sheets, dialog
// sheets ID and name map of the workbook. For example:
394
//
395
//    f, err := excelize.OpenFile("Book1.xlsx")
396
//    if err != nil {
397
//        return
398
//    }
xurime's avatar
xurime 已提交
399 400 401 402 403
//    defer func() {
//        if err := f.Close(); err != nil {
//            fmt.Println(err)
//        }
//    }()
xurime's avatar
xurime 已提交
404
//    for index, name := range f.GetSheetMap() {
xurime's avatar
xurime 已提交
405
//        fmt.Println(index, name)
406 407 408
//    }
//
func (f *File) GetSheetMap() map[int]string {
409
	wb := f.workbookReader()
410
	sheetMap := map[int]string{}
411
	if wb != nil {
412 413
		for _, sheet := range wb.Sheets.Sheet {
			sheetMap[sheet.SheetID] = sheet.Name
414
		}
415 416
	}
	return sheetMap
xurime's avatar
xurime 已提交
417
}
418

419 420
// GetSheetList provides a function to get worksheets, chart sheets, and
// dialog sheets name list of the workbook.
421 422 423 424 425 426 427 428 429 430 431
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
432
// of the spreadsheet.
433
func (f *File) getSheetMap() map[string]string {
434
	maps := map[string]string{}
435 436
	for _, v := range f.workbookReader().Sheets.Sheet {
		for _, rel := range f.relsReader(f.getWorkbookRelsPath()).Relationships {
437
			if rel.ID == v.ID {
xurime's avatar
xurime 已提交
438
				path := f.getWorksheetPath(rel.Target)
439
				if _, ok := f.Pkg.Load(path); ok {
440
					maps[v.Name] = path
S
Sustainedhhh 已提交
441
				}
442 443 444
				if _, ok := f.tempFiles.Load(path); ok {
					maps[v.Name] = path
				}
445 446
			}
		}
447 448 449 450
	}
	return maps
}

xurime's avatar
xurime 已提交
451
// SetSheetBackground provides a function to set background picture by given
452
// worksheet name and file path.
453 454 455 456 457 458
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
	}
459
	ext, ok := supportImageTypes[path.Ext(picture)]
460
	if !ok {
461
		return ErrImgExt
462
	}
xurime's avatar
xurime 已提交
463
	file, _ := ioutil.ReadFile(filepath.Clean(picture))
xurime's avatar
xurime 已提交
464
	name := f.addMedia(file, ext)
465
	sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
xurime's avatar
xurime 已提交
466
	rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
xurime's avatar
xurime 已提交
467
	f.addSheetPicture(sheet, rID)
468
	f.addSheetNameSpace(sheet, SourceRelationship)
469 470 471
	f.setContentTypePartImageExtensions()
	return err
}
472

xurime's avatar
xurime 已提交
473
// DeleteSheet provides a function to delete worksheet in a workbook by given
474 475 476 477 478 479
// 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.
480
func (f *File) DeleteSheet(name string) {
481
	if f.SheetCount == 1 || f.GetSheetIndex(name) == -1 {
482 483 484 485
		return
	}
	sheetName := trimSheetName(name)
	wb := f.workbookReader()
486
	wbRels := f.relsReader(f.getWorkbookRelsPath())
487
	activeSheetName := f.GetSheetName(f.GetActiveSheetIndex())
488
	deleteLocalSheetID := f.GetSheetIndex(name)
489 490 491 492 493 494 495 496 497 498 499 500 501 502
	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"
503 504 505
				}
			}
		}
506 507 508 509 510 511 512 513 514 515
		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--
516
	}
517 518 519
	f.SetActiveSheet(f.GetSheetIndex(activeSheetName))
}

xurime's avatar
xurime 已提交
520 521
// deleteAndAdjustDefinedNames delete and adjust defined name in the workbook
// by given worksheet ID.
522
func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) {
xurime's avatar
xurime 已提交
523
	if wb == nil || wb.DefinedNames == nil {
524 525 526 527 528 529 530 531 532 533 534
		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)
535
			}
536 537 538 539
		}
	}
}

xurime's avatar
xurime 已提交
540
// deleteSheetFromWorkbookRels provides a function to remove worksheet
541
// relationships by given relationships ID in the file workbook.xml.rels.
542
func (f *File) deleteSheetFromWorkbookRels(rID string) string {
543
	content := f.relsReader(f.getWorkbookRelsPath())
xurime's avatar
xurime 已提交
544 545
	content.Lock()
	defer content.Unlock()
546
	for k, v := range content.Relationships {
xurime's avatar
xurime 已提交
547 548 549
		if v.ID == rID {
			content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...)
			return v.Target
550 551 552 553 554
		}
	}
	return ""
}

xurime's avatar
xurime 已提交
555
// deleteSheetFromContentTypes provides a function to remove worksheet
556
// relationships by given target name in the file [Content_Types].xml.
557
func (f *File) deleteSheetFromContentTypes(target string) {
558 559 560
	if !strings.HasPrefix(target, "/") {
		target = "/xl/" + target
	}
xurime's avatar
xurime 已提交
561
	content := f.contentTypesReader()
xurime's avatar
xurime 已提交
562 563
	content.Lock()
	defer content.Unlock()
564
	for k, v := range content.Overrides {
565
		if v.PartName == target {
xurime's avatar
xurime 已提交
566
			content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
567 568 569
		}
	}
}
570

xurime's avatar
xurime 已提交
571
// CopySheet provides a function to duplicate a worksheet by gave source and
572 573 574 575
// target worksheet index. Note that currently doesn't support duplicate
// workbooks that contain tables, charts or pictures. For Example:
//
//    // Sheet1 already exists...
xurime's avatar
xurime 已提交
576 577
//    index := f.NewSheet("Sheet2")
//    err := f.CopySheet(1, index)
578
//    return err
579 580
//
func (f *File) CopySheet(from, to int) error {
581
	if from < 0 || to < 0 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" {
582
		return ErrSheetIdx
583
	}
xurime's avatar
xurime 已提交
584
	return f.copySheet(from, to)
585 586
}

xurime's avatar
xurime 已提交
587
// copySheet provides a function to duplicate a worksheet by gave source and
588
// target worksheet name.
xurime's avatar
xurime 已提交
589
func (f *File) copySheet(from, to int) error {
590 591
	fromSheet := f.GetSheetName(from)
	sheet, err := f.workSheetReader(fromSheet)
xurime's avatar
xurime 已提交
592 593 594
	if err != nil {
		return err
	}
595
	worksheet := deepcopy.Copy(sheet).(*xlsxWorksheet)
596 597
	toSheetID := strconv.Itoa(f.getSheetID(f.GetSheetName(to)))
	path := "xl/worksheets/sheet" + toSheetID + ".xml"
598 599 600 601 602 603
	if len(worksheet.SheetViews.SheetView) > 0 {
		worksheet.SheetViews.SheetView[0].TabSelected = false
	}
	worksheet.Drawing = nil
	worksheet.TableParts = nil
	worksheet.PageSetUp = nil
604
	f.Sheet.Store(path, worksheet)
605 606
	toRels := "xl/worksheets/_rels/sheet" + toSheetID + ".xml.rels"
	fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(f.getSheetID(fromSheet)) + ".xml.rels"
607 608
	if rels, ok := f.Pkg.Load(fromRels); ok && rels != nil {
		f.Pkg.Store(toRels, rels.([]byte))
609
	}
610 611
	fromSheetXMLPath := f.sheetMap[trimSheetName(fromSheet)]
	fromSheetAttr := f.xmlAttr[fromSheetXMLPath]
612
	f.xmlAttr[path] = fromSheetAttr
xurime's avatar
xurime 已提交
613
	return err
614
}
615

xurime's avatar
xurime 已提交
616
// SetSheetVisible provides a function to set worksheet visible by given worksheet
617 618
// name. A workbook must contain at least one visible worksheet. If the given
// worksheet has been activated, this setting will be invalidated. Sheet state
xurime's avatar
xurime 已提交
619
// values as defined by https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetstatevalues
xurime's avatar
xurime 已提交
620 621 622 623 624
//
//    visible
//    hidden
//    veryHidden
//
625 626
// For example, hide Sheet1:
//
xurime's avatar
xurime 已提交
627
//    err := f.SetSheetVisible("Sheet1", false)
628
//
xurime's avatar
xurime 已提交
629
func (f *File) SetSheetVisible(name string, visible bool) error {
630 631
	name = trimSheetName(name)
	content := f.workbookReader()
632 633 634 635 636 637
	if visible {
		for k, v := range content.Sheets.Sheet {
			if v.Name == name {
				content.Sheets.Sheet[k].State = ""
			}
		}
xurime's avatar
xurime 已提交
638
		return nil
639
	}
640 641
	count := 0
	for _, v := range content.Sheets.Sheet {
642
		if v.State != "hidden" {
643 644 645 646
			count++
		}
	}
	for k, v := range content.Sheets.Sheet {
xurime's avatar
xurime 已提交
647
		ws, err := f.workSheetReader(v.Name)
xurime's avatar
xurime 已提交
648 649 650
		if err != nil {
			return err
		}
651
		tabSelected := false
xurime's avatar
xurime 已提交
652 653
		if len(ws.SheetViews.SheetView) > 0 {
			tabSelected = ws.SheetViews.SheetView[0].TabSelected
654 655
		}
		if v.Name == name && count > 1 && !tabSelected {
656
			content.Sheets.Sheet[k].State = "hidden"
657 658
		}
	}
xurime's avatar
xurime 已提交
659
	return nil
660 661
}

xurime's avatar
xurime 已提交
662
// parseFormatPanesSet provides a function to parse the panes settings.
663
func parseFormatPanesSet(formatSet string) (*formatPanes, error) {
xurime's avatar
xurime 已提交
664
	format := formatPanes{}
665 666
	err := json.Unmarshal([]byte(formatSet), &format)
	return &format, err
xurime's avatar
xurime 已提交
667 668
}

xurime's avatar
xurime 已提交
669
// SetPanes provides a function to create and remove freeze panes and split panes
670
// by given worksheet name and panes format set.
xurime's avatar
xurime 已提交
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
//
// 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
//                                    |
xurime's avatar
xurime 已提交
699
//    topRight (Top Right Pane)       | Top right pane, when both vertical and horizontal
xurime's avatar
xurime 已提交
700 701 702 703 704 705 706
//                                    | 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.
//
707
// Pane state type is restricted to the values supported currently listed in the following table:
xurime's avatar
xurime 已提交
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
//
//     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
xurime's avatar
xurime 已提交
736
// Sheet1!K16:
xurime's avatar
xurime 已提交
737
//
xurime's avatar
xurime 已提交
738
//    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"}]}`)
xurime's avatar
xurime 已提交
739 740
//
// An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell
xurime's avatar
xurime 已提交
741
// ranges on Sheet1!A11:XFD11:
xurime's avatar
xurime 已提交
742
//
xurime's avatar
xurime 已提交
743
//    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"}]}`)
xurime's avatar
xurime 已提交
744 745 746 747
//
// An example of how to create split panes in the Sheet1 and set the active cell
// on Sheet1!J60:
//
xurime's avatar
xurime 已提交
748
//    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"}]}`)
xurime's avatar
xurime 已提交
749 750 751
//
// An example of how to unfreeze and remove all panes on Sheet1:
//
xurime's avatar
xurime 已提交
752
//    f.SetPanes("Sheet1", `{"freeze":false,"split":false}`)
xurime's avatar
xurime 已提交
753
//
xurime's avatar
xurime 已提交
754
func (f *File) SetPanes(sheet, panes string) error {
755
	fs, _ := parseFormatPanesSet(panes)
756
	ws, err := f.workSheetReader(sheet)
xurime's avatar
xurime 已提交
757 758 759
	if err != nil {
		return err
	}
xurime's avatar
xurime 已提交
760 761 762 763 764 765 766 767 768
	p := &xlsxPane{
		ActivePane:  fs.ActivePane,
		TopLeftCell: fs.TopLeftCell,
		XSplit:      float64(fs.XSplit),
		YSplit:      float64(fs.YSplit),
	}
	if fs.Freeze {
		p.State = "frozen"
	}
769
	ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = p
xurime's avatar
xurime 已提交
770
	if !(fs.Freeze) && !(fs.Split) {
771 772
		if len(ws.SheetViews.SheetView) > 0 {
			ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil
xurime's avatar
xurime 已提交
773 774 775 776 777 778 779 780 781 782
		}
	}
	s := []*xlsxSelection{}
	for _, p := range fs.Panes {
		s = append(s, &xlsxSelection{
			ActiveCell: p.ActiveCell,
			Pane:       p.Pane,
			SQRef:      p.SQRef,
		})
	}
783
	ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Selection = s
xurime's avatar
xurime 已提交
784
	return err
xurime's avatar
xurime 已提交
785 786
}

xurime's avatar
xurime 已提交
787
// GetSheetVisible provides a function to get worksheet visible by given worksheet
788 789
// name. For example, get visible state of Sheet1:
//
xurime's avatar
xurime 已提交
790
//    f.GetSheetVisible("Sheet1")
791 792
//
func (f *File) GetSheetVisible(name string) bool {
793
	content := f.workbookReader()
794
	visible := false
795
	for k, v := range content.Sheets.Sheet {
xurime's avatar
xurime 已提交
796
		if v.Name == trimSheetName(name) {
797 798 799
			if content.Sheets.Sheet[k].State == "" || content.Sheets.Sheet[k].State == "visible" {
				visible = true
			}
800 801
		}
	}
802
	return visible
803 804
}

805 806 807 808 809 810 811
// 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:
812
//
xurime's avatar
xurime 已提交
813
//    result, err := f.SearchSheet("Sheet1", "100")
814
//
815 816
// An example of search the coordinates where the numerical value in the range
// of "0-9" of Sheet1 is described:
R
r-uchino 已提交
817
//
xurime's avatar
xurime 已提交
818
//    result, err := f.SearchSheet("Sheet1", "[0-9]", true)
R
r-uchino 已提交
819
//
820
func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) {
xurime's avatar
xurime 已提交
821 822 823 824
	var (
		regSearch bool
		result    []string
	)
825 826 827
	for _, r := range reg {
		regSearch = r
	}
R
r-uchino 已提交
828 829
	name, ok := f.sheetMap[trimSheetName(sheet)]
	if !ok {
830
		return result, ErrSheetNotExist{sheet}
R
r-uchino 已提交
831
	}
832
	if ws, ok := f.Sheet.Load(name); ok && ws != nil {
833
		// flush data
834
		output, _ := xml.Marshal(ws.(*xlsxWorksheet))
835
		f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
R
r-uchino 已提交
836
	}
837 838 839 840 841
	return f.searchSheet(name, value, regSearch)
}

// searchSheet provides a function to get coordinates by given worksheet name,
// cell value, and regular expression.
842
func (f *File) searchSheet(name, value string, regSearch bool) (result []string, err error) {
843
	var (
844 845
		cellName, inElement string
		cellCol, row        int
xurime's avatar
xurime 已提交
846
		d                   *xlsxSST
847
	)
848 849

	d = f.sharedStringsReader()
850
	decoder := f.xmlNewDecoder(bytes.NewReader(f.readBytes(name)))
R
r-uchino 已提交
851
	for {
xurime's avatar
xurime 已提交
852 853
		var token xml.Token
		token, err = decoder.Token()
xurime's avatar
xurime 已提交
854
		if err != nil || token == nil {
855 856 857
			if err == io.EOF {
				err = nil
			}
R
r-uchino 已提交
858 859
			break
		}
xurime's avatar
xurime 已提交
860
		switch xmlElement := token.(type) {
R
r-uchino 已提交
861
		case xml.StartElement:
xurime's avatar
xurime 已提交
862
			inElement = xmlElement.Name.Local
R
r-uchino 已提交
863
			if inElement == "row" {
xurime's avatar
xurime 已提交
864
				row, err = attrValToInt("r", xmlElement.Attr)
xurime's avatar
xurime 已提交
865 866
				if err != nil {
					return
867 868 869 870
				}
			}
			if inElement == "c" {
				colCell := xlsxC{}
xurime's avatar
xurime 已提交
871
				_ = decoder.DecodeElement(&colCell, &xmlElement)
xurime's avatar
xurime 已提交
872
				val, _ := colCell.getValueFrom(f, d, false)
873 874 875 876
				if regSearch {
					regex := regexp.MustCompile(value)
					if !regex.MatchString(val) {
						continue
877
					}
878 879 880
				} else {
					if val != value {
						continue
881
					}
R
r-uchino 已提交
882
				}
883 884 885 886 887 888 889 890 891
				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)
R
r-uchino 已提交
892 893 894 895
			}
		default:
		}
	}
xurime's avatar
xurime 已提交
896 897
	return
}
898

xurime's avatar
xurime 已提交
899 900 901 902 903 904 905 906 907 908 909
// 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
			}
		}
	}
910
	return
R
r-uchino 已提交
911 912
}

913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
// 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 {
1030
	ws, err := f.workSheetReader(sheet)
1031 1032 1033 1034
	if err != nil {
		return err
	}
	if settings == nil {
1035
		ws.HeaderFooter = nil
1036 1037 1038 1039 1040 1041 1042
		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++ {
1043 1044
		if len(utf16.Encode([]rune(v.Field(i).String()))) > MaxFieldLength {
			return newFieldLengthError(v.Type().Field(i).Name)
1045 1046
		}
	}
1047
	ws.HeaderFooter = &xlsxHeaderFooter{
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
		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
}

1062
// ProtectSheet provides a function to prevent other users from accidentally
1063 1064 1065 1066 1067
// 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:
1068
//
xurime's avatar
xurime 已提交
1069
//    err := f.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{
1070
//        AlgorithmName: "SHA-512",
1071 1072 1073 1074
//        Password:      "password",
//        EditScenarios: false,
//    })
//
xurime's avatar
xurime 已提交
1075
func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) error {
1076
	ws, err := f.workSheetReader(sheet)
xurime's avatar
xurime 已提交
1077 1078 1079
	if err != nil {
		return err
	}
1080 1081 1082 1083 1084 1085 1086
	if settings == nil {
		settings = &FormatSheetProtection{
			EditObjects:       true,
			EditScenarios:     true,
			SelectLockedCells: true,
		}
	}
1087
	ws.SheetProtection = &xlsxSheetProtection{
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
		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 != "" {
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
		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)
1119
	}
xurime's avatar
xurime 已提交
1120
	return err
1121 1122
}

1123 1124 1125 1126
// 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 {
1127
	ws, err := f.workSheetReader(sheet)
xurime's avatar
xurime 已提交
1128 1129 1130
	if err != nil {
		return err
	}
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149
	// 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
			}
		}
	}
1150
	ws.SheetProtection = nil
xurime's avatar
xurime 已提交
1151
	return err
1152 1153
}

1154
// trimSheetName provides a function to trim invalid characters by given worksheet
1155 1156
// name.
func trimSheetName(name string) string {
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168
	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
			}
1169
		}
1170
		name = string(r)
1171 1172 1173
	}
	return name
}
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186

// 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)
}

1187
type (
1188 1189
	// BlackAndWhite specified print black and white.
	BlackAndWhite bool
1190
	// FirstPageNumber specified the first printed page number. If no value is
1191 1192
	// specified, then 'automatic' is assumed.
	FirstPageNumber uint
1193 1194 1195
	// PageLayoutOrientation defines the orientation of page layout for a
	// worksheet.
	PageLayoutOrientation string
1196
	// PageLayoutPaperSize defines the paper size of the worksheet.
1197
	PageLayoutPaperSize int
1198
	// FitToHeight specified the number of vertical pages to fit on.
1199
	FitToHeight int
1200
	// FitToWidth specified the number of horizontal pages to fit on.
1201
	FitToWidth int
1202 1203 1204 1205
	// PageLayoutScale defines the print scaling. This attribute is restricted
	// to values ranging from 10 (10%) to 400 (400%). This setting is
	// overridden when fitToWidth and/or fitToHeight are in use.
	PageLayoutScale uint
1206
)
1207 1208 1209 1210 1211 1212 1213 1214

const (
	// OrientationPortrait indicates page layout orientation id portrait.
	OrientationPortrait = "portrait"
	// OrientationLandscape indicates page layout orientation id landscape.
	OrientationLandscape = "landscape"
)

1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233
// 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) {
1234
	if 0 < int(p) {
1235
		ps.FirstPageNumber = strconv.Itoa(int(p))
1236 1237 1238 1239 1240 1241 1242
		ps.UseFirstPageNumber = true
	}
}

// getPageLayout provides a method to get the first printed page number for
// the worksheet.
func (p *FirstPageNumber) getPageLayout(ps *xlsxPageSetUp) {
1243 1244 1245 1246 1247
	if ps != nil && ps.UseFirstPageNumber {
		if number, _ := strconv.Atoi(ps.FirstPageNumber); number != 0 {
			*p = FirstPageNumber(number)
			return
		}
1248
	}
1249
	*p = 1
1250 1251
}

1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266
// 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)
}

1267 1268
// setPageLayout provides a method to set the paper size for the worksheet.
func (p PageLayoutPaperSize) setPageLayout(ps *xlsxPageSetUp) {
1269
	ps.PaperSize = intPtr(int(p))
1270 1271 1272 1273 1274
}

// getPageLayout provides a method to get the paper size for the worksheet.
func (p *PageLayoutPaperSize) getPageLayout(ps *xlsxPageSetUp) {
	// Excel default: 1
1275
	if ps == nil || ps.PaperSize == nil {
1276 1277 1278
		*p = 1
		return
	}
1279
	*p = PageLayoutPaperSize(*ps.PaperSize)
1280 1281
}

1282 1283 1284
// setPageLayout provides a method to set the fit to height for the worksheet.
func (p FitToHeight) setPageLayout(ps *xlsxPageSetUp) {
	if int(p) > 0 {
1285
		ps.FitToHeight = intPtr(int(p))
1286 1287 1288 1289 1290
	}
}

// getPageLayout provides a method to get the fit to height for the worksheet.
func (p *FitToHeight) getPageLayout(ps *xlsxPageSetUp) {
1291
	if ps == nil || ps.FitToHeight == nil {
1292 1293 1294
		*p = 1
		return
	}
1295
	*p = FitToHeight(*ps.FitToHeight)
1296 1297 1298 1299 1300
}

// setPageLayout provides a method to set the fit to width for the worksheet.
func (p FitToWidth) setPageLayout(ps *xlsxPageSetUp) {
	if int(p) > 0 {
1301
		ps.FitToWidth = intPtr(int(p))
1302 1303 1304 1305 1306
	}
}

// getPageLayout provides a method to get the fit to width for the worksheet.
func (p *FitToWidth) getPageLayout(ps *xlsxPageSetUp) {
1307
	if ps == nil || ps.FitToWidth == nil {
1308 1309 1310
		*p = 1
		return
	}
1311
	*p = FitToWidth(*ps.FitToWidth)
1312 1313
}

1314 1315
// setPageLayout provides a method to set the scale for the worksheet.
func (p PageLayoutScale) setPageLayout(ps *xlsxPageSetUp) {
1316 1317
	if 10 <= int(p) && int(p) <= 400 {
		ps.Scale = int(p)
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329
	}
}

// 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)
}

1330 1331 1332
// SetPageLayout provides a function to sets worksheet page layout.
//
// Available options:
1333 1334 1335 1336 1337 1338 1339 1340
//
//    BlackAndWhite(bool)
//    FirstPageNumber(uint)
//    PageLayoutOrientation(string)
//    PageLayoutPaperSize(int)
//    FitToHeight(int)
//    FitToWidth(int)
//    PageLayoutScale(uint)
1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387
//
// 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)
1388
//       43  | Japanese postcard (100 mm by 148 mm)
1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462
//       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)
//
1463
func (f *File) SetPageLayout(sheet string, opts ...PageLayoutOption) error {
xurime's avatar
xurime 已提交
1464 1465 1466 1467
	s, err := f.workSheetReader(sheet)
	if err != nil {
		return err
	}
1468 1469 1470 1471 1472 1473 1474 1475 1476
	ps := s.PageSetUp
	if ps == nil {
		ps = new(xlsxPageSetUp)
		s.PageSetUp = ps
	}

	for _, opt := range opts {
		opt.setPageLayout(ps)
	}
xurime's avatar
xurime 已提交
1477
	return err
1478 1479 1480 1481 1482 1483
}

// GetPageLayout provides a function to gets worksheet page layout.
//
// Available options:
//   PageLayoutOrientation(string)
1484
//   PageLayoutPaperSize(int)
1485 1486
//   FitToHeight(int)
//   FitToWidth(int)
1487
func (f *File) GetPageLayout(sheet string, opts ...PageLayoutOptionPtr) error {
xurime's avatar
xurime 已提交
1488 1489 1490 1491
	s, err := f.workSheetReader(sheet)
	if err != nil {
		return err
	}
1492 1493 1494 1495 1496
	ps := s.PageSetUp

	for _, opt := range opts {
		opt.getPageLayout(ps)
	}
xurime's avatar
xurime 已提交
1497
	return err
1498
}
1499

1500
// SetDefinedName provides a function to set the defined names of the workbook
xurime's avatar
xurime 已提交
1501
// or worksheet. If not specified scope, the default scope is workbook.
1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518
// 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 != "" {
1519 1520
		if sheetIndex := f.GetSheetIndex(definedName.Scope); sheetIndex >= 0 {
			d.LocalSheetID = &sheetIndex
1521 1522 1523 1524 1525 1526
		}
	}
	if wb.DefinedNames != nil {
		for _, dn := range wb.DefinedNames.DefinedName {
			var scope string
			if dn.LocalSheetID != nil {
1527
				scope = f.GetSheetName(*dn.LocalSheetID)
1528 1529
			}
			if scope == definedName.Scope && dn.Name == definedName.Name {
1530
				return ErrDefinedNameduplicate
1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541
			}
		}
		wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
		return nil
	}
	wb.DefinedNames = &xlsxDefinedNames{
		DefinedName: []xlsxDefinedName{d},
	}
	return nil
}

1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554
// 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 {
1555 1556 1557 1558 1559
			scope := "Workbook"
			deleteScope := definedName.Scope
			if deleteScope == "" {
				deleteScope = "Workbook"
			}
1560
			if dn.LocalSheetID != nil {
1561
				scope = f.GetSheetName(*dn.LocalSheetID)
1562
			}
1563
			if scope == deleteScope && dn.Name == definedName.Name {
1564 1565 1566 1567 1568
				wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...)
				return nil
			}
		}
	}
1569
	return ErrDefinedNameScope
1570 1571
}

1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584
// 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",
			}
J
jaby 已提交
1585
			if dn.LocalSheetID != nil && *dn.LocalSheetID >= 0 {
1586
				definedName.Scope = f.GetSheetName(*dn.LocalSheetID)
1587 1588 1589 1590 1591 1592 1593
			}
			definedNames = append(definedNames, definedName)
		}
	}
	return definedNames
}

1594 1595 1596 1597 1598 1599
// 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()
1600
	sheetMap := f.GetSheetList()
1601 1602 1603 1604 1605 1606 1607 1608
	for idx, sheetName := range sheetMap {
		for _, s := range sheets {
			if s == sheetName && idx == activeSheet {
				inActiveSheet = true
			}
		}
	}
	if !inActiveSheet {
1609
		return ErrGroupSheets
1610 1611
	}
	// check worksheet exists
1612
	wss := []*xlsxWorksheet{}
1613
	for _, sheet := range sheets {
1614
		worksheet, err := f.workSheetReader(sheet)
1615 1616 1617
		if err != nil {
			return err
		}
1618
		wss = append(wss, worksheet)
1619
	}
1620 1621
	for _, ws := range wss {
		sheetViews := ws.SheetViews.SheetView
1622 1623
		if len(sheetViews) > 0 {
			for idx := range sheetViews {
1624
				ws.SheetViews.SheetView[idx].TabSelected = true
1625 1626 1627 1628 1629 1630 1631 1632 1633 1634
			}
			continue
		}
	}
	return nil
}

// UngroupSheets provides a function to ungroup worksheets.
func (f *File) UngroupSheets() error {
	activeSheet := f.GetActiveSheetIndex()
1635 1636
	for index, sheet := range f.GetSheetList() {
		if activeSheet == index {
1637 1638
			continue
		}
1639 1640
		ws, _ := f.workSheetReader(sheet)
		sheetViews := ws.SheetViews.SheetView
1641 1642
		if len(sheetViews) > 0 {
			for idx := range sheetViews {
1643
				ws.SheetViews.SheetView[idx].TabSelected = false
1644 1645 1646 1647 1648 1649
			}
		}
	}
	return nil
}

1650 1651 1652 1653 1654 1655 1656
// 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
1657
	rowBrk, colBrk := -1, -1
1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758
	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
}

xurime's avatar
xurime 已提交
1759
// relsReader provides a function to get the pointer to the structure
1760
// after deserialization of xl/worksheets/_rels/sheet%d.xml.rels.
xurime's avatar
xurime 已提交
1761
func (f *File) relsReader(path string) *xlsxRelationships {
1762
	var err error
1763 1764
	rels, _ := f.Relationships.Load(path)
	if rels == nil {
1765
		if _, ok := f.Pkg.Load(path); ok {
xurime's avatar
xurime 已提交
1766
			c := xlsxRelationships{}
1767 1768 1769 1770
			if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
				Decode(&c); err != nil && err != io.EOF {
				log.Printf("xml decode error: %s", err)
			}
1771
			f.Relationships.Store(path, &c)
1772 1773
		}
	}
1774 1775 1776 1777
	if rels, _ = f.Relationships.Load(path); rels != nil {
		return rels.(*xlsxRelationships)
	}
	return nil
1778 1779
}

1780 1781
// fillSheetData ensures there are enough rows, and columns in the chosen
// row to accept data. Missing rows are backfilled and given their row number
H
Harris 已提交
1782
// Uses the last populated row as a hint for the size of the next row to add
1783
func prepareSheetXML(ws *xlsxWorksheet, col int, row int) {
1784 1785
	ws.Lock()
	defer ws.Unlock()
1786
	rowCount := len(ws.SheetData.Row)
H
Harris 已提交
1787
	sizeHint := 0
1788 1789
	var ht float64
	var customHeight bool
xurime's avatar
xurime 已提交
1790
	if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight {
1791
		ht = ws.SheetFormatPr.DefaultRowHeight
1792 1793
		customHeight = true
	}
H
Harris 已提交
1794
	if rowCount > 0 {
1795
		sizeHint = len(ws.SheetData.Row[rowCount-1].C)
H
Harris 已提交
1796
	}
1797 1798 1799
	if rowCount < row {
		// append missing rows
		for rowIdx := rowCount; rowIdx < row; rowIdx++ {
1800
			ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: rowIdx + 1, CustomHeight: customHeight, Ht: ht, C: make([]xlsxC, 0, sizeHint)})
1801 1802
		}
	}
1803
	rowData := &ws.SheetData.Row[row-1]
1804 1805 1806
	fillColumns(rowData, col, row)
}

1807
// fillColumns fill cells in the column of the row as contiguous.
1808 1809 1810 1811 1812 1813
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})
1814 1815 1816
		}
	}
}
1817

1818
// makeContiguousColumns make columns in specific rows as contiguous.
1819
func makeContiguousColumns(ws *xlsxWorksheet, fromRow, toRow, colCount int) {
1820 1821
	ws.Lock()
	defer ws.Unlock()
1822
	for ; fromRow < toRow; fromRow++ {
1823
		rowData := &ws.SheetData.Row[fromRow-1]
1824 1825 1826
		fillColumns(rowData, colCount, fromRow)
	}
}