excelize.go 7.6 KB
Newer Older
xurime's avatar
xurime 已提交
1
// Copyright 2016 - 2019 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 8
// 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
// charts of XLSX. This library needs Go version 1.8 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"
xurime's avatar
xurime 已提交
22 23 24
	"strconv"
)

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

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

// 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 已提交
67 68 69 70
	if err != nil {
		return nil, err
	}

J
Josh Fyne 已提交
71
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
J
Josh Fyne 已提交
72
	if err != nil {
73 74 75 76 77 78 79 80 81 82 83
		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 已提交
84 85
		return nil, err
	}
J
Josh Fyne 已提交
86

J
Josh Fyne 已提交
87 88 89
	file, sheetCount, err := ReadZipReader(zr)
	if err != nil {
		return nil, err
xurime's avatar
xurime 已提交
90
	}
wing2life's avatar
wing2life 已提交
91
	f := &File{
92 93
		checked:          make(map[string]bool),
		Comments:         make(map[string]*xlsxComments),
94 95
		DrawingRels:      make(map[string]*xlsxWorkbookRels),
		Drawings:         make(map[string]*xlsxWsDr),
96 97 98 99
		Sheet:            make(map[string]*xlsxWorksheet),
		SheetCount:       sheetCount,
		DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
		VMLDrawing:       make(map[string]*vmlDrawing),
100
		WorkSheetRels:    make(map[string]*xlsxWorkbookRels),
101
		XLSX:             file,
wing2life's avatar
wing2life 已提交
102
	}
103
	f.CalcChain = f.calcChainReader()
wing2life's avatar
wing2life 已提交
104 105
	f.sheetMap = f.getSheetMap()
	f.Styles = f.stylesReader()
xurime's avatar
xurime 已提交
106
	f.Theme = f.themeReader()
wing2life's avatar
wing2life 已提交
107
	return f, nil
xurime's avatar
xurime 已提交
108 109
}

xurime's avatar
xurime 已提交
110
// setDefaultTimeStyle provides a function to set default numbers format for
111 112
// time.Time type cell value by given worksheet name, cell coordinates and
// number format code.
113 114 115 116 117 118
func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
	s, err := f.GetCellStyle(sheet, axis)
	if err != nil {
		return err
	}
	if s == 0 {
119
		style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`)
120
		f.SetCellStyle(sheet, axis, axis, style)
121
	}
122
	return err
123 124
}

xurime's avatar
xurime 已提交
125 126
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
xurime's avatar
xurime 已提交
127
func (f *File) workSheetReader(sheet string) (*xlsxWorksheet, error) {
128
	name, ok := f.sheetMap[trimSheetName(sheet)]
129
	if !ok {
xurime's avatar
xurime 已提交
130
		return nil, fmt.Errorf("sheet %s is not exist", sheet)
131
	}
132
	if f.Sheet[name] == nil {
xurime's avatar
xurime 已提交
133
		var xlsx xlsxWorksheet
134
		_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(name)), &xlsx)
xurime's avatar
xurime 已提交
135 136 137 138 139
		if f.checked == nil {
			f.checked = make(map[string]bool)
		}
		ok := f.checked[name]
		if !ok {
140
			checkSheet(&xlsx)
xurime's avatar
xurime 已提交
141 142 143 144
			checkRow(&xlsx)
			f.checked[name] = true
		}
		f.Sheet[name] = &xlsx
145
	}
xurime's avatar
xurime 已提交
146
	return f.Sheet[name], nil
xurime's avatar
xurime 已提交
147
}
148

xurime's avatar
xurime 已提交
149
// checkSheet provides a function to fill each row element and make that is
150 151 152
// continuous in a worksheet of XML.
func checkSheet(xlsx *xlsxWorksheet) {
	row := len(xlsx.SheetData.Row)
xurime's avatar
xurime 已提交
153
	if row >= 1 {
154 155 156 157 158
		lastRow := xlsx.SheetData.Row[row-1].R
		if lastRow >= row {
			row = lastRow
		}
	}
159 160
	sheetData := xlsxSheetData{}
	existsRows := map[int]int{}
161 162
	for k := range xlsx.SheetData.Row {
		existsRows[xlsx.SheetData.Row[k].R] = k
163 164 165 166 167
	}
	for i := 0; i < row; i++ {
		_, ok := existsRows[i+1]
		if ok {
			sheetData.Row = append(sheetData.Row, xlsx.SheetData.Row[existsRows[i+1]])
xurime's avatar
xurime 已提交
168 169 170 171
		} else {
			sheetData.Row = append(sheetData.Row, xlsxRow{
				R: i + 1,
			})
xurime's avatar
xurime 已提交
172
		}
173 174
	}
	xlsx.SheetData = sheetData
xurime's avatar
xurime 已提交
175 176
}

xurime's avatar
xurime 已提交
177
// replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
178
// xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
179
// Office Excel 2007.
180 181 182 183 184 185 186
func replaceWorkSheetsRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
	var oldXmlns = []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
	var newXmlns = []byte(`<worksheet xr:uid="{00000000-0001-0000-0000-000000000000}" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" mc:Ignorable="x14ac xr xr2 xr3" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
	workbookMarshal = bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
	return workbookMarshal
}

187 188
// 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
189
// cell have a linked value. Reference
190
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
191 192 193 194 195 196
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
197 198 199 200 201 202
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//            <v>100</v>
//         </c>
//    </row>
203 204 205
//
// to
//
206 207 208 209 210
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//        </c>
//    </row>
211
//
xurime's avatar
xurime 已提交
212
func (f *File) UpdateLinkedValue() error {
213
	for _, name := range f.GetSheetMap() {
xurime's avatar
xurime 已提交
214 215 216 217
		xlsx, err := f.workSheetReader(name)
		if err != nil {
			return err
		}
218 219
		for indexR := range xlsx.SheetData.Row {
			for indexC, col := range xlsx.SheetData.Row[indexR].C {
220
				if col.F != nil && col.V != "" {
221 222
					xlsx.SheetData.Row[indexR].C[indexC].V = ""
					xlsx.SheetData.Row[indexR].C[indexC].T = ""
223 224 225 226
				}
			}
		}
	}
xurime's avatar
xurime 已提交
227
	return nil
228
}