excelize.go 7.9 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"
J
Josh Fyne 已提交
17 18 19
	"io"
	"io/ioutil"
	"os"
xurime's avatar
xurime 已提交
20 21 22 23
	"strconv"
	"strings"
)

24
// File define a populated XLSX file struct.
25
type File struct {
26 27 28 29 30
	checked          map[string]bool
	sheetMap         map[string]string
	CalcChain        *xlsxCalcChain
	Comments         map[string]*xlsxComments
	ContentTypes     *xlsxTypes
31 32
	DrawingRels      map[string]*xlsxWorkbookRels
	Drawings         map[string]*xlsxWsDr
33 34 35 36 37 38 39 40 41 42
	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
43
	WorkSheetRels    map[string]*xlsxWorkbookRels
44
	XLSX             map[string][]byte
xurime's avatar
xurime 已提交
45 46
}

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

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

J
Josh Fyne 已提交
70
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
J
Josh Fyne 已提交
71 72 73
	if err != nil {
		return nil, err
	}
J
Josh Fyne 已提交
74

J
Josh Fyne 已提交
75 76 77
	file, sheetCount, err := ReadZipReader(zr)
	if err != nil {
		return nil, err
xurime's avatar
xurime 已提交
78
	}
wing2life's avatar
wing2life 已提交
79
	f := &File{
80 81
		checked:          make(map[string]bool),
		Comments:         make(map[string]*xlsxComments),
82 83
		DrawingRels:      make(map[string]*xlsxWorkbookRels),
		Drawings:         make(map[string]*xlsxWsDr),
84 85 86 87
		Sheet:            make(map[string]*xlsxWorksheet),
		SheetCount:       sheetCount,
		DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
		VMLDrawing:       make(map[string]*vmlDrawing),
88
		WorkSheetRels:    make(map[string]*xlsxWorkbookRels),
89
		XLSX:             file,
wing2life's avatar
wing2life 已提交
90
	}
91
	f.CalcChain = f.calcChainReader()
wing2life's avatar
wing2life 已提交
92 93
	f.sheetMap = f.getSheetMap()
	f.Styles = f.stylesReader()
xurime's avatar
xurime 已提交
94
	f.Theme = f.themeReader()
wing2life's avatar
wing2life 已提交
95
	return f, nil
xurime's avatar
xurime 已提交
96 97
}

xurime's avatar
xurime 已提交
98
// setDefaultTimeStyle provides a function to set default numbers format for
99 100
// time.Time type cell value by given worksheet name, cell coordinates and
// number format code.
101 102 103 104 105 106
func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
	s, err := f.GetCellStyle(sheet, axis)
	if err != nil {
		return err
	}
	if s == 0 {
107
		style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`)
108
		f.SetCellStyle(sheet, axis, axis, style)
109
	}
110
	return err
111 112
}

xurime's avatar
xurime 已提交
113 114
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
xurime's avatar
xurime 已提交
115
func (f *File) workSheetReader(sheet string) *xlsxWorksheet {
116
	name, ok := f.sheetMap[trimSheetName(sheet)]
117 118 119
	if !ok {
		name = "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
	}
120
	if f.Sheet[name] == nil {
xurime's avatar
xurime 已提交
121
		var xlsx xlsxWorksheet
122
		_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(name)), &xlsx)
xurime's avatar
xurime 已提交
123 124 125 126 127
		if f.checked == nil {
			f.checked = make(map[string]bool)
		}
		ok := f.checked[name]
		if !ok {
128
			checkSheet(&xlsx)
xurime's avatar
xurime 已提交
129 130 131 132
			checkRow(&xlsx)
			f.checked[name] = true
		}
		f.Sheet[name] = &xlsx
133
	}
134
	return f.Sheet[name]
xurime's avatar
xurime 已提交
135
}
136

xurime's avatar
xurime 已提交
137
// checkSheet provides a function to fill each row element and make that is
138 139 140
// continuous in a worksheet of XML.
func checkSheet(xlsx *xlsxWorksheet) {
	row := len(xlsx.SheetData.Row)
xurime's avatar
xurime 已提交
141
	if row >= 1 {
142 143 144 145 146
		lastRow := xlsx.SheetData.Row[row-1].R
		if lastRow >= row {
			row = lastRow
		}
	}
147 148
	sheetData := xlsxSheetData{}
	existsRows := map[int]int{}
149 150
	for k := range xlsx.SheetData.Row {
		existsRows[xlsx.SheetData.Row[k].R] = k
151 152 153 154 155
	}
	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 已提交
156 157 158 159
		} else {
			sheetData.Row = append(sheetData.Row, xlsxRow{
				R: i + 1,
			})
xurime's avatar
xurime 已提交
160
		}
161 162
	}
	xlsx.SheetData = sheetData
xurime's avatar
xurime 已提交
163 164
}

xurime's avatar
xurime 已提交
165
// replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
166
// xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
167
// Office Excel 2007.
168 169 170 171 172 173 174
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
}

175 176
// 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
177
// cell have a linked value. Reference
178
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
179 180 181 182 183 184
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
185 186 187 188 189 190
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//            <v>100</v>
//         </c>
//    </row>
191 192 193
//
// to
//
194 195 196 197 198
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//        </c>
//    </row>
199
//
200
func (f *File) UpdateLinkedValue() {
201 202
	for _, name := range f.GetSheetMap() {
		xlsx := f.workSheetReader(name)
203 204
		for indexR := range xlsx.SheetData.Row {
			for indexC, col := range xlsx.SheetData.Row[indexR].C {
205
				if col.F != nil && col.V != "" {
206 207
					xlsx.SheetData.Row[indexR].C[indexC].V = ""
					xlsx.SheetData.Row[indexR].C[indexC].T = ""
208 209 210 211 212
				}
			}
		}
	}
}
213

214 215
// GetMergeCells provides a function to get all merged cells from a worksheet currently.
func (f *File) GetMergeCells(sheet string) []MergeCell {
216 217
	xlsx := f.workSheetReader(sheet)

218
	var mergeCells []MergeCell
219 220

	if xlsx.MergeCells != nil {
221
		mergeCells = make([]MergeCell, 0, len(xlsx.MergeCells.Cells))
222

223
		for i := range xlsx.MergeCells.Cells {
S
sairoutine 已提交
224 225
			ref := xlsx.MergeCells.Cells[i].Ref
			axis := strings.Split(ref, ":")[0]
226 227
			val, _ := f.GetCellValue(sheet, axis)
			mergeCells = append(mergeCells, []string{ref, val})
S
sairoutine 已提交
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
		}
	}

	return mergeCells
}

// MergeCell define a merged cell data.
// It consists of the following structure.
// example: []string{"D4:E10", "cell value"}
type MergeCell []string

// GetCellValue returns merged cell value.
func (m *MergeCell) GetCellValue() string {
	return (*m)[1]
}

// GetStartAxis returns the merge start axis.
// example: "C2"
func (m *MergeCell) GetStartAxis() string {
	axis := strings.Split((*m)[0], ":")
	return axis[0]
}

// GetEndAxis returns the merge end axis.
// example: "D4"
func (m *MergeCell) GetEndAxis() string {
	axis := strings.Split((*m)[0], ":")
	return axis[1]
}