sheet.go 26.6 KB
Newer Older
xurime's avatar
xurime 已提交
1 2 3 4 5 6 7 8
// Copyright 2016 - 2018 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
xurime's avatar
xurime 已提交
9

xurime's avatar
xurime 已提交
10 11 12
package excelize

import (
13
	"bytes"
xurime's avatar
xurime 已提交
14
	"encoding/json"
xurime's avatar
xurime 已提交
15
	"encoding/xml"
16
	"errors"
17
	"fmt"
18
	"io/ioutil"
19 20
	"os"
	"path"
xurime's avatar
xurime 已提交
21 22
	"strconv"
	"strings"
23
	"unicode/utf8"
24 25

	"github.com/mohae/deepcopy"
xurime's avatar
xurime 已提交
26 27
)

S
Sebastian Willing 已提交
28
// NewSheet provides function to create a new sheet by given worksheet name.
xurime's avatar
xurime 已提交
29 30
// When creating a new XLSX file, the default sheet will be created. Returns
// the number of sheets in the workbook (file) after appending the new sheet.
31
func (f *File) NewSheet(name string) int {
32 33 34 35
	// Check if the worksheet already exists
	if f.GetSheetIndex(name) != 0 {
		return f.SheetCount
	}
36
	f.SheetCount++
xurime's avatar
xurime 已提交
37
	// Update docProps/app.xml
38
	f.setAppXML()
xurime's avatar
xurime 已提交
39
	// Update [Content_Types].xml
40
	f.setContentTypes(f.SheetCount)
xurime's avatar
xurime 已提交
41
	// Create new sheet /xl/worksheets/sheet%d.xml
42
	f.setSheet(f.SheetCount, name)
xurime's avatar
xurime 已提交
43
	// Update xl/_rels/workbook.xml.rels
44
	rID := f.addXlsxWorkbookRels(f.SheetCount)
xurime's avatar
xurime 已提交
45
	// Update xl/workbook.xml
xurime's avatar
xurime 已提交
46
	f.setWorkbook(name, rID)
47
	return f.SheetCount
xurime's avatar
xurime 已提交
48 49
}

xurime's avatar
xurime 已提交
50
// contentTypesReader provides a function to get the pointer to the
xurime's avatar
xurime 已提交
51 52 53 54
// [Content_Types].xml structure after deserialization.
func (f *File) contentTypesReader() *xlsxTypes {
	if f.ContentTypes == nil {
		var content xlsxTypes
55
		_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("[Content_Types].xml")), &content)
xurime's avatar
xurime 已提交
56 57 58 59 60
		f.ContentTypes = &content
	}
	return f.ContentTypes
}

xurime's avatar
xurime 已提交
61
// contentTypesWriter provides a function to save [Content_Types].xml after
xurime's avatar
xurime 已提交
62 63 64 65
// serialize structure.
func (f *File) contentTypesWriter() {
	if f.ContentTypes != nil {
		output, _ := xml.Marshal(f.ContentTypes)
66
		f.saveFileList("[Content_Types].xml", output)
xurime's avatar
xurime 已提交
67 68 69
	}
}

xurime's avatar
xurime 已提交
70
// workbookReader provides a function to get the pointer to the xl/workbook.xml
xurime's avatar
xurime 已提交
71 72 73 74
// structure after deserialization.
func (f *File) workbookReader() *xlsxWorkbook {
	if f.WorkBook == nil {
		var content xlsxWorkbook
75
		_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/workbook.xml")), &content)
xurime's avatar
xurime 已提交
76 77 78 79 80
		f.WorkBook = &content
	}
	return f.WorkBook
}

xurime's avatar
xurime 已提交
81
// workbookWriter provides a function to save xl/workbook.xml after serialize
xurime's avatar
xurime 已提交
82 83 84 85
// structure.
func (f *File) workbookWriter() {
	if f.WorkBook != nil {
		output, _ := xml.Marshal(f.WorkBook)
86
		f.saveFileList("xl/workbook.xml", replaceRelationshipsNameSpaceBytes(output))
xurime's avatar
xurime 已提交
87 88 89
	}
}

xurime's avatar
xurime 已提交
90
// worksheetWriter provides a function to save xl/worksheets/sheet%d.xml after
xurime's avatar
xurime 已提交
91 92 93 94
// serialize structure.
func (f *File) worksheetWriter() {
	for path, sheet := range f.Sheet {
		if sheet != nil {
95 96 97
			for k, v := range sheet.SheetData.Row {
				f.Sheet[path].SheetData.Row[k].C = trimCell(v.C)
			}
xurime's avatar
xurime 已提交
98
			output, _ := xml.Marshal(sheet)
99
			f.saveFileList(path, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
100 101 102 103
			ok := f.checked[path]
			if ok {
				f.checked[path] = false
			}
xurime's avatar
xurime 已提交
104 105
		}
	}
xurime's avatar
xurime 已提交
106 107
}

xurime's avatar
xurime 已提交
108
// trimCell provides a function to trim blank cells which created by completeCol.
109
func trimCell(column []xlsxC) []xlsxC {
110 111
	col := make([]xlsxC, len(column))
	i := 0
112
	for _, c := range column {
xurime's avatar
xurime 已提交
113
		if c.S != 0 || c.V != "" || c.F != nil || c.T != "" {
114 115
			col[i] = c
			i++
116 117
		}
	}
118
	return col[0:i]
119 120
}

xurime's avatar
xurime 已提交
121
// Read and update property of contents type of XLSX.
122
func (f *File) setContentTypes(index int) {
xurime's avatar
xurime 已提交
123
	content := f.contentTypesReader()
xurime's avatar
xurime 已提交
124
	content.Overrides = append(content.Overrides, xlsxOverride{
125 126
		PartName:    "/xl/worksheets/sheet" + strconv.Itoa(index) + ".xml",
		ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
xurime's avatar
xurime 已提交
127 128 129
	})
}

xurime's avatar
xurime 已提交
130
// Update sheet property by given index.
131
func (f *File) setSheet(index int, name string) {
xurime's avatar
xurime 已提交
132
	var xlsx xlsxWorksheet
133
	xlsx.Dimension.Ref = "A1"
xurime's avatar
xurime 已提交
134
	xlsx.SheetViews.SheetView = append(xlsx.SheetViews.SheetView, xlsxSheetView{
xurime's avatar
xurime 已提交
135
		WorkbookViewID: 0,
xurime's avatar
xurime 已提交
136
	})
137
	path := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml"
138
	f.sheetMap[trimSheetName(name)] = path
xurime's avatar
xurime 已提交
139
	f.Sheet[path] = &xlsx
xurime's avatar
xurime 已提交
140 141
}

142 143
// setWorkbook update workbook property of XLSX. Maximum 31 characters are
// allowed in sheet title.
144
func (f *File) setWorkbook(name string, rid int) {
xurime's avatar
xurime 已提交
145
	content := f.workbookReader()
146 147 148 149 150 151 152 153
	rID := 0
	for _, v := range content.Sheets.Sheet {
		t, _ := strconv.Atoi(v.SheetID)
		if t > rID {
			rID = t
		}
	}
	rID++
xurime's avatar
xurime 已提交
154
	content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{
155
		Name:    trimSheetName(name),
156
		SheetID: strconv.Itoa(rID),
157
		ID:      "rId" + strconv.Itoa(rid),
xurime's avatar
xurime 已提交
158
	})
xurime's avatar
xurime 已提交
159 160
}

xurime's avatar
xurime 已提交
161
// workbookRelsReader provides a function to read and unmarshal workbook
xurime's avatar
xurime 已提交
162 163 164 165
// relationships of XLSX file.
func (f *File) workbookRelsReader() *xlsxWorkbookRels {
	if f.WorkBookRels == nil {
		var content xlsxWorkbookRels
166
		_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/_rels/workbook.xml.rels")), &content)
xurime's avatar
xurime 已提交
167
		f.WorkBookRels = &content
xurime's avatar
xurime 已提交
168
	}
xurime's avatar
xurime 已提交
169
	return f.WorkBookRels
xurime's avatar
xurime 已提交
170 171
}

xurime's avatar
xurime 已提交
172
// workbookRelsWriter provides a function to save xl/_rels/workbook.xml.rels after
xurime's avatar
xurime 已提交
173 174 175 176
// serialize structure.
func (f *File) workbookRelsWriter() {
	if f.WorkBookRels != nil {
		output, _ := xml.Marshal(f.WorkBookRels)
177
		f.saveFileList("xl/_rels/workbook.xml.rels", output)
xurime's avatar
xurime 已提交
178
	}
xurime's avatar
xurime 已提交
179 180
}

181
// addXlsxWorkbookRels update workbook relationships property of XLSX.
182
func (f *File) addXlsxWorkbookRels(sheet int) int {
xurime's avatar
xurime 已提交
183
	content := f.workbookRelsReader()
184 185 186 187 188 189 190 191
	rID := 0
	for _, v := range content.Relationships {
		t, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
		if t > rID {
			rID = t
		}
	}
	rID++
192
	ID := bytes.Buffer{}
193
	ID.WriteString("rId")
194 195
	ID.WriteString(strconv.Itoa(rID))
	target := bytes.Buffer{}
196
	target.WriteString("worksheets/sheet")
197
	target.WriteString(strconv.Itoa(sheet))
198
	target.WriteString(".xml")
xurime's avatar
xurime 已提交
199
	content.Relationships = append(content.Relationships, xlsxWorkbookRelation{
200 201
		ID:     ID.String(),
		Target: target.String(),
202
		Type:   SourceRelationshipWorkSheet,
xurime's avatar
xurime 已提交
203
	})
204
	return rID
xurime's avatar
xurime 已提交
205 206
}

207
// setAppXML update docProps/app.xml file of XML.
208
func (f *File) setAppXML() {
209
	f.saveFileList("docProps/app.xml", []byte(templateDocpropsApp))
xurime's avatar
xurime 已提交
210 211
}

212 213 214 215 216 217 218
// Some tools that read XLSX files have very strict requirements about the
// structure of the input XML. In particular both Numbers on the Mac and SAS
// dislike inline XML namespace declarations, or namespace prefixes that don't
// match the ones that Excel itself uses. This is a problem because the Go XML
// library doesn't multiple namespace declarations in a single element of a
// document. This function is a horrible hack to fix that after the XML
// marshalling is completed.
219 220 221 222 223 224
func replaceRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
	oldXmlns := []byte(`<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
	newXmlns := []byte(`<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main">`)
	return bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
}

225
// SetActiveSheet provides function to set default active worksheet of XLSX by
S
Sebastian Willing 已提交
226 227
// given index. Note that active index is different from the index returned by
// function GetSheetMap(). It should be greater than 0 and less than total
228
// worksheet numbers.
229
func (f *File) SetActiveSheet(index int) {
xurime's avatar
xurime 已提交
230 231 232
	if index < 1 {
		index = 1
	}
xurime's avatar
xurime 已提交
233
	index--
xurime's avatar
xurime 已提交
234
	content := f.workbookReader()
xurime's avatar
xurime 已提交
235 236 237 238 239 240 241
	if len(content.BookViews.WorkBookView) > 0 {
		content.BookViews.WorkBookView[0].ActiveTab = index
	} else {
		content.BookViews.WorkBookView = append(content.BookViews.WorkBookView, xlsxWorkBookView{
			ActiveTab: index,
		})
	}
xurime's avatar
xurime 已提交
242
	index++
243 244 245
	for idx, name := range f.GetSheetMap() {
		xlsx := f.workSheetReader(name)
		if index == idx {
xurime's avatar
xurime 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
			if len(xlsx.SheetViews.SheetView) > 0 {
				xlsx.SheetViews.SheetView[0].TabSelected = true
			} else {
				xlsx.SheetViews.SheetView = append(xlsx.SheetViews.SheetView, xlsxSheetView{
					TabSelected: true,
				})
			}
		} else {
			if len(xlsx.SheetViews.SheetView) > 0 {
				xlsx.SheetViews.SheetView[0].TabSelected = false
			}
		}
	}
}

xurime's avatar
xurime 已提交
261 262
// GetActiveSheetIndex provides a function to get active sheet index of the
// XLSX. If not found the active sheet will be return integer 0.
263 264
func (f *File) GetActiveSheetIndex() int {
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
265
	content := f.workbookReader()
266 267
	for _, v := range content.Sheets.Sheet {
		xlsx := xlsxWorksheet{}
268 269 270
		buffer.WriteString("xl/worksheets/sheet")
		buffer.WriteString(strings.TrimPrefix(v.ID, "rId"))
		buffer.WriteString(".xml")
271
		_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(buffer.String())), &xlsx)
272 273
		for _, sheetView := range xlsx.SheetViews.SheetView {
			if sheetView.TabSelected {
274 275
				ID, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
				return ID
276
			}
277
		}
278
		buffer.Reset()
279
	}
280
	return 0
281 282
}

xurime's avatar
xurime 已提交
283
// SetSheetName provides a function to set the worksheet name be given old and new
284
// worksheet name. Maximum 31 characters are allowed in sheet title and this
285 286 287
// 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.
288
func (f *File) SetSheetName(oldName, newName string) {
289 290
	oldName = trimSheetName(oldName)
	newName = trimSheetName(newName)
xurime's avatar
xurime 已提交
291
	content := f.workbookReader()
292 293 294
	for k, v := range content.Sheets.Sheet {
		if v.Name == oldName {
			content.Sheets.Sheet[k].Name = newName
295 296
			f.sheetMap[newName] = f.sheetMap[oldName]
			delete(f.sheetMap, oldName)
297 298 299 300
		}
	}
}

xurime's avatar
xurime 已提交
301
// GetSheetName provides a function to get worksheet name of XLSX by given
302 303
// worksheet index. If given sheet index is invalid, will return an empty
// string.
304
func (f *File) GetSheetName(index int) string {
xurime's avatar
xurime 已提交
305
	content := f.workbookReader()
306 307 308 309 310 311 312 313 314
	rels := f.workbookRelsReader()
	for _, rel := range rels.Relationships {
		rID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(rel.Target, "worksheets/sheet"), ".xml"))
		if rID == index {
			for _, v := range content.Sheets.Sheet {
				if v.ID == rel.ID {
					return v.Name
				}
			}
315 316
		}
	}
317
	return ""
318 319
}

xurime's avatar
xurime 已提交
320
// GetSheetIndex provides a function to get worksheet index of XLSX by given sheet
321 322
// name. If given worksheet name is invalid, will return an integer type value
// 0.
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
func (f *File) GetSheetIndex(name string) int {
	content := f.workbookReader()
	rels := f.workbookRelsReader()
	for _, v := range content.Sheets.Sheet {
		if v.Name == name {
			for _, rel := range rels.Relationships {
				if v.ID == rel.ID {
					rID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(rel.Target, "worksheets/sheet"), ".xml"))
					return rID
				}
			}
		}
	}
	return 0
}

xurime's avatar
xurime 已提交
339
// GetSheetMap provides a function to get worksheet name and index map of XLSX.
340
// For example:
341
//
xurime's avatar
xurime 已提交
342
//    xlsx, err := excelize.OpenFile("./Book1.xlsx")
343
//    if err != nil {
344
//        return
345
//    }
346 347
//    for index, name := range xlsx.GetSheetMap() {
//        fmt.Println(index, name)
348 349 350
//    }
//
func (f *File) GetSheetMap() map[int]string {
xurime's avatar
xurime 已提交
351
	content := f.workbookReader()
352
	rels := f.workbookRelsReader()
353 354
	sheetMap := map[int]string{}
	for _, v := range content.Sheets.Sheet {
355 356 357 358 359 360
		for _, rel := range rels.Relationships {
			if rel.ID == v.ID {
				rID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(rel.Target, "worksheets/sheet"), ".xml"))
				sheetMap[rID] = v.Name
			}
		}
361 362
	}
	return sheetMap
xurime's avatar
xurime 已提交
363
}
364

xurime's avatar
xurime 已提交
365
// getSheetMap provides a function to get worksheet name and XML file path map of
366 367 368 369 370 371 372 373 374
// XLSX.
func (f *File) getSheetMap() map[string]string {
	maps := make(map[string]string)
	for idx, name := range f.GetSheetMap() {
		maps[name] = "xl/worksheets/sheet" + strconv.Itoa(idx) + ".xml"
	}
	return maps
}

xurime's avatar
xurime 已提交
375
// SetSheetBackground provides a function to set background picture by given
376
// worksheet name and file path.
377 378 379 380 381 382
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
	}
383
	ext, ok := supportImageTypes[path.Ext(picture)]
384
	if !ok {
xurime's avatar
xurime 已提交
385
		return errors.New("unsupported image extension")
386 387 388 389
	}
	pictureID := f.countMedia() + 1
	rID := f.addSheetRelationships(sheet, SourceRelationshipImage, "../media/image"+strconv.Itoa(pictureID)+ext, "")
	f.addSheetPicture(sheet, rID)
390 391
	file, _ := ioutil.ReadFile(picture)
	f.addMedia(file, ext)
392 393 394
	f.setContentTypePartImageExtensions()
	return err
}
395

xurime's avatar
xurime 已提交
396
// DeleteSheet provides a function to delete worksheet in a workbook by given
397
// worksheet name. Use this method with caution, which will affect changes in
398 399 400 401
// references such as formulas, charts, and so on. If there is any referenced
// value of the deleted worksheet, it will cause a file error when you open it.
// This function will be invalid when only the one worksheet is left.
func (f *File) DeleteSheet(name string) {
xurime's avatar
xurime 已提交
402
	content := f.workbookReader()
403
	for k, v := range content.Sheets.Sheet {
xurime's avatar
xurime 已提交
404 405 406 407 408 409 410 411 412 413 414
		if v.Name == trimSheetName(name) && len(content.Sheets.Sheet) > 1 {
			content.Sheets.Sheet = append(content.Sheets.Sheet[:k], content.Sheets.Sheet[k+1:]...)
			sheet := "xl/worksheets/sheet" + strings.TrimPrefix(v.ID, "rId") + ".xml"
			rels := "xl/worksheets/_rels/sheet" + strings.TrimPrefix(v.ID, "rId") + ".xml.rels"
			target := f.deleteSheetFromWorkbookRels(v.ID)
			f.deleteSheetFromContentTypes(target)
			delete(f.sheetMap, name)
			delete(f.XLSX, sheet)
			delete(f.XLSX, rels)
			delete(f.Sheet, sheet)
			f.SheetCount--
415 416
		}
	}
417
	f.SetActiveSheet(len(f.GetSheetMap()))
418 419
}

xurime's avatar
xurime 已提交
420
// deleteSheetFromWorkbookRels provides a function to remove worksheet
421 422
// relationships by given relationships ID in the file
// xl/_rels/workbook.xml.rels.
423
func (f *File) deleteSheetFromWorkbookRels(rID string) string {
xurime's avatar
xurime 已提交
424
	content := f.workbookRelsReader()
425
	for k, v := range content.Relationships {
xurime's avatar
xurime 已提交
426 427 428
		if v.ID == rID {
			content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...)
			return v.Target
429 430 431 432 433
		}
	}
	return ""
}

xurime's avatar
xurime 已提交
434
// deleteSheetFromContentTypes provides a function to remove worksheet
435
// relationships by given target name in the file [Content_Types].xml.
436
func (f *File) deleteSheetFromContentTypes(target string) {
xurime's avatar
xurime 已提交
437
	content := f.contentTypesReader()
438
	for k, v := range content.Overrides {
xurime's avatar
xurime 已提交
439 440
		if v.PartName == "/xl/"+target {
			content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
441 442 443
		}
	}
}
444

xurime's avatar
xurime 已提交
445
// CopySheet provides a function to duplicate a worksheet by gave source and
446 447 448 449
// target worksheet index. Note that currently doesn't support duplicate
// workbooks that contain tables, charts or pictures. For Example:
//
//    // Sheet1 already exists...
450 451
//    index := xlsx.NewSheet("Sheet2")
//    err := xlsx.CopySheet(1, index)
452
//    return err
453 454 455
//
func (f *File) CopySheet(from, to int) error {
	if from < 1 || to < 1 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" {
xurime's avatar
xurime 已提交
456
		return errors.New("invalid worksheet index")
457
	}
458 459
	f.copySheet(from, to)
	return nil
460 461
}

xurime's avatar
xurime 已提交
462
// copySheet provides a function to duplicate a worksheet by gave source and
463
// target worksheet name.
464
func (f *File) copySheet(from, to int) {
465
	sheet := f.workSheetReader("sheet" + strconv.Itoa(from))
466
	worksheet := deepcopy.Copy(sheet).(*xlsxWorksheet)
467 468 469 470 471 472 473
	path := "xl/worksheets/sheet" + strconv.Itoa(to) + ".xml"
	if len(worksheet.SheetViews.SheetView) > 0 {
		worksheet.SheetViews.SheetView[0].TabSelected = false
	}
	worksheet.Drawing = nil
	worksheet.TableParts = nil
	worksheet.PageSetUp = nil
474
	f.Sheet[path] = worksheet
475 476 477 478 479 480 481
	toRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(to) + ".xml.rels"
	fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(from) + ".xml.rels"
	_, ok := f.XLSX[fromRels]
	if ok {
		f.XLSX[toRels] = f.XLSX[fromRels]
	}
}
482

xurime's avatar
xurime 已提交
483
// SetSheetVisible provides a function to set worksheet visible by given worksheet
484 485
// name. A workbook must contain at least one visible worksheet. If the given
// worksheet has been activated, this setting will be invalidated. Sheet state
486
// values as defined by http://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.sheetstatevalues.aspx
xurime's avatar
xurime 已提交
487 488 489 490 491
//
//    visible
//    hidden
//    veryHidden
//
492 493 494 495 496
// For example, hide Sheet1:
//
//    xlsx.SetSheetVisible("Sheet1", false)
//
func (f *File) SetSheetVisible(name string, visible bool) {
497 498
	name = trimSheetName(name)
	content := f.workbookReader()
499 500 501 502 503 504 505 506
	if visible {
		for k, v := range content.Sheets.Sheet {
			if v.Name == name {
				content.Sheets.Sheet[k].State = ""
			}
		}
		return
	}
507 508
	count := 0
	for _, v := range content.Sheets.Sheet {
509
		if v.State != "hidden" {
510 511 512 513
			count++
		}
	}
	for k, v := range content.Sheets.Sheet {
514
		xlsx := f.workSheetReader(f.GetSheetMap()[k])
515 516 517 518 519
		tabSelected := false
		if len(xlsx.SheetViews.SheetView) > 0 {
			tabSelected = xlsx.SheetViews.SheetView[0].TabSelected
		}
		if v.Name == name && count > 1 && !tabSelected {
520
			content.Sheets.Sheet[k].State = "hidden"
521 522 523 524
		}
	}
}

xurime's avatar
xurime 已提交
525
// parseFormatPanesSet provides a function to parse the panes settings.
526
func parseFormatPanesSet(formatSet string) (*formatPanes, error) {
xurime's avatar
xurime 已提交
527
	format := formatPanes{}
528 529
	err := json.Unmarshal([]byte(formatSet), &format)
	return &format, err
xurime's avatar
xurime 已提交
530 531
}

xurime's avatar
xurime 已提交
532
// SetPanes provides a function to create and remove freeze panes and split panes
533
// by given worksheet name and panes format set.
xurime's avatar
xurime 已提交
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
//
// 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 已提交
562
//    topRight (Top Right Pane)       | Top right pane, when both vertical and horizontal
xurime's avatar
xurime 已提交
563 564 565 566 567 568 569
//                                    | 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.
//
570
// Pane state type is restricted to the values supported currently listed in the following table:
xurime's avatar
xurime 已提交
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
//
//     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 已提交
599
// Sheet1!K16:
xurime's avatar
xurime 已提交
600
//
xurime's avatar
xurime 已提交
601
//    xlsx.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 已提交
602 603
//
// An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell
xurime's avatar
xurime 已提交
604
// ranges on Sheet1!A11:XFD11:
xurime's avatar
xurime 已提交
605
//
xurime's avatar
xurime 已提交
606
//    xlsx.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 已提交
607 608 609 610
//
// An example of how to create split panes in the Sheet1 and set the active cell
// on Sheet1!J60:
//
xurime's avatar
xurime 已提交
611
//    xlsx.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 已提交
612 613 614 615 616 617
//
// An example of how to unfreeze and remove all panes on Sheet1:
//
//    xlsx.SetPanes("Sheet1", `{"freeze":false,"split":false}`)
//
func (f *File) SetPanes(sheet, panes string) {
618
	fs, _ := parseFormatPanesSet(panes)
xurime's avatar
xurime 已提交
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
	xlsx := f.workSheetReader(sheet)
	p := &xlsxPane{
		ActivePane:  fs.ActivePane,
		TopLeftCell: fs.TopLeftCell,
		XSplit:      float64(fs.XSplit),
		YSplit:      float64(fs.YSplit),
	}
	if fs.Freeze {
		p.State = "frozen"
	}
	xlsx.SheetViews.SheetView[len(xlsx.SheetViews.SheetView)-1].Pane = p
	if !(fs.Freeze) && !(fs.Split) {
		if len(xlsx.SheetViews.SheetView) > 0 {
			xlsx.SheetViews.SheetView[len(xlsx.SheetViews.SheetView)-1].Pane = nil
		}
	}
	s := []*xlsxSelection{}
	for _, p := range fs.Panes {
		s = append(s, &xlsxSelection{
			ActiveCell: p.ActiveCell,
			Pane:       p.Pane,
			SQRef:      p.SQRef,
		})
	}
	xlsx.SheetViews.SheetView[len(xlsx.SheetViews.SheetView)-1].Selection = s
}

xurime's avatar
xurime 已提交
646
// GetSheetVisible provides a function to get worksheet visible by given worksheet
647 648 649 650 651
// name. For example, get visible state of Sheet1:
//
//    xlsx.GetSheetVisible("Sheet1")
//
func (f *File) GetSheetVisible(name string) bool {
652
	content := f.workbookReader()
653
	visible := false
654
	for k, v := range content.Sheets.Sheet {
xurime's avatar
xurime 已提交
655
		if v.Name == trimSheetName(name) {
656 657 658
			if content.Sheets.Sheet[k].State == "" || content.Sheets.Sheet[k].State == "visible" {
				visible = true
			}
659 660
		}
	}
661
	return visible
662 663
}

664 665 666 667 668 669 670 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 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
// SearchSheet provides a function to get coordinates by given worksheet name
// and cell value. This function only supports exact match of strings and
// numbers, doesn't support 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. For example,
// search the coordinates of the value of "100" on Sheet1:
//
//    xlsx.SearchSheet("Sheet1", "100")
//
func (f *File) SearchSheet(sheet, value string) []string {
	xlsx := f.workSheetReader(sheet)
	result := []string{}
	name, ok := f.sheetMap[trimSheetName(sheet)]
	if !ok {
		return result
	}
	if xlsx != nil {
		output, _ := xml.Marshal(f.Sheet[name])
		f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
	}
	xml.NewDecoder(bytes.NewReader(f.readXML(name)))
	d := f.sharedStringsReader()
	var inElement string
	var r xlsxRow
	decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name)))
	for {
		token, _ := decoder.Token()
		if token == nil {
			break
		}
		switch startElement := token.(type) {
		case xml.StartElement:
			inElement = startElement.Name.Local
			if inElement == "row" {
				r = xlsxRow{}
				_ = decoder.DecodeElement(&r, &startElement)
				for _, colCell := range r.C {
					val, _ := colCell.getValueFrom(f, d)
					if val != value {
						continue
					}
					result = append(result, fmt.Sprintf("%s%d", strings.Map(letterOnlyMapF, colCell.R), r.R))
				}
			}
		default:
		}
	}
	return result
}

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
// ProtectSheet provides a function to prevent other users from accidentally
// or deliberately changing, moving, or deleting data in a worksheet. For
// example protect Sheet1 with protection settings:
//
//    xlsx.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{
//        Password:      "password",
//        EditScenarios: false,
//    })
//
func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) {
	xlsx := f.workSheetReader(sheet)
	if settings == nil {
		settings = &FormatSheetProtection{
			EditObjects:       true,
			EditScenarios:     true,
			SelectLockedCells: true,
		}
	}
	xlsx.SheetProtection = &xlsxSheetProtection{
		AutoFilter:          settings.AutoFilter,
		DeleteColumns:       settings.DeleteColumns,
		DeleteRows:          settings.DeleteRows,
		FormatCells:         settings.FormatCells,
		FormatColumns:       settings.FormatColumns,
		FormatRows:          settings.FormatRows,
		InsertColumns:       settings.InsertColumns,
		InsertHyperlinks:    settings.InsertHyperlinks,
		InsertRows:          settings.InsertRows,
		Objects:             settings.EditObjects,
		PivotTables:         settings.PivotTables,
		Scenarios:           settings.EditScenarios,
		SelectLockedCells:   settings.SelectLockedCells,
		SelectUnlockedCells: settings.SelectUnlockedCells,
		Sheet:               true,
		Sort:                settings.Sort,
	}
	if settings.Password != "" {
		xlsx.SheetProtection.Password = genSheetPasswd(settings.Password)
	}
}

xurime's avatar
xurime 已提交
755
// trimSheetName provides a function to trim invaild characters by given worksheet
756 757
// name.
func trimSheetName(name string) string {
758
	r := []rune{}
759
	for _, v := range name {
760 761 762 763 764 765 766 767
		switch v {
		case 58, 92, 47, 63, 42, 91, 93: // replace :\/?*[]
			continue
		default:
			r = append(r, v)
		}
	}
	name = string(r)
768 769
	if utf8.RuneCountInString(name) > 31 {
		name = string([]rune(name)[0:31])
770 771 772
	}
	return name
}