excelize.go 10.7 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 6 7
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
8
// charts of XLSX. This library needs Go version 1.10 or later.
xurime's avatar
xurime 已提交
9 10
//
// See https://xuri.me/excelize for more information about this package.
xurime's avatar
xurime 已提交
11 12 13 14
package excelize

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

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

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

51 52
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)

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

69
// newFile is object builder
70 71 72 73 74 75 76 77 78 79 80 81 82 83
func newFile() *File {
	return &File{
		checked:          make(map[string]bool),
		sheetMap:         make(map[string]string),
		Comments:         make(map[string]*xlsxComments),
		Drawings:         make(map[string]*xlsxWsDr),
		Sheet:            make(map[string]*xlsxWorksheet),
		DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
		VMLDrawing:       make(map[string]*vmlDrawing),
		Relationships:    make(map[string]*xlsxRelationships),
		CharsetReader:    charset.NewReaderLabel,
	}
}

J
Josh Fyne 已提交
84 85 86
// OpenReader take an io.Reader and return a populated XLSX file.
func OpenReader(r io.Reader) (*File, error) {
	b, err := ioutil.ReadAll(r)
J
Josh Fyne 已提交
87 88 89 90
	if err != nil {
		return nil, err
	}

J
Josh Fyne 已提交
91
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
J
Josh Fyne 已提交
92
	if err != nil {
93 94 95 96 97 98 99 100 101 102 103
		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 已提交
104 105
		return nil, err
	}
J
Josh Fyne 已提交
106

J
Josh Fyne 已提交
107 108 109
	file, sheetCount, err := ReadZipReader(zr)
	if err != nil {
		return nil, err
xurime's avatar
xurime 已提交
110
	}
111 112
	f := newFile()
	f.SheetCount, f.XLSX = sheetCount, file
113
	f.CalcChain = f.calcChainReader()
wing2life's avatar
wing2life 已提交
114 115
	f.sheetMap = f.getSheetMap()
	f.Styles = f.stylesReader()
xurime's avatar
xurime 已提交
116
	f.Theme = f.themeReader()
wing2life's avatar
wing2life 已提交
117
	return f, nil
xurime's avatar
xurime 已提交
118 119
}

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

124
// Creates new XML decoder with charset reader.
125 126 127 128 129 130
func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
	ret = xml.NewDecoder(rdr)
	ret.CharsetReader = f.CharsetReader
	return
}

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

xurime's avatar
xurime 已提交
146 147
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
148 149 150 151 152 153 154 155 156
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
157
	}
158 159 160 161 162 163 164 165
	if xlsx = f.Sheet[name]; f.Sheet[name] == nil {
		xlsx = new(xlsxWorksheet)
		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 已提交
166 167 168
		if f.checked == nil {
			f.checked = make(map[string]bool)
		}
169 170 171 172 173
		if ok = f.checked[name]; !ok {
			checkSheet(xlsx)
			if err = checkRow(xlsx); err != nil {
				return
			}
xurime's avatar
xurime 已提交
174 175
			f.checked[name] = true
		}
176
		f.Sheet[name] = xlsx
177
	}
178 179

	return
xurime's avatar
xurime 已提交
180
}
181

xurime's avatar
xurime 已提交
182
// checkSheet provides a function to fill each row element and make that is
183 184 185
// continuous in a worksheet of XML.
func checkSheet(xlsx *xlsxWorksheet) {
	row := len(xlsx.SheetData.Row)
xurime's avatar
xurime 已提交
186
	if row >= 1 {
187 188 189 190 191
		lastRow := xlsx.SheetData.Row[row-1].R
		if lastRow >= row {
			row = lastRow
		}
	}
H
Harris 已提交
192 193 194
	sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
	for _, r := range xlsx.SheetData.Row {
		sheetData.Row[r.R-1] = r
195
	}
H
Harris 已提交
196 197
	for i := 1; i <= row; i++ {
		sheetData.Row[i-1].R = i
198 199
	}
	xlsx.SheetData = sheetData
xurime's avatar
xurime 已提交
200 201
}

xurime's avatar
xurime 已提交
202 203 204 205 206 207 208
// 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 {
	rels := f.relsReader(relPath)
	if rels == nil {
		rels = &xlsxRelationships{}
	}
209 210 211 212 213 214 215 216
	var rID int
	for _, rel := range rels.Relationships {
		ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
		if ID > rID {
			rID = ID
		}
	}
	rID++
xurime's avatar
xurime 已提交
217 218 219 220 221 222 223 224 225 226 227 228 229
	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
}

xurime's avatar
xurime 已提交
230
// replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
231
// xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
232
// Office Excel 2007.
233 234
func replaceWorkSheetsRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
	var oldXmlns = []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
xurime's avatar
xurime 已提交
235
	var newXmlns = []byte(`<worksheet` + templateNamespaceIDMap)
236 237 238 239
	workbookMarshal = bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
	return workbookMarshal
}

xurime's avatar
xurime 已提交
240 241 242 243 244
// replaceStyleRelationshipsNameSpaceBytes provides a function to replace
// xl/styles.xml XML tags to self-closing for compatible Microsoft Office
// Excel 2007.
func replaceStyleRelationshipsNameSpaceBytes(contentMarshal []byte) []byte {
	var oldXmlns = []byte(`<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
xurime's avatar
xurime 已提交
245
	var newXmlns = []byte(`<styleSheet` + templateNamespaceIDMap)
xurime's avatar
xurime 已提交
246 247 248 249
	contentMarshal = bytes.Replace(contentMarshal, oldXmlns, newXmlns, -1)
	return contentMarshal
}

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

// AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be .xlsm. For example:
//
299
//    if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil {
xurime's avatar
xurime 已提交
300
//        fmt.Println(err)
301
//    }
302
//    if err := f.AddVBAProject("vbaProject.bin"); err != nil {
xurime's avatar
xurime 已提交
303
//        fmt.Println(err)
304
//    }
305
//    if err := f.SaveAs("macros.xlsm"); err != nil {
xurime's avatar
xurime 已提交
306
//        fmt.Println(err)
307 308 309 310 311 312 313 314 315 316 317 318
//    }
//
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 已提交
319
	wb := f.relsReader("xl/_rels/workbook.xml.rels")
320 321 322 323 324 325 326 327 328 329 330 331 332 333
	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 已提交
334
		wb.Relationships = append(wb.Relationships, xlsxRelationship{
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
			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" {
			content.Overrides[idx].ContentType = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
		}
	}
	if !ok {
		content.Defaults = append(content.Defaults, xlsxDefault{
			Extension:   "bin",
			ContentType: "application/vnd.ms-office.vbaProject",
		})
	}
}