sheet.go 23.0 KB
Newer Older
xurime's avatar
xurime 已提交
1 2 3
package excelize

import (
4
	"bytes"
xurime's avatar
xurime 已提交
5
	"encoding/json"
xurime's avatar
xurime 已提交
6
	"encoding/xml"
7 8 9
	"errors"
	"os"
	"path"
xurime's avatar
xurime 已提交
10 11
	"strconv"
	"strings"
12
	"unicode/utf8"
13 14

	"github.com/mohae/deepcopy"
xurime's avatar
xurime 已提交
15 16
)

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

xurime's avatar
xurime 已提交
39
// contentTypesReader provides a function to get the pointer to the
xurime's avatar
xurime 已提交
40 41 42 43
// [Content_Types].xml structure after deserialization.
func (f *File) contentTypesReader() *xlsxTypes {
	if f.ContentTypes == nil {
		var content xlsxTypes
44
		_ = xml.Unmarshal([]byte(f.readXML("[Content_Types].xml")), &content)
xurime's avatar
xurime 已提交
45 46 47 48 49
		f.ContentTypes = &content
	}
	return f.ContentTypes
}

xurime's avatar
xurime 已提交
50
// contentTypesWriter provides a function to save [Content_Types].xml after
xurime's avatar
xurime 已提交
51 52 53 54
// serialize structure.
func (f *File) contentTypesWriter() {
	if f.ContentTypes != nil {
		output, _ := xml.Marshal(f.ContentTypes)
55
		f.saveFileList("[Content_Types].xml", output)
xurime's avatar
xurime 已提交
56 57 58
	}
}

xurime's avatar
xurime 已提交
59
// workbookReader provides a function to get the pointer to the xl/workbook.xml
xurime's avatar
xurime 已提交
60 61 62 63
// structure after deserialization.
func (f *File) workbookReader() *xlsxWorkbook {
	if f.WorkBook == nil {
		var content xlsxWorkbook
64
		_ = xml.Unmarshal([]byte(f.readXML("xl/workbook.xml")), &content)
xurime's avatar
xurime 已提交
65 66 67 68 69
		f.WorkBook = &content
	}
	return f.WorkBook
}

xurime's avatar
xurime 已提交
70
// workbookWriter provides a function to save xl/workbook.xml after serialize
xurime's avatar
xurime 已提交
71 72 73 74
// structure.
func (f *File) workbookWriter() {
	if f.WorkBook != nil {
		output, _ := xml.Marshal(f.WorkBook)
75
		f.saveFileList("xl/workbook.xml", replaceRelationshipsNameSpaceBytes(output))
xurime's avatar
xurime 已提交
76 77 78
	}
}

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

xurime's avatar
xurime 已提交
97
// trimCell provides a function to trim blank cells which created by completeCol.
98
func trimCell(column []xlsxC) []xlsxC {
99 100
	col := make([]xlsxC, len(column))
	i := 0
101
	for _, c := range column {
xurime's avatar
xurime 已提交
102
		if c.S != 0 || c.V != "" || c.F != nil || c.T != "" {
103 104
			col[i] = c
			i++
105 106
		}
	}
107
	return col[0:i]
108 109
}

xurime's avatar
xurime 已提交
110
// Read and update property of contents type of XLSX.
111
func (f *File) setContentTypes(index int) {
xurime's avatar
xurime 已提交
112
	content := f.contentTypesReader()
xurime's avatar
xurime 已提交
113
	content.Overrides = append(content.Overrides, xlsxOverride{
114 115
		PartName:    "/xl/worksheets/sheet" + strconv.Itoa(index) + ".xml",
		ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
xurime's avatar
xurime 已提交
116 117 118
	})
}

xurime's avatar
xurime 已提交
119
// Update sheet property by given index.
120
func (f *File) setSheet(index int, name string) {
xurime's avatar
xurime 已提交
121
	var xlsx xlsxWorksheet
122
	xlsx.Dimension.Ref = "A1"
xurime's avatar
xurime 已提交
123
	xlsx.SheetViews.SheetView = append(xlsx.SheetViews.SheetView, xlsxSheetView{
xurime's avatar
xurime 已提交
124
		WorkbookViewID: 0,
xurime's avatar
xurime 已提交
125
	})
126
	path := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml"
127
	f.sheetMap[trimSheetName(name)] = path
xurime's avatar
xurime 已提交
128
	f.Sheet[path] = &xlsx
xurime's avatar
xurime 已提交
129 130
}

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

xurime's avatar
xurime 已提交
150
// workbookRelsReader provides a function to read and unmarshal workbook
xurime's avatar
xurime 已提交
151 152 153 154
// relationships of XLSX file.
func (f *File) workbookRelsReader() *xlsxWorkbookRels {
	if f.WorkBookRels == nil {
		var content xlsxWorkbookRels
155
		_ = xml.Unmarshal([]byte(f.readXML("xl/_rels/workbook.xml.rels")), &content)
xurime's avatar
xurime 已提交
156
		f.WorkBookRels = &content
xurime's avatar
xurime 已提交
157
	}
xurime's avatar
xurime 已提交
158
	return f.WorkBookRels
xurime's avatar
xurime 已提交
159 160
}

xurime's avatar
xurime 已提交
161
// workbookRelsWriter provides a function to save xl/_rels/workbook.xml.rels after
xurime's avatar
xurime 已提交
162 163 164 165
// serialize structure.
func (f *File) workbookRelsWriter() {
	if f.WorkBookRels != nil {
		output, _ := xml.Marshal(f.WorkBookRels)
166
		f.saveFileList("xl/_rels/workbook.xml.rels", output)
xurime's avatar
xurime 已提交
167
	}
xurime's avatar
xurime 已提交
168 169
}

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

196
// setAppXML update docProps/app.xml file of XML.
197
func (f *File) setAppXML() {
198
	f.saveFileList("docProps/app.xml", []byte(templateDocpropsApp))
xurime's avatar
xurime 已提交
199 200
}

201 202 203 204 205 206 207
// 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.
208 209 210 211 212 213
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 已提交
214
// SetActiveSheet provides a function to set default active worksheet of XLSX by
215 216 217
// 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.
218
func (f *File) SetActiveSheet(index int) {
xurime's avatar
xurime 已提交
219 220 221
	if index < 1 {
		index = 1
	}
xurime's avatar
xurime 已提交
222
	index--
xurime's avatar
xurime 已提交
223
	content := f.workbookReader()
xurime's avatar
xurime 已提交
224 225 226 227 228 229 230
	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 已提交
231
	index++
232 233 234
	for idx, name := range f.GetSheetMap() {
		xlsx := f.workSheetReader(name)
		if index == idx {
xurime's avatar
xurime 已提交
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
			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 已提交
250
// GetActiveSheetIndex provides a function to get active sheet of XLSX. If not
251
// found the active sheet will be return integer 0.
252 253
func (f *File) GetActiveSheetIndex() int {
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
254
	content := f.workbookReader()
255 256
	for _, v := range content.Sheets.Sheet {
		xlsx := xlsxWorksheet{}
257 258 259
		buffer.WriteString("xl/worksheets/sheet")
		buffer.WriteString(strings.TrimPrefix(v.ID, "rId"))
		buffer.WriteString(".xml")
260
		_ = xml.Unmarshal([]byte(f.readXML(buffer.String())), &xlsx)
261 262
		for _, sheetView := range xlsx.SheetViews.SheetView {
			if sheetView.TabSelected {
263 264
				ID, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
				return ID
265
			}
266
		}
267
		buffer.Reset()
268
	}
269
	return 0
270 271
}

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

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

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

xurime's avatar
xurime 已提交
354
// getSheetMap provides a function to get worksheet name and XML file path map of
355 356 357 358 359 360 361 362 363
// 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 已提交
364
// SetSheetBackground provides a function to set background picture by given
xurime's avatar
xurime 已提交
365
// worksheet name.
366 367 368 369 370 371
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
	}
372
	ext, ok := supportImageTypes[path.Ext(picture)]
373 374 375 376 377 378 379 380 381 382
	if !ok {
		return errors.New("Unsupported image extension")
	}
	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
}
383

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

xurime's avatar
xurime 已提交
408
// deleteSheetFromWorkbookRels provides a function to remove worksheet
409 410
// relationships by given relationships ID in the file
// xl/_rels/workbook.xml.rels.
411
func (f *File) deleteSheetFromWorkbookRels(rID string) string {
xurime's avatar
xurime 已提交
412
	content := f.workbookRelsReader()
413
	for k, v := range content.Relationships {
xurime's avatar
xurime 已提交
414 415 416
		if v.ID == rID {
			content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...)
			return v.Target
417 418 419 420 421
		}
	}
	return ""
}

xurime's avatar
xurime 已提交
422
// deleteSheetFromContentTypes provides a function to remove worksheet
423
// relationships by given target name in the file [Content_Types].xml.
424
func (f *File) deleteSheetFromContentTypes(target string) {
xurime's avatar
xurime 已提交
425
	content := f.contentTypesReader()
426
	for k, v := range content.Overrides {
xurime's avatar
xurime 已提交
427 428
		if v.PartName == "/xl/"+target {
			content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
429 430 431
		}
	}
}
432

xurime's avatar
xurime 已提交
433
// CopySheet provides a function to duplicate a worksheet by gave source and
434 435 436 437
// target worksheet index. Note that currently doesn't support duplicate
// workbooks that contain tables, charts or pictures. For Example:
//
//    // Sheet1 already exists...
438 439
//    index := xlsx.NewSheet("Sheet2")
//    err := xlsx.CopySheet(1, index)
440
//    return err
441 442 443 444 445
//
func (f *File) CopySheet(from, to int) error {
	if from < 1 || to < 1 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" {
		return errors.New("Invalid worksheet index")
	}
446 447
	f.copySheet(from, to)
	return nil
448 449
}

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

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

xurime's avatar
xurime 已提交
513
// parseFormatPanesSet provides a function to parse the panes settings.
514
func parseFormatPanesSet(formatSet string) (*formatPanes, error) {
xurime's avatar
xurime 已提交
515
	format := formatPanes{}
516 517
	err := json.Unmarshal([]byte(formatSet), &format)
	return &format, err
xurime's avatar
xurime 已提交
518 519
}

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

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