sheet.go 22.7 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"
xurime's avatar
xurime 已提交
13 14
)

xurime's avatar
xurime 已提交
15 16
// NewSheet provides function to create a new sheet by given index, when
// creating a new XLSX file, the default sheet will be create, when you create a
17 18 19
// new file.
func (f *File) NewSheet(name string) int {
	f.SheetCount++
xurime's avatar
xurime 已提交
20
	// Update docProps/app.xml
21
	f.setAppXML()
xurime's avatar
xurime 已提交
22
	// Update [Content_Types].xml
23
	f.setContentTypes(f.SheetCount)
xurime's avatar
xurime 已提交
24
	// Create new sheet /xl/worksheets/sheet%d.xml
25
	f.setSheet(f.SheetCount, name)
xurime's avatar
xurime 已提交
26
	// Update xl/_rels/workbook.xml.rels
27
	rID := f.addXlsxWorkbookRels(f.SheetCount)
xurime's avatar
xurime 已提交
28
	// Update xl/workbook.xml
xurime's avatar
xurime 已提交
29
	f.setWorkbook(name, rID)
30
	return f.SheetCount
xurime's avatar
xurime 已提交
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
}

// contentTypesReader provides function to get the pointer to the
// [Content_Types].xml structure after deserialization.
func (f *File) contentTypesReader() *xlsxTypes {
	if f.ContentTypes == nil {
		var content xlsxTypes
		xml.Unmarshal([]byte(f.readXML("[Content_Types].xml")), &content)
		f.ContentTypes = &content
	}
	return f.ContentTypes
}

// contentTypesWriter provides function to save [Content_Types].xml after
// serialize structure.
func (f *File) contentTypesWriter() {
	if f.ContentTypes != nil {
		output, _ := xml.Marshal(f.ContentTypes)
		f.saveFileList("[Content_Types].xml", string(output))
	}
}

// workbookReader provides function to get the pointer to the xl/workbook.xml
// structure after deserialization.
func (f *File) workbookReader() *xlsxWorkbook {
	if f.WorkBook == nil {
		var content xlsxWorkbook
		xml.Unmarshal([]byte(f.readXML("xl/workbook.xml")), &content)
		f.WorkBook = &content
	}
	return f.WorkBook
}

// workbookWriter provides function to save xl/workbook.xml after serialize
// structure.
func (f *File) workbookWriter() {
	if f.WorkBook != nil {
		output, _ := xml.Marshal(f.WorkBook)
		f.saveFileList("xl/workbook.xml", replaceRelationshipsNameSpace(string(output)))
	}
}

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

91 92 93 94 95 96 97 98 99 100 101 102
// trimCell provides function to trim blank cells which created by completeCol.
func trimCell(column []xlsxC) []xlsxC {
	col := []xlsxC{}
	for _, c := range column {
		if c.S == 0 && c.V == "" && c.F == nil && c.T == "" {
			continue
		}
		col = append(col, c)
	}
	return col
}

xurime's avatar
xurime 已提交
103
// Read and update property of contents type of XLSX.
104
func (f *File) setContentTypes(index int) {
xurime's avatar
xurime 已提交
105
	content := f.contentTypesReader()
xurime's avatar
xurime 已提交
106
	content.Overrides = append(content.Overrides, xlsxOverride{
107 108
		PartName:    "/xl/worksheets/sheet" + strconv.Itoa(index) + ".xml",
		ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
xurime's avatar
xurime 已提交
109 110 111
	})
}

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

124 125
// setWorkbook update workbook property of XLSX. Maximum 31 characters are
// allowed in sheet title.
126
func (f *File) setWorkbook(name string, rid int) {
xurime's avatar
xurime 已提交
127
	content := f.workbookReader()
xurime's avatar
xurime 已提交
128
	content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{
129
		Name:    trimSheetName(name),
130
		SheetID: strconv.Itoa(rid),
131
		ID:      "rId" + strconv.Itoa(rid),
xurime's avatar
xurime 已提交
132
	})
xurime's avatar
xurime 已提交
133 134 135 136 137 138 139 140 141
}

// workbookRelsReader provides function to read and unmarshal workbook
// relationships of XLSX file.
func (f *File) workbookRelsReader() *xlsxWorkbookRels {
	if f.WorkBookRels == nil {
		var content xlsxWorkbookRels
		xml.Unmarshal([]byte(f.readXML("xl/_rels/workbook.xml.rels")), &content)
		f.WorkBookRels = &content
xurime's avatar
xurime 已提交
142
	}
xurime's avatar
xurime 已提交
143
	return f.WorkBookRels
xurime's avatar
xurime 已提交
144 145
}

xurime's avatar
xurime 已提交
146 147 148 149 150 151 152
// workbookRelsWriter provides function to save xl/_rels/workbook.xml.rels after
// serialize structure.
func (f *File) workbookRelsWriter() {
	if f.WorkBookRels != nil {
		output, _ := xml.Marshal(f.WorkBookRels)
		f.saveFileList("xl/_rels/workbook.xml.rels", string(output))
	}
xurime's avatar
xurime 已提交
153 154
}

155
// addXlsxWorkbookRels update workbook relationships property of XLSX.
156
func (f *File) addXlsxWorkbookRels(sheet int) int {
xurime's avatar
xurime 已提交
157
	content := f.workbookRelsReader()
158 159 160 161 162 163 164 165
	rID := 0
	for _, v := range content.Relationships {
		t, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
		if t > rID {
			rID = t
		}
	}
	rID++
166
	ID := bytes.Buffer{}
167
	ID.WriteString("rId")
168 169
	ID.WriteString(strconv.Itoa(rID))
	target := bytes.Buffer{}
170
	target.WriteString("worksheets/sheet")
171
	target.WriteString(strconv.Itoa(sheet))
172
	target.WriteString(".xml")
xurime's avatar
xurime 已提交
173
	content.Relationships = append(content.Relationships, xlsxWorkbookRelation{
174 175
		ID:     ID.String(),
		Target: target.String(),
176
		Type:   SourceRelationshipWorkSheet,
xurime's avatar
xurime 已提交
177
	})
178
	return rID
xurime's avatar
xurime 已提交
179 180
}

181
// setAppXML update docProps/app.xml file of XML.
182
func (f *File) setAppXML() {
183
	f.saveFileList("docProps/app.xml", templateDocpropsApp)
xurime's avatar
xurime 已提交
184 185
}

186 187 188 189 190 191 192
// 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.
xurime's avatar
xurime 已提交
193 194
func replaceRelationshipsNameSpace(workbookMarshal string) string {
	oldXmlns := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
195
	newXmlns := `<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">`
xurime's avatar
xurime 已提交
196 197 198
	return strings.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
}

199
// SetActiveSheet provides function to set default active sheet of XLSX by given
200
// index.
201
func (f *File) SetActiveSheet(index int) {
xurime's avatar
xurime 已提交
202 203 204
	if index < 1 {
		index = 1
	}
xurime's avatar
xurime 已提交
205
	index--
xurime's avatar
xurime 已提交
206
	content := f.workbookReader()
xurime's avatar
xurime 已提交
207 208 209 210 211 212 213
	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 已提交
214
	index++
215 216 217
	for idx, name := range f.GetSheetMap() {
		xlsx := f.workSheetReader(name)
		if index == idx {
xurime's avatar
xurime 已提交
218 219 220 221 222 223 224 225 226 227 228 229 230
			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
			}
		}
	}
231
	return
xurime's avatar
xurime 已提交
232 233
}

234 235
// GetActiveSheetIndex provides function to get active sheet of XLSX. If not
// found the active sheet will be return integer 0.
236 237
func (f *File) GetActiveSheetIndex() int {
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
238
	content := f.workbookReader()
239 240
	for _, v := range content.Sheets.Sheet {
		xlsx := xlsxWorksheet{}
241 242 243
		buffer.WriteString("xl/worksheets/sheet")
		buffer.WriteString(strings.TrimPrefix(v.ID, "rId"))
		buffer.WriteString(".xml")
244 245 246
		xml.Unmarshal([]byte(f.readXML(buffer.String())), &xlsx)
		for _, sheetView := range xlsx.SheetViews.SheetView {
			if sheetView.TabSelected {
247 248
				ID, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
				return ID
249
			}
250
		}
251
		buffer.Reset()
252
	}
253
	return 0
254 255
}

256 257
// SetSheetName provides function to set the worksheet name be given old and new
// worksheet name. Maximum 31 characters are allowed in sheet title and this
258 259 260
// 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.
261
func (f *File) SetSheetName(oldName, newName string) {
262 263
	oldName = trimSheetName(oldName)
	newName = trimSheetName(newName)
xurime's avatar
xurime 已提交
264
	content := f.workbookReader()
265 266 267
	for k, v := range content.Sheets.Sheet {
		if v.Name == oldName {
			content.Sheets.Sheet[k].Name = newName
268 269
			f.sheetMap[newName] = f.sheetMap[oldName]
			delete(f.sheetMap, oldName)
270 271 272 273
		}
	}
}

274 275 276
// GetSheetName provides function to get worksheet name of XLSX by given
// worksheet index. If given sheet index is invalid, will return an empty
// string.
277
func (f *File) GetSheetName(index int) string {
xurime's avatar
xurime 已提交
278
	content := f.workbookReader()
279 280 281 282 283 284 285 286 287
	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
				}
			}
288 289
		}
	}
290
	return ""
291 292
}

293
// GetSheetIndex provides function to get worksheet index of XLSX by given sheet
294 295
// name. If given worksheet name is invalid, will return an integer type value
// 0.
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
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
}

312 313
// GetSheetMap provides function to get worksheet name and index map of XLSX.
// For example:
314
//
315
//    xlsx, err := excelize.OpenFile("./Workbook.xlsx")
316 317 318 319
//    if err != nil {
//        fmt.Println(err)
//        os.Exit(1)
//    }
320 321
//    for index, name := range xlsx.GetSheetMap() {
//        fmt.Println(index, name)
322 323 324
//    }
//
func (f *File) GetSheetMap() map[int]string {
xurime's avatar
xurime 已提交
325
	content := f.workbookReader()
326
	rels := f.workbookRelsReader()
327 328
	sheetMap := map[int]string{}
	for _, v := range content.Sheets.Sheet {
329 330 331 332 333 334
		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
			}
		}
335 336
	}
	return sheetMap
xurime's avatar
xurime 已提交
337
}
338

339 340 341 342 343 344 345 346 347 348
// getSheetMap provides function to get worksheet name and XML file path map of
// 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 已提交
349 350
// SetSheetBackground provides function to set background picture by given
// worksheet name.
351 352 353 354 355 356
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
	}
357
	ext, ok := supportImageTypes[path.Ext(picture)]
358 359 360 361 362 363 364 365 366 367
	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
}
368

369
// DeleteSheet provides function to delete worksheet in a workbook by given
370
// worksheet name. Use this method with caution, which will affect changes in
371 372 373 374
// 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 已提交
375
	content := f.workbookReader()
376
	for k, v := range content.Sheets.Sheet {
377
		if v.Name != trimSheetName(name) || len(content.Sheets.Sheet) < 2 {
378 379 380 381 382
			continue
		}
		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"
383 384
		target := f.deleteSheetFromWorkbookRels(v.ID)
		f.deleteSheetFromContentTypes(target)
385 386 387 388
		delete(f.sheetMap, name)
		delete(f.XLSX, sheet)
		delete(f.XLSX, rels)
		delete(f.Sheet, sheet)
xurime's avatar
xurime 已提交
389
		f.SheetCount--
390 391 392
	}
}

393
// deleteSheetFromWorkbookRels provides function to remove worksheet
394 395
// relationships by given relationships ID in the file
// xl/_rels/workbook.xml.rels.
396
func (f *File) deleteSheetFromWorkbookRels(rID string) string {
xurime's avatar
xurime 已提交
397
	content := f.workbookRelsReader()
398 399 400 401 402 403 404 405 406 407
	for k, v := range content.Relationships {
		if v.ID != rID {
			continue
		}
		content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...)
		return v.Target
	}
	return ""
}

408
// deleteSheetFromContentTypes provides function to remove worksheet
409
// relationships by given target name in the file [Content_Types].xml.
410
func (f *File) deleteSheetFromContentTypes(target string) {
xurime's avatar
xurime 已提交
411
	content := f.contentTypesReader()
412 413 414 415 416 417 418
	for k, v := range content.Overrides {
		if v.PartName != "/xl/"+target {
			continue
		}
		content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
	}
}
419 420 421 422 423 424

// CopySheet provides function to duplicate a worksheet by gave source and
// target worksheet index. Note that currently doesn't support duplicate
// workbooks that contain tables, charts or pictures. For Example:
//
//    // Sheet1 already exists...
425 426
//    index := xlsx.NewSheet("Sheet2")
//    err := xlsx.CopySheet(1, index)
427 428 429 430 431 432 433 434 435 436 437 438 439 440
//    if err != nil {
//        fmt.Println(err)
//        os.Exit(1)
//    }
//
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")
	}
	f.copySheet(from, to)
	return nil
}

// copySheet provides function to duplicate a worksheet by gave source and
441
// target worksheet name.
442 443
func (f *File) copySheet(from, to int) {
	sheet := f.workSheetReader("sheet" + strconv.Itoa(from))
444 445
	worksheet := xlsxWorksheet{}
	deepCopy(&worksheet, &sheet)
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
	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
	f.Sheet[path] = &worksheet
	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]
	}
}
461

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

xurime's avatar
xurime 已提交
504 505 506 507 508 509 510 511
// parseFormatPanesSet provides function to parse the panes settings.
func parseFormatPanesSet(formatSet string) *formatPanes {
	format := formatPanes{}
	json.Unmarshal([]byte(formatSet), &format)
	return &format
}

// SetPanes provides function to create and remove freeze panes and split panes
512
// by given worksheet name and panes format set.
xurime's avatar
xurime 已提交
513 514 515 516 517 518 519 520 521 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 550 551 552
//
// 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
//                                    |
//                                    | Top right pane, when both vertical and horizontal
//                                    | splits are applied.
//                                    |
//    topRight (Top Right Pane)       | This value is also used when only a vertical split has
//                                    | 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.
//
553
// Pane state type is restricted to the values supported currently listed in the following table:
xurime's avatar
xurime 已提交
554 555 556 557 558 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 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
//
//     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
// Sheet1!A16:
//
//    xlsx.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"topLeftCell":"B1","activePane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`)
//
// An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell
// on Sheet1!A11:
//
//    xlsx.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"topLeftCell":"A34","activePane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`)
//
// An example of how to create split panes in the Sheet1 and set the active cell
// on Sheet1!J60:
//
//    xlsx.SetPanes("Sheet1", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"topLeftCell":"N57","activePane":"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"}]}`)
//
// 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) {
	fs := parseFormatPanesSet(panes)
	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
}

629 630 631 632 633 634
// GetSheetVisible provides function to get worksheet visible by given worksheet
// name. For example, get visible state of Sheet1:
//
//    xlsx.GetSheetVisible("Sheet1")
//
func (f *File) GetSheetVisible(name string) bool {
635
	content := f.workbookReader()
636
	visible := false
637
	for k, v := range content.Sheets.Sheet {
xurime's avatar
xurime 已提交
638
		if v.Name == trimSheetName(name) {
639 640 641
			if content.Sheets.Sheet[k].State == "" || content.Sheets.Sheet[k].State == "visible" {
				visible = true
			}
642 643
		}
	}
644
	return visible
645 646 647 648 649 650 651
}

// trimSheetName provides function to trim invaild characters by given worksheet
// name.
func trimSheetName(name string) string {
	r := strings.NewReplacer(":", "", "\\", "", "/", "", "?", "", "*", "", "[", "", "]", "")
	name = r.Replace(name)
652 653
	if utf8.RuneCountInString(name) > 31 {
		name = string([]rune(name)[0:31])
654 655 656
	}
	return name
}