excelize.go 10.6 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"
xurime's avatar
xurime 已提交
25
	"strconv"
26
	"strings"
27 28

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

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

55 56
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)

57 58
// OpenFile take the name of an spreadsheet file and returns a populated
// spreadsheet file struct for it.
59
func OpenFile(filename string) (*File, error) {
J
Josh Fyne 已提交
60
	file, err := os.Open(filename)
61
	if err != nil {
J
Josh Fyne 已提交
62 63
		return nil, err
	}
J
Josh Fyne 已提交
64
	defer file.Close()
J
Josh Fyne 已提交
65 66 67 68 69 70
	f, err := OpenReader(file)
	if err != nil {
		return nil, err
	}
	f.Path = filename
	return f, nil
J
Josh Fyne 已提交
71 72
}

73
// newFile is object builder
74 75
func newFile() *File {
	return &File{
76
		xmlAttr:          make(map[string][]xml.Attr),
77 78 79 80
		checked:          make(map[string]bool),
		sheetMap:         make(map[string]string),
		Comments:         make(map[string]*xlsxComments),
		Drawings:         make(map[string]*xlsxWsDr),
81
		sharedStringsMap: make(map[string]int),
82 83 84 85 86 87 88 89
		Sheet:            make(map[string]*xlsxWorksheet),
		DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
		VMLDrawing:       make(map[string]*vmlDrawing),
		Relationships:    make(map[string]*xlsxRelationships),
		CharsetReader:    charset.NewReaderLabel,
	}
}

90 91
// OpenReader read data stream from io.Reader and return a populated
// spreadsheet file.
J
Josh Fyne 已提交
92 93
func OpenReader(r io.Reader) (*File, error) {
	b, err := ioutil.ReadAll(r)
J
Josh Fyne 已提交
94 95 96 97
	if err != nil {
		return nil, err
	}

J
Josh Fyne 已提交
98
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
J
Josh Fyne 已提交
99
	if err != nil {
100 101 102 103 104 105 106 107 108 109 110
		identifier := []byte{
			// checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier
			0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
			0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
			0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
			0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
			0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
		}
		if bytes.Contains(b, identifier) {
			return nil, errors.New("not support encrypted file currently")
		}
J
Josh Fyne 已提交
111 112
		return nil, err
	}
J
Josh Fyne 已提交
113

J
Josh Fyne 已提交
114 115 116
	file, sheetCount, err := ReadZipReader(zr)
	if err != nil {
		return nil, err
xurime's avatar
xurime 已提交
117
	}
118 119
	f := newFile()
	f.SheetCount, f.XLSX = sheetCount, file
120
	f.CalcChain = f.calcChainReader()
wing2life's avatar
wing2life 已提交
121 122
	f.sheetMap = f.getSheetMap()
	f.Styles = f.stylesReader()
xurime's avatar
xurime 已提交
123
	f.Theme = f.themeReader()
wing2life's avatar
wing2life 已提交
124
	return f, nil
xurime's avatar
xurime 已提交
125 126
}

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

131
// Creates new XML decoder with charset reader.
132 133 134 135 136 137
func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
	ret = xml.NewDecoder(rdr)
	ret.CharsetReader = f.CharsetReader
	return
}

xurime's avatar
xurime 已提交
138
// setDefaultTimeStyle provides a function to set default numbers format for
139 140
// time.Time type cell value by given worksheet name, cell coordinates and
// number format code.
141 142 143 144 145 146
func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
	s, err := f.GetCellStyle(sheet, axis)
	if err != nil {
		return err
	}
	if s == 0 {
147
		style, _ := f.NewStyle(&Style{NumFmt: format})
148
		_ = f.SetCellStyle(sheet, axis, axis, style)
149
	}
150
	return err
151 152
}

xurime's avatar
xurime 已提交
153 154
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
155 156 157 158 159 160 161 162 163
func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
	var (
		name string
		ok   bool
	)

	if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {
		err = fmt.Errorf("sheet %s is not exist", sheet)
		return
164
	}
165
	if xlsx = f.Sheet[name]; f.Sheet[name] == nil {
166 167 168 169
		if strings.HasPrefix(name, "xl/chartsheets") {
			err = fmt.Errorf("sheet %s is chart sheet", sheet)
			return
		}
170
		xlsx = new(xlsxWorksheet)
171 172 173 174
		if _, ok := f.xmlAttr[name]; !ok {
			d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name))))
			f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)
		}
175 176 177 178 179 180
		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 已提交
181 182 183
		if f.checked == nil {
			f.checked = make(map[string]bool)
		}
184 185 186 187 188
		if ok = f.checked[name]; !ok {
			checkSheet(xlsx)
			if err = checkRow(xlsx); err != nil {
				return
			}
xurime's avatar
xurime 已提交
189 190
			f.checked[name] = true
		}
191
		f.Sheet[name] = xlsx
192
	}
193 194

	return
xurime's avatar
xurime 已提交
195
}
196

xurime's avatar
xurime 已提交
197
// checkSheet provides a function to fill each row element and make that is
198 199
// continuous in a worksheet of XML.
func checkSheet(xlsx *xlsxWorksheet) {
200 201 202 203 204
	var row int
	for _, r := range xlsx.SheetData.Row {
		if r.R != 0 && r.R > row {
			row = r.R
			continue
205
		}
206
		row++
207
	}
H
Harris 已提交
208
	sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
209
	row = 0
H
Harris 已提交
210
	for _, r := range xlsx.SheetData.Row {
211 212 213 214 215 216 217 218
		if r.R != 0 {
			sheetData.Row[r.R-1] = r
			row = r.R
			continue
		}
		row++
		r.R = row
		sheetData.Row[row-1] = r
219
	}
H
Harris 已提交
220 221
	for i := 1; i <= row; i++ {
		sheetData.Row[i-1].R = i
222 223
	}
	xlsx.SheetData = sheetData
xurime's avatar
xurime 已提交
224 225
}

xurime's avatar
xurime 已提交
226 227 228
// 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 {
229 230 231
	var uniqPart = map[string]string{
		SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
	}
xurime's avatar
xurime 已提交
232 233 234 235
	rels := f.relsReader(relPath)
	if rels == nil {
		rels = &xlsxRelationships{}
	}
236
	var rID int
237
	for idx, rel := range rels.Relationships {
238 239 240 241
		ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
		if ID > rID {
			rID = ID
		}
242 243 244 245 246 247
		if relType == rel.Type {
			if partName, ok := uniqPart[rel.Type]; ok {
				rels.Relationships[idx].Target = partName
				return rID
			}
		}
248 249
	}
	rID++
xurime's avatar
xurime 已提交
250 251 252 253 254 255 256 257 258 259 260 261 262
	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
}

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

// AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be .xlsm. For example:
//
312
//    if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil {
xurime's avatar
xurime 已提交
313
//        fmt.Println(err)
314
//    }
315
//    if err := f.AddVBAProject("vbaProject.bin"); err != nil {
xurime's avatar
xurime 已提交
316
//        fmt.Println(err)
317
//    }
318
//    if err := f.SaveAs("macros.xlsm"); err != nil {
xurime's avatar
xurime 已提交
319
//        fmt.Println(err)
320 321 322 323 324 325 326 327 328 329 330 331
//    }
//
func (f *File) AddVBAProject(bin string) error {
	var err error
	// Check vbaProject.bin exists first.
	if _, err = os.Stat(bin); os.IsNotExist(err) {
		return err
	}
	if path.Ext(bin) != ".bin" {
		return errors.New("unsupported VBA project extension")
	}
	f.setContentTypePartVBAProjectExtensions()
xurime's avatar
xurime 已提交
332
	wb := f.relsReader("xl/_rels/workbook.xml.rels")
333 334 335 336 337 338 339 340 341 342 343 344 345 346
	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 已提交
347
		wb.Relationships = append(wb.Relationships, xlsxRelationship{
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
			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" {
370
			content.Overrides[idx].ContentType = ContentTypeMacro
371 372 373 374 375
		}
	}
	if !ok {
		content.Defaults = append(content.Defaults, xlsxDefault{
			Extension:   "bin",
376
			ContentType: ContentTypeVBA,
377 378 379
		})
	}
}