excelize.go 10.9 KB
Newer Older
xurime's avatar
xurime 已提交
1
// Copyright 2016 - 2020 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 7 8 9 10
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
// spreadsheet documents generated by Microsoft Exce™ 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.10 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"
19
	"errors"
xurime's avatar
xurime 已提交
20
	"fmt"
J
Josh Fyne 已提交
21 22 23
	"io"
	"io/ioutil"
	"os"
24
	"path"
25
	"path/filepath"
xurime's avatar
xurime 已提交
26
	"strconv"
27
	"strings"
28
	"sync"
29 30

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

33
// File define a populated spreadsheet file struct.
34
type File struct {
xurime's avatar
xurime 已提交
35
	sync.Mutex
xurime's avatar
xurime 已提交
36
	options          *Options
37
	xmlAttr          map[string][]xml.Attr
38 39
	checked          map[string]bool
	sheetMap         map[string]string
40
	streams          map[string]*StreamWriter
41 42 43
	CalcChain        *xlsxCalcChain
	Comments         map[string]*xlsxComments
	ContentTypes     *xlsxTypes
44
	Drawings         map[string]*xlsxWsDr
45 46
	Path             string
	SharedStrings    *xlsxSST
47
	sharedStringsMap map[string]int
48 49 50 51 52 53 54
	Sheet            map[string]*xlsxWorksheet
	SheetCount       int
	Styles           *xlsxStyleSheet
	Theme            *xlsxTheme
	DecodeVMLDrawing map[string]*decodeVmlDrawing
	VMLDrawing       map[string]*vmlDrawing
	WorkBook         *xlsxWorkbook
xurime's avatar
xurime 已提交
55
	Relationships    map[string]*xlsxRelationships
56
	XLSX             map[string][]byte
57
	CharsetReader    charsetTranscoderFn
xurime's avatar
xurime 已提交
58 59
}

60 61
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)

62 63 64 65 66 67 68 69 70 71 72 73 74
// Options define the options for open spreadsheet.
type Options struct {
	Password string
}

// OpenFile take the name of an spreadsheet file and returns a populated spreadsheet file struct
// for it. For example, open spreadsheet with password protection:
//
//    f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
//    if err != nil {
//        return
//    }
//
75 76
// Note that the excelize just support decrypt and not support encrypt currently, the spreadsheet
// saved by Save and SaveAs will be without password unprotected.
77
func OpenFile(filename string, opt ...Options) (*File, error) {
J
Josh Fyne 已提交
78
	file, err := os.Open(filename)
79
	if err != nil {
J
Josh Fyne 已提交
80 81
		return nil, err
	}
J
Josh Fyne 已提交
82
	defer file.Close()
xurime's avatar
xurime 已提交
83
	f, err := OpenReader(file, opt...)
J
Josh Fyne 已提交
84 85 86 87 88
	if err != nil {
		return nil, err
	}
	f.Path = filename
	return f, nil
J
Josh Fyne 已提交
89 90
}

91
// newFile is object builder
92 93
func newFile() *File {
	return &File{
94
		xmlAttr:          make(map[string][]xml.Attr),
95 96 97 98
		checked:          make(map[string]bool),
		sheetMap:         make(map[string]string),
		Comments:         make(map[string]*xlsxComments),
		Drawings:         make(map[string]*xlsxWsDr),
99
		sharedStringsMap: make(map[string]int),
100 101 102 103 104 105 106 107
		Sheet:            make(map[string]*xlsxWorksheet),
		DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
		VMLDrawing:       make(map[string]*vmlDrawing),
		Relationships:    make(map[string]*xlsxRelationships),
		CharsetReader:    charset.NewReaderLabel,
	}
}

108 109
// OpenReader read data stream from io.Reader and return a populated
// spreadsheet file.
110
func OpenReader(r io.Reader, opt ...Options) (*File, error) {
J
Josh Fyne 已提交
111
	b, err := ioutil.ReadAll(r)
J
Josh Fyne 已提交
112 113 114
	if err != nil {
		return nil, err
	}
xurime's avatar
xurime 已提交
115
	f := newFile()
116
	if bytes.Contains(b, oleIdentifier) && len(opt) > 0 {
117
		for _, o := range opt {
xurime's avatar
xurime 已提交
118
			f.options = &o
119
		}
xurime's avatar
xurime 已提交
120
		b, err = Decrypt(b, f.options)
121 122
		if err != nil {
			return nil, fmt.Errorf("decrypted file failed")
123
		}
124 125 126
	}
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
	if err != nil {
J
Josh Fyne 已提交
127 128
		return nil, err
	}
J
Josh Fyne 已提交
129

J
Josh Fyne 已提交
130 131 132
	file, sheetCount, err := ReadZipReader(zr)
	if err != nil {
		return nil, err
xurime's avatar
xurime 已提交
133
	}
134
	f.SheetCount, f.XLSX = sheetCount, file
135
	f.CalcChain = f.calcChainReader()
wing2life's avatar
wing2life 已提交
136 137
	f.sheetMap = f.getSheetMap()
	f.Styles = f.stylesReader()
xurime's avatar
xurime 已提交
138
	f.Theme = f.themeReader()
wing2life's avatar
wing2life 已提交
139
	return f, nil
xurime's avatar
xurime 已提交
140 141
}

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

146
// Creates new XML decoder with charset reader.
147 148 149 150 151 152
func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
	ret = xml.NewDecoder(rdr)
	ret.CharsetReader = f.CharsetReader
	return
}

xurime's avatar
xurime 已提交
153
// setDefaultTimeStyle provides a function to set default numbers format for
154 155
// time.Time type cell value by given worksheet name, cell coordinates and
// number format code.
156 157 158 159 160 161
func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
	s, err := f.GetCellStyle(sheet, axis)
	if err != nil {
		return err
	}
	if s == 0 {
162
		style, _ := f.NewStyle(&Style{NumFmt: format})
163
		err = f.SetCellStyle(sheet, axis, axis, style)
164
	}
165
	return err
166 167
}

xurime's avatar
xurime 已提交
168 169
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
170
func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
171 172
	f.Lock()
	defer f.Unlock()
173 174 175 176 177 178 179 180
	var (
		name string
		ok   bool
	)

	if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {
		err = fmt.Errorf("sheet %s is not exist", sheet)
		return
181
	}
182
	if xlsx = f.Sheet[name]; f.Sheet[name] == nil {
183 184 185 186
		if strings.HasPrefix(name, "xl/chartsheets") {
			err = fmt.Errorf("sheet %s is chart sheet", sheet)
			return
		}
187
		xlsx = new(xlsxWorksheet)
188 189 190 191
		if _, ok := f.xmlAttr[name]; !ok {
			d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name))))
			f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)
		}
192 193 194 195 196 197
		if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))).
			Decode(xlsx); err != nil && err != io.EOF {
			err = fmt.Errorf("xml decode error: %s", err)
			return
		}
		err = nil
xurime's avatar
xurime 已提交
198 199 200
		if f.checked == nil {
			f.checked = make(map[string]bool)
		}
201 202 203 204 205
		if ok = f.checked[name]; !ok {
			checkSheet(xlsx)
			if err = checkRow(xlsx); err != nil {
				return
			}
xurime's avatar
xurime 已提交
206 207
			f.checked[name] = true
		}
208
		f.Sheet[name] = xlsx
209
	}
210 211

	return
xurime's avatar
xurime 已提交
212
}
213

xurime's avatar
xurime 已提交
214
// checkSheet provides a function to fill each row element and make that is
215 216
// continuous in a worksheet of XML.
func checkSheet(xlsx *xlsxWorksheet) {
217 218 219 220 221
	var row int
	for _, r := range xlsx.SheetData.Row {
		if r.R != 0 && r.R > row {
			row = r.R
			continue
222
		}
223
		row++
224
	}
H
Harris 已提交
225
	sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
226
	row = 0
H
Harris 已提交
227
	for _, r := range xlsx.SheetData.Row {
228 229 230 231 232 233 234 235
		if r.R != 0 {
			sheetData.Row[r.R-1] = r
			row = r.R
			continue
		}
		row++
		r.R = row
		sheetData.Row[row-1] = r
236
	}
H
Harris 已提交
237 238
	for i := 1; i <= row; i++ {
		sheetData.Row[i-1].R = i
239 240
	}
	xlsx.SheetData = sheetData
xurime's avatar
xurime 已提交
241 242
}

xurime's avatar
xurime 已提交
243 244 245
// 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 {
246 247 248
	var uniqPart = map[string]string{
		SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
	}
xurime's avatar
xurime 已提交
249 250 251 252
	rels := f.relsReader(relPath)
	if rels == nil {
		rels = &xlsxRelationships{}
	}
253
	var rID int
254
	for idx, rel := range rels.Relationships {
255 256 257 258
		ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
		if ID > rID {
			rID = ID
		}
259 260 261 262 263 264
		if relType == rel.Type {
			if partName, ok := uniqPart[rel.Type]; ok {
				rels.Relationships[idx].Target = partName
				return rID
			}
		}
265 266
	}
	rID++
xurime's avatar
xurime 已提交
267 268 269 270 271 272 273 274 275 276 277 278 279
	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,
	})
	f.Relationships[relPath] = rels
	return rID
}

280 281
// 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
282
// cell have a linked value. Reference
283
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
284 285 286 287 288 289
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
290 291 292 293 294 295
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//            <v>100</v>
//         </c>
//    </row>
296 297 298
//
// to
//
299 300 301 302 303
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//        </c>
//    </row>
304
//
xurime's avatar
xurime 已提交
305
func (f *File) UpdateLinkedValue() error {
306 307 308
	wb := f.workbookReader()
	// recalculate formulas
	wb.CalcPr = nil
309
	for _, name := range f.GetSheetList() {
xurime's avatar
xurime 已提交
310 311 312 313
		xlsx, err := f.workSheetReader(name)
		if err != nil {
			return err
		}
314 315
		for indexR := range xlsx.SheetData.Row {
			for indexC, col := range xlsx.SheetData.Row[indexR].C {
316
				if col.F != nil && col.V != "" {
317 318
					xlsx.SheetData.Row[indexR].C[indexC].V = ""
					xlsx.SheetData.Row[indexR].C[indexC].T = ""
319 320 321 322
				}
			}
		}
	}
xurime's avatar
xurime 已提交
323
	return nil
324
}
325 326 327 328

// AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be .xlsm. For example:
//
329
//    if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil {
xurime's avatar
xurime 已提交
330
//        fmt.Println(err)
331
//    }
332
//    if err := f.AddVBAProject("vbaProject.bin"); err != nil {
xurime's avatar
xurime 已提交
333
//        fmt.Println(err)
334
//    }
335
//    if err := f.SaveAs("macros.xlsm"); err != nil {
xurime's avatar
xurime 已提交
336
//        fmt.Println(err)
337 338 339 340 341 342
//    }
//
func (f *File) AddVBAProject(bin string) error {
	var err error
	// Check vbaProject.bin exists first.
	if _, err = os.Stat(bin); os.IsNotExist(err) {
343
		return fmt.Errorf("stat %s: no such file or directory", bin)
344 345 346 347 348
	}
	if path.Ext(bin) != ".bin" {
		return errors.New("unsupported VBA project extension")
	}
	f.setContentTypePartVBAProjectExtensions()
349 350 351
	wbPath := f.getWorkbookPath()
	wbRelsPath := strings.TrimPrefix(filepath.Join(filepath.Dir(wbPath), "_rels", filepath.Base(wbPath)+".rels"), string(filepath.Separator))
	wb := f.relsReader(wbRelsPath)
352 353 354 355 356 357 358 359 360 361 362 363 364 365
	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 已提交
366
		wb.Relationships = append(wb.Relationships, xlsxRelationship{
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
			ID:     "rId" + strconv.Itoa(rID),
			Target: "vbaProject.bin",
			Type:   SourceRelationshipVBAProject,
		})
	}
	file, _ := ioutil.ReadFile(bin)
	f.XLSX["xl/vbaProject.bin"] = file
	return err
}

// setContentTypePartVBAProjectExtensions provides a function to set the
// content type for relationship parts and the main document part.
func (f *File) setContentTypePartVBAProjectExtensions() {
	var ok bool
	content := f.contentTypesReader()
	for _, v := range content.Defaults {
		if v.Extension == "bin" {
			ok = true
		}
	}
	for idx, o := range content.Overrides {
		if o.PartName == "/xl/workbook.xml" {
389
			content.Overrides[idx].ContentType = ContentTypeMacro
390 391 392 393 394
		}
	}
	if !ok {
		content.Defaults = append(content.Defaults, xlsxDefault{
			Extension:   "bin",
395
			ContentType: ContentTypeVBA,
396 397 398
		})
	}
}