excelize.go 13.7 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

xurime's avatar
xurime 已提交
5
// Package excelize providing a set of functions that allow you to write to
6
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
R
Ray 已提交
7
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
8 9
// complex components by high compatibility, and provided streaming API for
// generating or reading data from a worksheet with huge amounts of data. This
10
// 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 66 67 68 69 70 71 72
// Options define the options for open and reading spreadsheet.
//
// 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
73
// UnzipXMLSizeLimit, the default size limit is 16GB.
74
//
75 76 77 78 79
// 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.
80
type Options struct {
81 82 83 84
	Password          string
	RawCellValue      bool
	UnzipSizeLimit    int64
	UnzipXMLSizeLimit int64
85 86
}

xurime's avatar
xurime 已提交
87 88 89
// OpenFile take the name of an spreadsheet file and returns a populated
// spreadsheet file struct for it. For example, open spreadsheet with
// password protection:
90 91 92 93 94 95
//
//    f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
//    if err != nil {
//        return
//    }
//
xurime's avatar
xurime 已提交
96 97
// Note that the excelize just support decrypt and not support encrypt
// currently, the spreadsheet saved by Save and SaveAs will be without
98 99
// password unprotected. Close the file by Close after opening the
// spreadsheet.
100
func OpenFile(filename string, opt ...Options) (*File, error) {
xurime's avatar
xurime 已提交
101
	file, err := os.Open(filepath.Clean(filename))
102
	if err != nil {
J
Josh Fyne 已提交
103 104
		return nil, err
	}
J
Josh Fyne 已提交
105
	defer file.Close()
xurime's avatar
xurime 已提交
106
	f, err := OpenReader(file, opt...)
J
Josh Fyne 已提交
107 108 109 110 111
	if err != nil {
		return nil, err
	}
	f.Path = filename
	return f, nil
J
Josh Fyne 已提交
112 113
}

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

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

xurime's avatar
xurime 已提交
182 183 184 185 186 187 188 189 190 191
// 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
}

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

196
// Creates new XML decoder with charset reader.
197 198 199 200 201 202
func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
	ret = xml.NewDecoder(rdr)
	ret.CharsetReader = f.CharsetReader
	return
}

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

xurime's avatar
xurime 已提交
218 219
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
220
func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
221 222
	f.Lock()
	defer f.Unlock()
223 224 225 226 227 228 229
	var (
		name string
		ok   bool
	)
	if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {
		err = fmt.Errorf("sheet %s is not exist", sheet)
		return
230
	}
231 232 233 234
	if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {
		ws = worksheet.(*xlsxWorksheet)
		return
	}
xurime's avatar
xurime 已提交
235 236
	if strings.HasPrefix(name, "xl/chartsheets") || strings.HasPrefix(name, "xl/macrosheet") {
		err = fmt.Errorf("sheet %s is not a worksheet", sheet)
237 238 239 240
		return
	}
	ws = new(xlsxWorksheet)
	if _, ok := f.xmlAttr[name]; !ok {
241
		d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name))))
242 243
		f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)
	}
244
	if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))).
245 246 247 248 249 250 251 252 253 254 255
		Decode(ws); err != nil && err != io.EOF {
		err = fmt.Errorf("xml decode error: %s", err)
		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 {
256 257
			return
		}
258
		f.checked[name] = true
259
	}
260
	f.Sheet.Store(name, ws)
261
	return
xurime's avatar
xurime 已提交
262
}
263

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

xurime's avatar
xurime 已提交
327 328 329
// 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 {
330
	uniqPart := map[string]string{
331 332
		SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
	}
xurime's avatar
xurime 已提交
333 334 335 336
	rels := f.relsReader(relPath)
	if rels == nil {
		rels = &xlsxRelationships{}
	}
xurime's avatar
xurime 已提交
337 338
	rels.Lock()
	defer rels.Unlock()
339
	var rID int
340
	for idx, rel := range rels.Relationships {
341 342 343 344
		ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
		if ID > rID {
			rID = ID
		}
345 346 347 348 349 350
		if relType == rel.Type {
			if partName, ok := uniqPart[rel.Type]; ok {
				rels.Relationships[idx].Target = partName
				return rID
			}
		}
351 352
	}
	rID++
xurime's avatar
xurime 已提交
353 354 355 356 357 358 359 360 361
	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,
	})
362
	f.Relationships.Store(relPath, rels)
xurime's avatar
xurime 已提交
363 364 365
	return rID
}

366 367
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
// Office Excel 2007 and 2010. This function will be remove value tag when met a
368
// cell have a linked value. Reference
369
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
370 371 372 373 374 375
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
376 377 378 379 380 381
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//            <v>100</v>
//         </c>
//    </row>
382 383 384
//
// to
//
385 386 387 388 389
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//        </c>
//    </row>
390
//
xurime's avatar
xurime 已提交
391
func (f *File) UpdateLinkedValue() error {
392 393 394
	wb := f.workbookReader()
	// recalculate formulas
	wb.CalcPr = nil
395
	for _, name := range f.GetSheetList() {
xurime's avatar
xurime 已提交
396
		ws, err := f.workSheetReader(name)
xurime's avatar
xurime 已提交
397
		if err != nil {
xurime's avatar
xurime 已提交
398
			if err.Error() == fmt.Sprintf("sheet %s is not a worksheet", trimSheetName(name)) {
399 400
				continue
			}
xurime's avatar
xurime 已提交
401 402
			return err
		}
xurime's avatar
xurime 已提交
403 404
		for indexR := range ws.SheetData.Row {
			for indexC, col := range ws.SheetData.Row[indexR].C {
405
				if col.F != nil && col.V != "" {
xurime's avatar
xurime 已提交
406 407
					ws.SheetData.Row[indexR].C[indexC].V = ""
					ws.SheetData.Row[indexR].C[indexC].T = ""
408 409 410 411
				}
			}
		}
	}
xurime's avatar
xurime 已提交
412
	return nil
413
}
414 415 416 417

// AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be .xlsm. For example:
//
418
//    if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil {
xurime's avatar
xurime 已提交
419
//        fmt.Println(err)
420
//    }
421
//    if err := f.AddVBAProject("vbaProject.bin"); err != nil {
xurime's avatar
xurime 已提交
422
//        fmt.Println(err)
423
//    }
424
//    if err := f.SaveAs("macros.xlsm"); err != nil {
xurime's avatar
xurime 已提交
425
//        fmt.Println(err)
426 427 428 429 430 431
//    }
//
func (f *File) AddVBAProject(bin string) error {
	var err error
	// Check vbaProject.bin exists first.
	if _, err = os.Stat(bin); os.IsNotExist(err) {
432
		return fmt.Errorf("stat %s: no such file or directory", bin)
433 434
	}
	if path.Ext(bin) != ".bin" {
435
		return ErrAddVBAProject
436
	}
437
	wb := f.relsReader(f.getWorkbookRelsPath())
xurime's avatar
xurime 已提交
438 439
	wb.Lock()
	defer wb.Unlock()
440 441 442 443 444 445 446 447 448 449 450 451 452 453
	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 已提交
454
		wb.Relationships = append(wb.Relationships, xlsxRelationship{
455 456 457 458 459
			ID:     "rId" + strconv.Itoa(rID),
			Target: "vbaProject.bin",
			Type:   SourceRelationshipVBAProject,
		})
	}
xurime's avatar
xurime 已提交
460
	file, _ := ioutil.ReadFile(filepath.Clean(bin))
461
	f.Pkg.Store("xl/vbaProject.bin", file)
462 463 464
	return err
}

465 466 467
// setContentTypePartProjectExtensions provides a function to set the content
// type for relationship parts and the main document part.
func (f *File) setContentTypePartProjectExtensions(contentType string) {
468 469
	var ok bool
	content := f.contentTypesReader()
xurime's avatar
xurime 已提交
470 471
	content.Lock()
	defer content.Unlock()
472 473 474 475 476 477 478
	for _, v := range content.Defaults {
		if v.Extension == "bin" {
			ok = true
		}
	}
	for idx, o := range content.Overrides {
		if o.PartName == "/xl/workbook.xml" {
479
			content.Overrides[idx].ContentType = contentType
480 481 482 483 484
		}
	}
	if !ok {
		content.Defaults = append(content.Defaults, xlsxDefault{
			Extension:   "bin",
485
			ContentType: ContentTypeVBA,
486 487 488
		})
	}
}