excelize.go 14.3 KB
Newer Older
1
// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of
xurime's avatar
xurime 已提交
2 3
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
xurime's avatar
xurime 已提交
4

5 6 7 8 9 10
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.
xurime's avatar
xurime 已提交
11 12
//
// See https://xuri.me/excelize for more information about this package.
xurime's avatar
xurime 已提交
13 14 15 16
package excelize

import (
	"archive/zip"
17
	"bytes"
xurime's avatar
xurime 已提交
18
	"encoding/xml"
xurime's avatar
xurime 已提交
19
	"fmt"
J
Josh Fyne 已提交
20 21 22
	"io"
	"io/ioutil"
	"os"
23
	"path"
xurime's avatar
xurime 已提交
24
	"path/filepath"
xurime's avatar
xurime 已提交
25
	"strconv"
26
	"strings"
27
	"sync"
28 29

	"golang.org/x/net/html/charset"
xurime's avatar
xurime 已提交
30 31
)

32
// File define a populated spreadsheet file struct.
33
type File struct {
xurime's avatar
xurime 已提交
34
	sync.Mutex
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
	options          *Options
	xmlAttr          map[string][]xml.Attr
	checked          map[string]bool
	sheetMap         map[string]string
	streams          map[string]*StreamWriter
	tempFiles        sync.Map
	CalcChain        *xlsxCalcChain
	Comments         map[string]*xlsxComments
	ContentTypes     *xlsxTypes
	Drawings         sync.Map
	Path             string
	SharedStrings    *xlsxSST
	sharedStringsMap map[string]int
	sharedStringItem [][]uint
	sharedStringTemp *os.File
	Sheet            sync.Map
	SheetCount       int
	Styles           *xlsxStyleSheet
	Theme            *xlsxTheme
	DecodeVMLDrawing map[string]*decodeVmlDrawing
	VMLDrawing       map[string]*vmlDrawing
	WorkBook         *xlsxWorkbook
	Relationships    sync.Map
	Pkg              sync.Map
	CharsetReader    charsetTranscoderFn
xurime's avatar
xurime 已提交
60 61
}

62 63
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)

64 65
// Options define the options for open and reading spreadsheet.
//
66 67 68
// MaxCalcIterations specifies the maximum iterations for iterative
// calculation, the default value is 0.
//
69 70 71 72 73 74 75
// Password specifies the password of the spreadsheet in plain text.
//
// RawCellValue specifies if apply the number format for the cell value or get
// the raw value.
//
// UnzipSizeLimit specifies the unzip size limit in bytes on open the
// spreadsheet, this value should be greater than or equal to
76
// UnzipXMLSizeLimit, the default size limit is 16GB.
77
//
78 79 80 81 82
// UnzipXMLSizeLimit specifies the memory limit on unzipping worksheet and
// shared string table in bytes, worksheet XML will be extracted to system
// temporary directory when the file size is over this value, this value
// should be less than or equal to UnzipSizeLimit, the default value is
// 16MB.
83
type Options struct {
84
	MaxCalcIterations uint
85 86 87 88
	Password          string
	RawCellValue      bool
	UnzipSizeLimit    int64
	UnzipXMLSizeLimit int64
89 90
}

xurime's avatar
xurime 已提交
91 92 93
// OpenFile take the name of an spreadsheet file and returns a populated
// spreadsheet file struct for it. For example, open spreadsheet with
// password protection:
94
//
95 96 97 98
//	f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
//	if err != nil {
//	    return
//	}
99
//
100
// Close the file by Close function after opening the spreadsheet.
101
func OpenFile(filename string, opt ...Options) (*File, error) {
xurime's avatar
xurime 已提交
102
	file, err := os.Open(filepath.Clean(filename))
103
	if err != nil {
J
Josh Fyne 已提交
104 105
		return nil, err
	}
xurime's avatar
xurime 已提交
106
	f, err := OpenReader(file, opt...)
J
Josh Fyne 已提交
107
	if err != nil {
108 109 110 111 112
		closeErr := file.Close()
		if closeErr == nil {
			return f, err
		}
		return f, closeErr
J
Josh Fyne 已提交
113 114
	}
	f.Path = filename
115
	return f, file.Close()
J
Josh Fyne 已提交
116 117
}

118
// newFile is object builder
119 120
func newFile() *File {
	return &File{
121
		options:          &Options{UnzipSizeLimit: UnzipSizeLimit, UnzipXMLSizeLimit: StreamChunkSize},
122
		xmlAttr:          make(map[string][]xml.Attr),
123 124
		checked:          make(map[string]bool),
		sheetMap:         make(map[string]string),
125
		tempFiles:        sync.Map{},
126
		Comments:         make(map[string]*xlsxComments),
127
		Drawings:         sync.Map{},
128
		sharedStringsMap: make(map[string]int),
129
		Sheet:            sync.Map{},
130 131
		DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
		VMLDrawing:       make(map[string]*vmlDrawing),
132
		Relationships:    sync.Map{},
133 134 135 136
		CharsetReader:    charset.NewReaderLabel,
	}
}

137 138
// OpenReader read data stream from io.Reader and return a populated
// spreadsheet file.
139
func OpenReader(r io.Reader, opts ...Options) (*File, error) {
J
Josh Fyne 已提交
140
	b, err := ioutil.ReadAll(r)
J
Josh Fyne 已提交
141 142 143
	if err != nil {
		return nil, err
	}
xurime's avatar
xurime 已提交
144
	f := newFile()
145
	f.options = parseOptions(opts...)
xurime's avatar
xurime 已提交
146 147
	if f.options.UnzipSizeLimit == 0 {
		f.options.UnzipSizeLimit = UnzipSizeLimit
148 149
		if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
			f.options.UnzipSizeLimit = f.options.UnzipXMLSizeLimit
150 151
		}
	}
152 153 154 155
	if f.options.UnzipXMLSizeLimit == 0 {
		f.options.UnzipXMLSizeLimit = StreamChunkSize
		if f.options.UnzipSizeLimit < f.options.UnzipXMLSizeLimit {
			f.options.UnzipXMLSizeLimit = f.options.UnzipSizeLimit
156 157
		}
	}
158
	if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
159
		return nil, ErrOptionsUnzipSizeLimit
xurime's avatar
xurime 已提交
160 161
	}
	if bytes.Contains(b, oleIdentifier) {
162 163
		if b, err = Decrypt(b, f.options); err != nil {
			return nil, ErrWorkbookFileFormat
164
		}
165 166 167
	}
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
	if err != nil {
168 169 170
		if len(f.options.Password) > 0 {
			return nil, ErrWorkbookPassword
		}
J
Josh Fyne 已提交
171 172
		return nil, err
	}
173
	file, sheetCount, err := f.ReadZipReader(zr)
J
Josh Fyne 已提交
174 175
	if err != nil {
		return nil, err
xurime's avatar
xurime 已提交
176
	}
177 178 179 180
	f.SheetCount = sheetCount
	for k, v := range file {
		f.Pkg.Store(k, v)
	}
181
	f.CalcChain = f.calcChainReader()
wing2life's avatar
wing2life 已提交
182 183
	f.sheetMap = f.getSheetMap()
	f.Styles = f.stylesReader()
xurime's avatar
xurime 已提交
184
	f.Theme = f.themeReader()
wing2life's avatar
wing2life 已提交
185
	return f, nil
xurime's avatar
xurime 已提交
186 187
}

xurime's avatar
xurime 已提交
188 189 190 191 192 193 194 195 196 197
// parseOptions provides a function to parse the optional settings for open
// and reading spreadsheet.
func parseOptions(opts ...Options) *Options {
	opt := &Options{}
	for _, o := range opts {
		opt = &o
	}
	return opt
}

198 199
// CharsetTranscoder Set user defined codepage transcoder function for open
// XLSX from non UTF-8 encoding.
200 201
func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f }

202
// Creates new XML decoder with charset reader.
203 204 205 206 207 208
func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
	ret = xml.NewDecoder(rdr)
	ret.CharsetReader = f.CharsetReader
	return
}

xurime's avatar
xurime 已提交
209
// setDefaultTimeStyle provides a function to set default numbers format for
210 211
// time.Time type cell value by given worksheet name, cell coordinates and
// number format code.
212 213 214 215 216 217
func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
	s, err := f.GetCellStyle(sheet, axis)
	if err != nil {
		return err
	}
	if s == 0 {
218
		style, _ := f.NewStyle(&Style{NumFmt: format})
219
		err = f.SetCellStyle(sheet, axis, axis, style)
220
	}
221
	return err
222 223
}

xurime's avatar
xurime 已提交
224 225
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
226
func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
227 228
	f.Lock()
	defer f.Unlock()
229 230 231 232
	var (
		name string
		ok   bool
	)
233
	if name, ok = f.getSheetXMLPath(sheet); !ok {
234
		err = newNoExistSheetError(sheet)
235
		return
236
	}
237 238 239 240
	if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {
		ws = worksheet.(*xlsxWorksheet)
		return
	}
241 242
	for _, sheetType := range []string{"xl/chartsheets", "xl/dialogsheet", "xl/macrosheet"} {
		if strings.HasPrefix(name, sheetType) {
243
			err = newNotWorksheetError(sheet)
244 245
			return
		}
246 247 248
	}
	ws = new(xlsxWorksheet)
	if _, ok := f.xmlAttr[name]; !ok {
249
		d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name))))
250 251
		f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)
	}
252
	if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))).
253
		Decode(ws); err != nil && err != io.EOF {
254
		err = newDecodeXMLError(err)
255 256 257 258 259 260 261 262 263
		return
	}
	err = nil
	if f.checked == nil {
		f.checked = make(map[string]bool)
	}
	if ok = f.checked[name]; !ok {
		checkSheet(ws)
		if err = checkRow(ws); err != nil {
264 265
			return
		}
266
		f.checked[name] = true
267
	}
268
	f.Sheet.Store(name, ws)
269
	return
xurime's avatar
xurime 已提交
270
}
271

xurime's avatar
xurime 已提交
272
// checkSheet provides a function to fill each row element and make that is
273
// continuous in a worksheet of XML.
274
func checkSheet(ws *xlsxWorksheet) {
275
	var row int
276 277 278 279 280 281 282
	var r0 xlsxRow
	for i, r := range ws.SheetData.Row {
		if i == 0 && r.R == 0 {
			r0 = r
			ws.SheetData.Row = ws.SheetData.Row[1:]
			continue
		}
283 284 285
		if r.R != 0 && r.R > row {
			row = r.R
			continue
286
		}
287 288 289
		if r.R != row {
			row++
		}
290
	}
H
Harris 已提交
291
	sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
292
	row = 0
293
	for _, r := range ws.SheetData.Row {
294
		if r.R == row && row > 0 {
295 296 297
			sheetData.Row[r.R-1].C = append(sheetData.Row[r.R-1].C, r.C...)
			continue
		}
298 299 300 301 302 303 304 305
		if r.R != 0 {
			sheetData.Row[r.R-1] = r
			row = r.R
			continue
		}
		row++
		r.R = row
		sheetData.Row[row-1] = r
306
	}
H
Harris 已提交
307 308
	for i := 1; i <= row; i++ {
		sheetData.Row[i-1].R = i
309
	}
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
	checkSheetR0(ws, &sheetData, &r0)
}

// checkSheetR0 handle the row element with r="0" attribute, cells in this row
// could be disorderly, the cell in this row can be used as the value of
// which cell is empty in the normal rows.
func checkSheetR0(ws *xlsxWorksheet, sheetData *xlsxSheetData, r0 *xlsxRow) {
	for _, cell := range r0.C {
		if col, row, err := CellNameToCoordinates(cell.R); err == nil {
			rows, rowIdx := len(sheetData.Row), row-1
			for r := rows; r < row; r++ {
				sheetData.Row = append(sheetData.Row, xlsxRow{R: r + 1})
			}
			columns, colIdx := len(sheetData.Row[rowIdx].C), col-1
			for c := columns; c < col; c++ {
				sheetData.Row[rowIdx].C = append(sheetData.Row[rowIdx].C, xlsxC{})
			}
			if !sheetData.Row[rowIdx].C[colIdx].hasValue() {
				sheetData.Row[rowIdx].C[colIdx] = cell
			}
		}
	}
	ws.SheetData = *sheetData
xurime's avatar
xurime 已提交
333 334
}

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
// setRels provides a function to set relationships by given relationship ID,
// XML path, relationship type, target and target mode.
func (f *File) setRels(rID, relPath, relType, target, targetMode string) int {
	rels := f.relsReader(relPath)
	if rels == nil || rID == "" {
		return f.addRels(relPath, relType, target, targetMode)
	}
	rels.Lock()
	defer rels.Unlock()
	var ID int
	for i, rel := range rels.Relationships {
		if rel.ID == rID {
			rels.Relationships[i].Type = relType
			rels.Relationships[i].Target = target
			rels.Relationships[i].TargetMode = targetMode
			ID, _ = strconv.Atoi(strings.TrimPrefix(rID, "rId"))
			break
		}
	}
	return ID
}

xurime's avatar
xurime 已提交
357 358 359
// addRels provides a function to add relationships by given XML path,
// relationship type, target and target mode.
func (f *File) addRels(relPath, relType, target, targetMode string) int {
360
	uniqPart := map[string]string{
361 362
		SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
	}
xurime's avatar
xurime 已提交
363 364 365 366
	rels := f.relsReader(relPath)
	if rels == nil {
		rels = &xlsxRelationships{}
	}
xurime's avatar
xurime 已提交
367 368
	rels.Lock()
	defer rels.Unlock()
369
	var rID int
370
	for idx, rel := range rels.Relationships {
371 372 373 374
		ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
		if ID > rID {
			rID = ID
		}
375 376 377 378 379 380
		if relType == rel.Type {
			if partName, ok := uniqPart[rel.Type]; ok {
				rels.Relationships[idx].Target = partName
				return rID
			}
		}
381 382
	}
	rID++
xurime's avatar
xurime 已提交
383 384 385 386 387 388 389 390 391
	var ID bytes.Buffer
	ID.WriteString("rId")
	ID.WriteString(strconv.Itoa(rID))
	rels.Relationships = append(rels.Relationships, xlsxRelationship{
		ID:         ID.String(),
		Type:       relType,
		Target:     target,
		TargetMode: targetMode,
	})
392
	f.Relationships.Store(relPath, rels)
xurime's avatar
xurime 已提交
393 394 395
	return rID
}

396
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
397
// Office Excel application. This function will be remove value tag when met a
398
// cell have a linked value. Reference
399
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
400
//
401 402
// Notice: after opening generated workbook, Excel will update the linked value
// and generate a new value and will prompt to save the file or not.
403 404 405
//
// For example:
//
406 407 408 409 410 411
//	<row r="19" spans="2:2">
//	    <c r="B19">
//	        <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//	        <v>100</v>
//	     </c>
//	</row>
412 413 414
//
// to
//
415 416 417 418 419
//	<row r="19" spans="2:2">
//	    <c r="B19">
//	        <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//	    </c>
//	</row>
xurime's avatar
xurime 已提交
420
func (f *File) UpdateLinkedValue() error {
421 422 423
	wb := f.workbookReader()
	// recalculate formulas
	wb.CalcPr = nil
424
	for _, name := range f.GetSheetList() {
xurime's avatar
xurime 已提交
425
		ws, err := f.workSheetReader(name)
xurime's avatar
xurime 已提交
426
		if err != nil {
427
			if err.Error() == newNotWorksheetError(name).Error() {
428 429
				continue
			}
xurime's avatar
xurime 已提交
430 431
			return err
		}
xurime's avatar
xurime 已提交
432 433
		for indexR := range ws.SheetData.Row {
			for indexC, col := range ws.SheetData.Row[indexR].C {
434
				if col.F != nil && col.V != "" {
xurime's avatar
xurime 已提交
435 436
					ws.SheetData.Row[indexR].C[indexC].V = ""
					ws.SheetData.Row[indexR].C[indexC].T = ""
437 438 439 440
				}
			}
		}
	}
xurime's avatar
xurime 已提交
441
	return nil
442
}
443 444 445 446

// AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be .xlsm. For example:
//
447 448 449 450 451 452 453 454 455
//	if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil {
//	    fmt.Println(err)
//	}
//	if err := f.AddVBAProject("vbaProject.bin"); err != nil {
//	    fmt.Println(err)
//	}
//	if err := f.SaveAs("macros.xlsm"); err != nil {
//	    fmt.Println(err)
//	}
456 457 458 459
func (f *File) AddVBAProject(bin string) error {
	var err error
	// Check vbaProject.bin exists first.
	if _, err = os.Stat(bin); os.IsNotExist(err) {
460
		return fmt.Errorf("stat %s: no such file or directory", bin)
461 462
	}
	if path.Ext(bin) != ".bin" {
463
		return ErrAddVBAProject
464
	}
465
	wb := f.relsReader(f.getWorkbookRelsPath())
xurime's avatar
xurime 已提交
466 467
	wb.Lock()
	defer wb.Unlock()
468 469 470 471 472 473 474 475 476 477 478 479 480 481
	var rID int
	var ok bool
	for _, rel := range wb.Relationships {
		if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {
			ok = true
			continue
		}
		t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
		if t > rID {
			rID = t
		}
	}
	rID++
	if !ok {
xurime's avatar
xurime 已提交
482
		wb.Relationships = append(wb.Relationships, xlsxRelationship{
483 484 485 486 487
			ID:     "rId" + strconv.Itoa(rID),
			Target: "vbaProject.bin",
			Type:   SourceRelationshipVBAProject,
		})
	}
xurime's avatar
xurime 已提交
488
	file, _ := ioutil.ReadFile(filepath.Clean(bin))
489
	f.Pkg.Store("xl/vbaProject.bin", file)
490 491 492
	return err
}

493 494 495
// setContentTypePartProjectExtensions provides a function to set the content
// type for relationship parts and the main document part.
func (f *File) setContentTypePartProjectExtensions(contentType string) {
496 497
	var ok bool
	content := f.contentTypesReader()
xurime's avatar
xurime 已提交
498 499
	content.Lock()
	defer content.Unlock()
500 501 502 503 504 505 506
	for _, v := range content.Defaults {
		if v.Extension == "bin" {
			ok = true
		}
	}
	for idx, o := range content.Overrides {
		if o.PartName == "/xl/workbook.xml" {
507
			content.Overrides[idx].ContentType = contentType
508 509 510 511 512
		}
	}
	if !ok {
		content.Defaults = append(content.Defaults, xlsxDefault{
			Extension:   "bin",
513
			ContentType: ContentTypeVBA,
514 515 516
		})
	}
}