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

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

xurime's avatar
xurime 已提交
13 14 15
// 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
// new file, you need to ensure that the index is continuous.
16
func (f *File) NewSheet(index int, name string) {
xurime's avatar
xurime 已提交
17
	// Update docProps/app.xml
18
	f.setAppXML()
xurime's avatar
xurime 已提交
19
	// Update [Content_Types].xml
20
	f.setContentTypes(index)
xurime's avatar
xurime 已提交
21
	// Create new sheet /xl/worksheets/sheet%d.xml
22
	f.setSheet(index)
xurime's avatar
xurime 已提交
23
	// Update xl/_rels/workbook.xml.rels
xurime's avatar
xurime 已提交
24
	rID := f.addXlsxWorkbookRels(index)
xurime's avatar
xurime 已提交
25
	// Update xl/workbook.xml
xurime's avatar
xurime 已提交
26
	f.setWorkbook(name, rID)
xurime's avatar
xurime 已提交
27 28 29 30 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 78
	f.SheetCount++
}

// 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 {
			output, _ := xml.Marshal(sheet)
			f.saveFileList(path, replaceWorkSheetsRelationshipsNameSpace(string(output)))
		}
	}
xurime's avatar
xurime 已提交
79 80
}

xurime's avatar
xurime 已提交
81
// Read and update property of contents type of XLSX.
82
func (f *File) setContentTypes(index int) {
xurime's avatar
xurime 已提交
83
	content := f.contentTypesReader()
xurime's avatar
xurime 已提交
84
	content.Overrides = append(content.Overrides, xlsxOverride{
85 86
		PartName:    "/xl/worksheets/sheet" + strconv.Itoa(index) + ".xml",
		ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
xurime's avatar
xurime 已提交
87 88 89
	})
}

xurime's avatar
xurime 已提交
90
// Update sheet property by given index.
91
func (f *File) setSheet(index int) {
xurime's avatar
xurime 已提交
92
	var xlsx xlsxWorksheet
93
	xlsx.Dimension.Ref = "A1"
xurime's avatar
xurime 已提交
94
	xlsx.SheetViews.SheetView = append(xlsx.SheetViews.SheetView, xlsxSheetView{
xurime's avatar
xurime 已提交
95
		WorkbookViewID: 0,
xurime's avatar
xurime 已提交
96
	})
97
	path := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml"
xurime's avatar
xurime 已提交
98
	f.Sheet[path] = &xlsx
xurime's avatar
xurime 已提交
99 100
}

101 102
// setWorkbook update workbook property of XLSX. Maximum 31 characters are
// allowed in sheet title.
103
func (f *File) setWorkbook(name string, rid int) {
104 105
	r := strings.NewReplacer(":", "", "\\", "", "/", "", "?", "", "*", "", "[", "", "]", "")
	name = r.Replace(name)
106 107 108
	if len(name) > 31 {
		name = name[0:31]
	}
xurime's avatar
xurime 已提交
109
	content := f.workbookReader()
xurime's avatar
xurime 已提交
110 111
	content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{
		Name:    name,
112
		SheetID: strconv.Itoa(rid),
113
		ID:      "rId" + strconv.Itoa(rid),
xurime's avatar
xurime 已提交
114
	})
xurime's avatar
xurime 已提交
115 116 117 118 119 120 121 122 123
}

// 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 已提交
124
	}
xurime's avatar
xurime 已提交
125
	return f.WorkBookRels
xurime's avatar
xurime 已提交
126 127
}

xurime's avatar
xurime 已提交
128 129 130 131 132 133 134
// 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 已提交
135 136
}

137
// addXlsxWorkbookRels update workbook relationships property of XLSX.
138
func (f *File) addXlsxWorkbookRels(sheet int) int {
xurime's avatar
xurime 已提交
139
	content := f.workbookRelsReader()
140 141 142 143 144 145 146 147
	rID := 0
	for _, v := range content.Relationships {
		t, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
		if t > rID {
			rID = t
		}
	}
	rID++
148
	ID := bytes.Buffer{}
149
	ID.WriteString("rId")
150 151
	ID.WriteString(strconv.Itoa(rID))
	target := bytes.Buffer{}
152
	target.WriteString("worksheets/sheet")
153
	target.WriteString(strconv.Itoa(sheet))
154
	target.WriteString(".xml")
xurime's avatar
xurime 已提交
155
	content.Relationships = append(content.Relationships, xlsxWorkbookRelation{
156 157
		ID:     ID.String(),
		Target: target.String(),
158
		Type:   SourceRelationshipWorkSheet,
xurime's avatar
xurime 已提交
159
	})
160
	return rID
xurime's avatar
xurime 已提交
161 162
}

163
// setAppXML update docProps/app.xml file of XML.
164
func (f *File) setAppXML() {
165
	f.saveFileList("docProps/app.xml", templateDocpropsApp)
xurime's avatar
xurime 已提交
166 167
}

168 169 170 171 172 173 174
// 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 已提交
175 176
func replaceRelationshipsNameSpace(workbookMarshal string) string {
	oldXmlns := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
177
	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 已提交
178 179 180
	return strings.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
}

181
// SetActiveSheet provides function to set default active sheet of XLSX by given
182
// index.
183
func (f *File) SetActiveSheet(index int) {
xurime's avatar
xurime 已提交
184 185 186
	if index < 1 {
		index = 1
	}
xurime's avatar
xurime 已提交
187
	index--
xurime's avatar
xurime 已提交
188
	content := f.workbookReader()
xurime's avatar
xurime 已提交
189 190 191 192 193 194 195 196
	if len(content.BookViews.WorkBookView) > 0 {
		content.BookViews.WorkBookView[0].ActiveTab = index
	} else {
		content.BookViews.WorkBookView = append(content.BookViews.WorkBookView, xlsxWorkBookView{
			ActiveTab: index,
		})
	}
	sheets := len(content.Sheets.Sheet)
xurime's avatar
xurime 已提交
197
	index++
xurime's avatar
xurime 已提交
198 199
	for i := 0; i < sheets; i++ {
		sheetIndex := i + 1
xurime's avatar
xurime 已提交
200
		xlsx := f.workSheetReader("sheet" + strconv.Itoa(sheetIndex))
xurime's avatar
xurime 已提交
201 202 203 204 205 206 207 208 209 210 211 212 213 214
		if index == sheetIndex {
			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
			}
		}
	}
215
	return
xurime's avatar
xurime 已提交
216 217
}

218 219
// GetActiveSheetIndex provides function to get active sheet of XLSX. If not
// found the active sheet will be return integer 0.
220 221
func (f *File) GetActiveSheetIndex() int {
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
222
	content := f.workbookReader()
223 224
	for _, v := range content.Sheets.Sheet {
		xlsx := xlsxWorksheet{}
225 226 227
		buffer.WriteString("xl/worksheets/sheet")
		buffer.WriteString(strings.TrimPrefix(v.ID, "rId"))
		buffer.WriteString(".xml")
228 229 230
		xml.Unmarshal([]byte(f.readXML(buffer.String())), &xlsx)
		for _, sheetView := range xlsx.SheetViews.SheetView {
			if sheetView.TabSelected {
231 232
				ID, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId"))
				return ID
233
			}
234
		}
235
		buffer.Reset()
236
	}
237
	return 0
238 239
}

240 241 242 243 244
// SetSheetName provides function to set the sheet name be given old and new
// sheet name. Maximum 31 characters are allowed in sheet title and this
// function only changes the name of the sheet and will not update the sheet
// name in the formula or reference associated with the cell. So there may be
// problem formula error or reference missing.
245 246 247 248 249 250
func (f *File) SetSheetName(oldName, newName string) {
	r := strings.NewReplacer(":", "", "\\", "", "/", "", "?", "", "*", "", "[", "", "]", "")
	newName = r.Replace(newName)
	if len(newName) > 31 {
		newName = newName[0:31]
	}
xurime's avatar
xurime 已提交
251
	content := f.workbookReader()
252 253 254 255 256 257 258
	for k, v := range content.Sheets.Sheet {
		if v.Name == oldName {
			content.Sheets.Sheet[k].Name = newName
		}
	}
}

259
// GetSheetName provides function to get sheet name of XLSX by given worksheet
260
// index. If given sheet index is invalid, will return an empty string.
261
func (f *File) GetSheetName(index int) string {
xurime's avatar
xurime 已提交
262
	content := f.workbookReader()
263 264 265 266 267 268 269 270 271
	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
				}
			}
272 273
		}
	}
274
	return ""
275 276
}

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
// GetSheetIndex provides function to get worksheet index of XLSX by given sheet
// name. If given sheet name is invalid, will return an integer type value 0.
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
}

295
// GetSheetMap provides function to get sheet map of XLSX. For example:
296 297 298 299 300 301 302 303 304 305 306
//
//    xlsx, err := excelize.OpenFile("/tmp/Workbook.xlsx")
//    if err != nil {
//        fmt.Println(err)
//        os.Exit(1)
//    }
//    for k, v := range xlsx.GetSheetMap()
//        fmt.Println(k, v)
//    }
//
func (f *File) GetSheetMap() map[int]string {
xurime's avatar
xurime 已提交
307
	content := f.workbookReader()
308
	rels := f.workbookRelsReader()
309 310
	sheetMap := map[int]string{}
	for _, v := range content.Sheets.Sheet {
311 312 313 314 315 316
		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
			}
		}
317 318
	}
	return sheetMap
xurime's avatar
xurime 已提交
319
}
320 321 322 323 324 325 326 327 328

// SetSheetBackground provides function to set background picture by given sheet
// index.
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
	}
329
	ext, ok := supportImageTypes[path.Ext(picture)]
330 331 332 333 334 335 336 337 338 339
	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
}
340 341 342 343 344 345 346

// DeleteSheet provides function to detele worksheet in a workbook by given
// sheet name. Use this method with caution, which will affect changes in
// references such as formulas, charts, and so on. If there is any referenced
// value of the deleted worksheet, it will cause a file error when you open it.
// This function will be invalid when only the one worksheet is left.
func (f *File) DeleteSheet(name string) {
xurime's avatar
xurime 已提交
347
	content := f.workbookReader()
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
	for k, v := range content.Sheets.Sheet {
		if v.Name != name || len(content.Sheets.Sheet) < 2 {
			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"
		target := f.deteleSheetFromWorkbookRels(v.ID)
		f.deteleSheetFromContentTypes(target)
		_, ok := f.XLSX[sheet]
		if ok {
			delete(f.XLSX, sheet)
		}
		_, ok = f.XLSX[rels]
		if ok {
			delete(f.XLSX, rels)
		}
		_, ok = f.Sheet[sheet]
		if ok {
			delete(f.Sheet, sheet)
		}
xurime's avatar
xurime 已提交
369
		f.SheetCount--
370 371 372 373 374 375 376
	}
}

// deteleSheetFromWorkbookRels provides function to remove worksheet
// relationships by given relationships ID in the file
// xl/_rels/workbook.xml.rels.
func (f *File) deteleSheetFromWorkbookRels(rID string) string {
xurime's avatar
xurime 已提交
377
	content := f.workbookRelsReader()
378 379 380 381 382 383 384 385 386 387 388 389 390
	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 ""
}

// deteleSheetFromContentTypes provides function to remove worksheet
// relationships by given target name in the file [Content_Types].xml.
func (f *File) deteleSheetFromContentTypes(target string) {
xurime's avatar
xurime 已提交
391
	content := f.contentTypesReader()
392 393 394 395 396 397 398
	for k, v := range content.Overrides {
		if v.PartName != "/xl/"+target {
			continue
		}
		content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
	}
}