sheet.go 23.5 KB
Newer Older
xurime's avatar
xurime 已提交
1 2 3 4 5 6 7 8
// 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.
//
// 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.
xurime's avatar
xurime 已提交
9 10 11
package excelize

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

	"github.com/mohae/deepcopy"
xurime's avatar
xurime 已提交
23 24
)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

209 210 211 212 213 214 215
// 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.
216 217 218 219 220 221
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)
}

xurime's avatar
xurime 已提交
222
// SetActiveSheet provides a function to set default active worksheet of XLSX by
223 224 225
// given index. Note that active index is different with the index that got by
// function GetSheetMap, and it should be greater than 0 and less than total
// worksheet numbers.
226
func (f *File) SetActiveSheet(index int) {
xurime's avatar
xurime 已提交
227 228 229
	if index < 1 {
		index = 1
	}
xurime's avatar
xurime 已提交
230
	index--
xurime's avatar
xurime 已提交
231
	content := f.workbookReader()
xurime's avatar
xurime 已提交
232 233 234 235 236 237 238
	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 已提交
239
	index++
240 241 242
	for idx, name := range f.GetSheetMap() {
		xlsx := f.workSheetReader(name)
		if index == idx {
xurime's avatar
xurime 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
			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 已提交
258
// GetActiveSheetIndex provides a function to get active sheet of XLSX. If not
259
// found the active sheet will be return integer 0.
260 261
func (f *File) GetActiveSheetIndex() int {
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
262
	content := f.workbookReader()
263 264
	for _, v := range content.Sheets.Sheet {
		xlsx := xlsxWorksheet{}
265 266 267
		buffer.WriteString("xl/worksheets/sheet")
		buffer.WriteString(strings.TrimPrefix(v.ID, "rId"))
		buffer.WriteString(".xml")
268
		_ = xml.Unmarshal([]byte(f.readXML(buffer.String())), &xlsx)
269 270
		for _, sheetView := range xlsx.SheetViews.SheetView {
			if sheetView.TabSelected {
271 272
				ID, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
				return ID
273
			}
274
		}
275
		buffer.Reset()
276
	}
277
	return 0
278 279
}

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

xurime's avatar
xurime 已提交
298
// GetSheetName provides a function to get worksheet name of XLSX by given
299 300
// worksheet index. If given sheet index is invalid, will return an empty
// string.
301
func (f *File) GetSheetName(index int) string {
xurime's avatar
xurime 已提交
302
	content := f.workbookReader()
303 304 305 306 307 308 309 310 311
	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
				}
			}
312 313
		}
	}
314
	return ""
315 316
}

xurime's avatar
xurime 已提交
317
// GetSheetIndex provides a function to get worksheet index of XLSX by given sheet
318 319
// name. If given worksheet name is invalid, will return an integer type value
// 0.
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
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 已提交
336
// GetSheetMap provides a function to get worksheet name and index map of XLSX.
337
// For example:
338
//
xurime's avatar
xurime 已提交
339
//    xlsx, err := excelize.OpenFile("./Book1.xlsx")
340
//    if err != nil {
341
//        return
342
//    }
343 344
//    for index, name := range xlsx.GetSheetMap() {
//        fmt.Println(index, name)
345 346 347
//    }
//
func (f *File) GetSheetMap() map[int]string {
xurime's avatar
xurime 已提交
348
	content := f.workbookReader()
349
	rels := f.workbookRelsReader()
350 351
	sheetMap := map[int]string{}
	for _, v := range content.Sheets.Sheet {
352 353 354 355 356 357
		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
			}
		}
358 359
	}
	return sheetMap
xurime's avatar
xurime 已提交
360
}
361

xurime's avatar
xurime 已提交
362
// getSheetMap provides a function to get worksheet name and XML file path map of
363 364 365 366 367 368 369 370 371
// 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 已提交
372
// SetSheetBackground provides a function to set background picture by given
xurime's avatar
xurime 已提交
373
// worksheet name.
374 375 376 377 378 379
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
	}
380
	ext, ok := supportImageTypes[path.Ext(picture)]
381
	if !ok {
xurime's avatar
xurime 已提交
382
		return errors.New("unsupported image extension")
383 384 385 386 387 388 389 390
	}
	pictureID := f.countMedia() + 1
	rID := f.addSheetRelationships(sheet, SourceRelationshipImage, "../media/image"+strconv.Itoa(pictureID)+ext, "")
	f.addSheetPicture(sheet, rID)
	f.addMedia(picture, ext)
	f.setContentTypePartImageExtensions()
	return err
}
391

xurime's avatar
xurime 已提交
392
// DeleteSheet provides a function to delete worksheet in a workbook by given
393
// worksheet name. Use this method with caution, which will affect changes in
394 395 396 397
// 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 已提交
398
	content := f.workbookReader()
399
	for k, v := range content.Sheets.Sheet {
xurime's avatar
xurime 已提交
400 401 402 403 404 405 406 407 408 409 410
		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--
411 412
		}
	}
413
	f.SetActiveSheet(len(f.GetSheetMap()))
414 415
}

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

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

xurime's avatar
xurime 已提交
441
// CopySheet provides a function to duplicate a worksheet by gave source and
442 443 444 445
// target worksheet index. Note that currently doesn't support duplicate
// workbooks that contain tables, charts or pictures. For Example:
//
//    // Sheet1 already exists...
446 447
//    index := xlsx.NewSheet("Sheet2")
//    err := xlsx.CopySheet(1, index)
448
//    return err
449 450 451
//
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 已提交
452
		return errors.New("invalid worksheet index")
453
	}
454 455
	f.copySheet(from, to)
	return nil
456 457
}

xurime's avatar
xurime 已提交
458
// copySheet provides a function to duplicate a worksheet by gave source and
459
// target worksheet name.
460
func (f *File) copySheet(from, to int) {
461
	sheet := f.workSheetReader("sheet" + strconv.Itoa(from))
462
	worksheet := deepcopy.Copy(sheet).(*xlsxWorksheet)
463 464 465 466 467 468 469
	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
470
	f.Sheet[path] = worksheet
471 472 473 474 475 476 477
	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]
	}
}
478

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

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

xurime's avatar
xurime 已提交
528
// SetPanes provides a function to create and remove freeze panes and split panes
529
// by given worksheet name and panes format set.
xurime's avatar
xurime 已提交
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
//
// 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 已提交
558
//    topRight (Top Right Pane)       | Top right pane, when both vertical and horizontal
xurime's avatar
xurime 已提交
559 560 561 562 563 564 565
//                                    | 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.
//
566
// Pane state type is restricted to the values supported currently listed in the following table:
xurime's avatar
xurime 已提交
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
//
//     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 已提交
595
// Sheet1!K16:
xurime's avatar
xurime 已提交
596
//
xurime's avatar
xurime 已提交
597
//    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 已提交
598 599
//
// An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell
xurime's avatar
xurime 已提交
600
// ranges on Sheet1!A11:XFD11:
xurime's avatar
xurime 已提交
601
//
xurime's avatar
xurime 已提交
602
//    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 已提交
603 604 605 606
//
// An example of how to create split panes in the Sheet1 and set the active cell
// on Sheet1!J60:
//
xurime's avatar
xurime 已提交
607
//    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 已提交
608 609 610 611 612 613
//
// 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) {
614
	fs, _ := parseFormatPanesSet(panes)
xurime's avatar
xurime 已提交
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
	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 已提交
642
// GetSheetVisible provides a function to get worksheet visible by given worksheet
643 644 645 646 647
// name. For example, get visible state of Sheet1:
//
//    xlsx.GetSheetVisible("Sheet1")
//
func (f *File) GetSheetVisible(name string) bool {
648
	content := f.workbookReader()
649
	visible := false
650
	for k, v := range content.Sheets.Sheet {
xurime's avatar
xurime 已提交
651
		if v.Name == trimSheetName(name) {
652 653 654
			if content.Sheets.Sheet[k].State == "" || content.Sheets.Sheet[k].State == "visible" {
				visible = true
			}
655 656
		}
	}
657
	return visible
658 659
}

xurime's avatar
xurime 已提交
660
// trimSheetName provides a function to trim invaild characters by given worksheet
661 662
// name.
func trimSheetName(name string) string {
663
	r := []rune{}
664
	for _, v := range name {
665 666 667 668 669 670 671 672
		switch v {
		case 58, 92, 47, 63, 42, 91, 93: // replace :\/?*[]
			continue
		default:
			r = append(r, v)
		}
	}
	name = string(r)
673 674
	if utf8.RuneCountInString(name) > 31 {
		name = string([]rune(name)[0:31])
675 676 677
	}
	return name
}