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 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
//    }
//
96
// Close the file by Close function after opening the spreadsheet.
97
func OpenFile(filename string, opt ...Options) (*File, error) {
xurime's avatar
xurime 已提交
98
	file, err := os.Open(filepath.Clean(filename))
99
	if err != nil {
J
Josh Fyne 已提交
100 101
		return nil, err
	}
xurime's avatar
xurime 已提交
102
	f, err := OpenReader(file, opt...)
J
Josh Fyne 已提交
103
	if err != nil {
104 105 106 107 108
		closeErr := file.Close()
		if closeErr == nil {
			return f, err
		}
		return f, closeErr
J
Josh Fyne 已提交
109 110
	}
	f.Path = filename
111
	return f, file.Close()
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) {
158 159
		if b, err = Decrypt(b, f.options); err != nil {
			return nil, ErrWorkbookFileFormat
160
		}
161 162 163
	}
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
	if err != nil {
164 165 166
		if len(f.options.Password) > 0 {
			return nil, ErrWorkbookPassword
		}
J
Josh Fyne 已提交
167 168
		return nil, err
	}
169
	file, sheetCount, err := f.ReadZipReader(zr)
J
Josh Fyne 已提交
170 171
	if err != nil {
		return nil, err
xurime's avatar
xurime 已提交
172
	}
173 174 175 176
	f.SheetCount = sheetCount
	for k, v := range file {
		f.Pkg.Store(k, v)
	}
177
	f.CalcChain = f.calcChainReader()
wing2life's avatar
wing2life 已提交
178 179
	f.sheetMap = f.getSheetMap()
	f.Styles = f.stylesReader()
xurime's avatar
xurime 已提交
180
	f.Theme = f.themeReader()
wing2life's avatar
wing2life 已提交
181
	return f, nil
xurime's avatar
xurime 已提交
182 183
}

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

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

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

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

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

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

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

390 391
// 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
392
// cell have a linked value. Reference
393
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
394 395 396 397 398 399
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
400 401 402 403 404 405
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//            <v>100</v>
//         </c>
//    </row>
406 407 408
//
// to
//
409 410 411 412 413
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//        </c>
//    </row>
414
//
xurime's avatar
xurime 已提交
415
func (f *File) UpdateLinkedValue() error {
416 417 418
	wb := f.workbookReader()
	// recalculate formulas
	wb.CalcPr = nil
419
	for _, name := range f.GetSheetList() {
xurime's avatar
xurime 已提交
420
		ws, err := f.workSheetReader(name)
xurime's avatar
xurime 已提交
421
		if err != nil {
xurime's avatar
xurime 已提交
422
			if err.Error() == fmt.Sprintf("sheet %s is not a worksheet", trimSheetName(name)) {
423 424
				continue
			}
xurime's avatar
xurime 已提交
425 426
			return err
		}
xurime's avatar
xurime 已提交
427 428
		for indexR := range ws.SheetData.Row {
			for indexC, col := range ws.SheetData.Row[indexR].C {
429
				if col.F != nil && col.V != "" {
xurime's avatar
xurime 已提交
430 431
					ws.SheetData.Row[indexR].C[indexC].V = ""
					ws.SheetData.Row[indexR].C[indexC].T = ""
432 433 434 435
				}
			}
		}
	}
xurime's avatar
xurime 已提交
436
	return nil
437
}
438 439 440 441

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

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