excelize.go 11.2 KB
Newer Older
xurime's avatar
xurime 已提交
1 2 3 4
package excelize

import (
	"archive/zip"
5
	"bytes"
xurime's avatar
xurime 已提交
6
	"encoding/xml"
J
Josh Fyne 已提交
7 8 9
	"io"
	"io/ioutil"
	"os"
xurime's avatar
xurime 已提交
10 11 12 13
	"strconv"
	"strings"
)

14
// File define a populated XLSX file struct.
15
type File struct {
xurime's avatar
xurime 已提交
16 17 18 19 20 21 22 23
	checked      map[string]bool
	ContentTypes *xlsxTypes
	Path         string
	Sheet        map[string]*xlsxWorksheet
	SheetCount   int
	WorkBook     *xlsxWorkbook
	WorkBookRels *xlsxWorkbookRels
	XLSX         map[string]string
xurime's avatar
xurime 已提交
24 25
}

26 27
// OpenFile take the name of an XLSX file and returns a populated XLSX file
// struct for it.
28
func OpenFile(filename string) (*File, error) {
J
Josh Fyne 已提交
29
	file, err := os.Open(filename)
30
	if err != nil {
J
Josh Fyne 已提交
31 32
		return nil, err
	}
J
Josh Fyne 已提交
33
	defer file.Close()
J
Josh Fyne 已提交
34 35 36 37 38 39
	f, err := OpenReader(file)
	if err != nil {
		return nil, err
	}
	f.Path = filename
	return f, nil
J
Josh Fyne 已提交
40 41 42 43 44
}

// 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 已提交
45 46 47 48
	if err != nil {
		return nil, err
	}

J
Josh Fyne 已提交
49
	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
J
Josh Fyne 已提交
50 51 52
	if err != nil {
		return nil, err
	}
J
Josh Fyne 已提交
53

J
Josh Fyne 已提交
54 55 56
	file, sheetCount, err := ReadZipReader(zr)
	if err != nil {
		return nil, err
xurime's avatar
xurime 已提交
57
	}
58
	return &File{
J
Josh Fyne 已提交
59
		checked:    make(map[string]bool),
xurime's avatar
xurime 已提交
60
		Sheet:      make(map[string]*xlsxWorksheet),
61
		SheetCount: sheetCount,
xurime's avatar
xurime 已提交
62
		XLSX:       file,
63
	}, nil
xurime's avatar
xurime 已提交
64 65
}

66
// SetCellValue provides function to set int or string type value of a cell.
67
func (f *File) SetCellValue(sheet, axis string, value interface{}) {
68
	switch t := value.(type) {
69
	case int:
70
		f.SetCellInt(sheet, axis, value.(int))
71 72 73 74 75 76 77 78 79
	case int8:
		f.SetCellInt(sheet, axis, int(value.(int8)))
	case int16:
		f.SetCellInt(sheet, axis, int(value.(int16)))
	case int32:
		f.SetCellInt(sheet, axis, int(value.(int32)))
	case int64:
		f.SetCellInt(sheet, axis, int(value.(int64)))
	case float32:
S
Sharsie 已提交
80
		f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float32)), 'f', -1, 32))
81
	case float64:
S
Sharsie 已提交
82
		f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float64)), 'f', -1, 64))
83 84 85 86 87
	case string:
		f.SetCellStr(sheet, axis, t)
	case []byte:
		f.SetCellStr(sheet, axis, string(t))
	default:
88
		f.SetCellStr(sheet, axis, "")
89 90 91
	}
}

xurime's avatar
xurime 已提交
92 93 94
// workSheetReader provides function to get the pointer to the structure after
// deserialization by given worksheet index.
func (f *File) workSheetReader(sheet string) *xlsxWorksheet {
95
	name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
xurime's avatar
xurime 已提交
96 97 98 99 100 101 102 103 104
	worksheet := f.Sheet[name]
	if worksheet == nil {
		var xlsx xlsxWorksheet
		xml.Unmarshal([]byte(f.readXML(name)), &xlsx)
		if f.checked == nil {
			f.checked = make(map[string]bool)
		}
		ok := f.checked[name]
		if !ok {
105
			checkSheet(&xlsx)
xurime's avatar
xurime 已提交
106 107 108 109 110
			checkRow(&xlsx)
			f.checked[name] = true
		}
		f.Sheet[name] = &xlsx
		worksheet = f.Sheet[name]
111
	}
xurime's avatar
xurime 已提交
112 113
	return worksheet
}
114

xurime's avatar
xurime 已提交
115 116 117 118
// SetCellInt provides function to set int type value of a cell.
func (f *File) SetCellInt(sheet, axis string, value int) {
	xlsx := f.workSheetReader(sheet)
	axis = strings.ToUpper(axis)
119 120 121 122 123 124 125
	if xlsx.MergeCells != nil {
		for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
			if checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref) {
				axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
			}
		}
	}
126 127
	col := string(strings.Map(letterOnlyMapF, axis))
	row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
xurime's avatar
xurime 已提交
128 129 130 131 132 133
	xAxis := row - 1
	yAxis := titleToNumber(col)

	rows := xAxis + 1
	cell := yAxis + 1

xurime's avatar
xurime 已提交
134 135
	completeRow(xlsx, rows, cell)
	completeCol(xlsx, rows, cell)
xurime's avatar
xurime 已提交
136 137 138 139 140

	xlsx.SheetData.Row[xAxis].C[yAxis].T = ""
	xlsx.SheetData.Row[xAxis].C[yAxis].V = strconv.Itoa(value)
}

141 142
// SetCellStr provides function to set string type value of a cell. Total number
// of characters that a cell can contain 32767 characters.
143
func (f *File) SetCellStr(sheet, axis, value string) {
xurime's avatar
xurime 已提交
144
	xlsx := f.workSheetReader(sheet)
xurime's avatar
xurime 已提交
145
	axis = strings.ToUpper(axis)
146 147 148 149 150 151 152
	if xlsx.MergeCells != nil {
		for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
			if checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref) {
				axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
			}
		}
	}
153 154 155
	if len(value) > 32767 {
		value = value[0:32767]
	}
156 157
	col := string(strings.Map(letterOnlyMapF, axis))
	row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
xurime's avatar
xurime 已提交
158 159 160 161 162 163
	xAxis := row - 1
	yAxis := titleToNumber(col)

	rows := xAxis + 1
	cell := yAxis + 1

xurime's avatar
xurime 已提交
164 165
	completeRow(xlsx, rows, cell)
	completeCol(xlsx, rows, cell)
xurime's avatar
xurime 已提交
166

167 168 169 170 171 172 173 174 175
	// Leading space(s) character detection.
	if len(value) > 0 {
		if value[0] == 32 {
			xlsx.SheetData.Row[xAxis].C[yAxis].XMLSpace = xml.Attr{
				Name:  xml.Name{Space: NameSpaceXML, Local: "space"},
				Value: "preserve",
			}
		}
	}
176
	xlsx.SheetData.Row[xAxis].C[yAxis].T = "str"
xurime's avatar
xurime 已提交
177 178 179
	xlsx.SheetData.Row[xAxis].C[yAxis].V = value
}

180 181
// SetCellDefault provides function to set string type value of a cell as
// default format without escaping the cell.
182
func (f *File) SetCellDefault(sheet, axis, value string) {
xurime's avatar
xurime 已提交
183
	xlsx := f.workSheetReader(sheet)
184
	axis = strings.ToUpper(axis)
185 186 187 188 189 190 191
	if xlsx.MergeCells != nil {
		for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
			if checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref) {
				axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
			}
		}
	}
192 193 194 195
	col := string(strings.Map(letterOnlyMapF, axis))
	row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
	xAxis := row - 1
	yAxis := titleToNumber(col)
S
Sharsie 已提交
196

197 198
	rows := xAxis + 1
	cell := yAxis + 1
S
Sharsie 已提交
199

xurime's avatar
xurime 已提交
200 201
	completeRow(xlsx, rows, cell)
	completeCol(xlsx, rows, cell)
S
Sharsie 已提交
202

203 204
	xlsx.SheetData.Row[xAxis].C[yAxis].T = ""
	xlsx.SheetData.Row[xAxis].C[yAxis].V = value
S
Sharsie 已提交
205 206
}

xurime's avatar
xurime 已提交
207
// Completion column element tags of XML in a sheet.
208
func completeCol(xlsx *xlsxWorksheet, row int, cell int) {
xurime's avatar
xurime 已提交
209 210 211 212 213 214 215
	if len(xlsx.SheetData.Row) < cell {
		for i := len(xlsx.SheetData.Row); i < cell; i++ {
			xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{
				R: i + 1,
			})
		}
	}
216
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
217 218 219 220
	for k, v := range xlsx.SheetData.Row {
		if len(v.C) < cell {
			start := len(v.C)
			for iii := start; iii < cell; iii++ {
221 222
				buffer.WriteString(toAlphaString(iii + 1))
				buffer.WriteString(strconv.Itoa(k + 1))
xurime's avatar
xurime 已提交
223
				xlsx.SheetData.Row[k].C = append(xlsx.SheetData.Row[k].C, xlsxC{
224
					R: buffer.String(),
xurime's avatar
xurime 已提交
225
				})
226
				buffer.Reset()
xurime's avatar
xurime 已提交
227 228 229 230 231
			}
		}
	}
}

232 233 234
// completeRow provides function to check and fill each column element for a
// single row and make that is continuous in a worksheet of XML by given row
// index and axis.
235
func completeRow(xlsx *xlsxWorksheet, row, cell int) {
236 237 238 239 240 241
	currentRows := len(xlsx.SheetData.Row)
	if currentRows > 1 {
		lastRow := xlsx.SheetData.Row[currentRows-1].R
		if lastRow >= row {
			row = lastRow
		}
242
	}
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	for i := currentRows; i < row; i++ {
		xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{
			R: i + 1,
		})
	}
	buffer := bytes.Buffer{}
	for ii := currentRows; ii < row; ii++ {
		start := len(xlsx.SheetData.Row[ii].C)
		if start == 0 {
			for iii := start; iii < cell; iii++ {
				buffer.WriteString(toAlphaString(iii + 1))
				buffer.WriteString(strconv.Itoa(ii + 1))
				xlsx.SheetData.Row[ii].C = append(xlsx.SheetData.Row[ii].C, xlsxC{
					R: buffer.String(),
				})
				buffer.Reset()
			}
		}
	}
}

// checkSheet provides function to fill each row element and make that is
// continuous in a worksheet of XML.
func checkSheet(xlsx *xlsxWorksheet) {
	row := len(xlsx.SheetData.Row)
	if row > 1 {
		lastRow := xlsx.SheetData.Row[row-1].R
		if lastRow >= row {
			row = lastRow
		}
	}
274 275 276 277 278 279 280 281 282 283
	sheetData := xlsxSheetData{}
	existsRows := map[int]int{}
	for k, v := range xlsx.SheetData.Row {
		existsRows[v.R] = k
	}
	for i := 0; i < row; i++ {
		_, ok := existsRows[i+1]
		if ok {
			sheetData.Row = append(sheetData.Row, xlsx.SheetData.Row[existsRows[i+1]])
			continue
xurime's avatar
xurime 已提交
284
		}
285 286 287 288 289
		sheetData.Row = append(sheetData.Row, xlsxRow{
			R: i + 1,
		})
	}
	xlsx.SheetData = sheetData
xurime's avatar
xurime 已提交
290 291
}

292 293
// replaceWorkSheetsRelationshipsNameSpace provides function to replace
// xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
294
// Office Excel 2007.
xurime's avatar
xurime 已提交
295 296 297 298 299 300 301
func replaceWorkSheetsRelationshipsNameSpace(workbookMarshal string) string {
	oldXmlns := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
	newXmlns := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main">`
	workbookMarshal = strings.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
	return workbookMarshal
}

302 303
// checkRow provides function to check and fill each column element for all rows
// and make that is continuous in a worksheet of XML. For example:
xurime's avatar
xurime 已提交
304
//
305 306 307 308 309 310
//    <row r="15" spans="1:22" x14ac:dyDescent="0.2">
//        <c r="A15" s="2" />
//        <c r="B15" s="2" />
//        <c r="F15" s="1" />
//        <c r="G15" s="1" />
//    </row>
xurime's avatar
xurime 已提交
311 312 313
//
// in this case, we should to change it to
//
314 315 316 317 318 319 320 321 322
//    <row r="15" spans="1:22" x14ac:dyDescent="0.2">
//        <c r="A15" s="2" />
//        <c r="B15" s="2" />
//        <c r="C15" s="2" />
//        <c r="D15" s="2" />
//        <c r="E15" s="2" />
//        <c r="F15" s="1" />
//        <c r="G15" s="1" />
//    </row>
xurime's avatar
xurime 已提交
323
//
324 325
// Noteice: this method could be very slow for large spreadsheets (more than
// 3000 rows one sheet).
326
func checkRow(xlsx *xlsxWorksheet) {
327
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
328 329
	for k, v := range xlsx.SheetData.Row {
		lenCol := len(v.C)
xurime's avatar
xurime 已提交
330 331 332
		if lenCol < 1 {
			continue
		}
333 334
		endR := string(strings.Map(letterOnlyMapF, v.C[lenCol-1].R))
		endRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, v.C[lenCol-1].R))
335
		endCol := titleToNumber(endR) + 1
xurime's avatar
xurime 已提交
336 337 338 339 340
		if lenCol < endCol {
			oldRow := xlsx.SheetData.Row[k].C
			xlsx.SheetData.Row[k].C = xlsx.SheetData.Row[k].C[:0]
			tmp := []xlsxC{}
			for i := 0; i <= endCol; i++ {
341 342
				buffer.WriteString(toAlphaString(i + 1))
				buffer.WriteString(strconv.Itoa(endRow))
xurime's avatar
xurime 已提交
343
				tmp = append(tmp, xlsxC{
344
					R: buffer.String(),
xurime's avatar
xurime 已提交
345
				})
346
				buffer.Reset()
xurime's avatar
xurime 已提交
347 348 349
			}
			xlsx.SheetData.Row[k].C = tmp
			for _, y := range oldRow {
350
				colAxis := titleToNumber(string(strings.Map(letterOnlyMapF, y.R)))
xurime's avatar
xurime 已提交
351 352 353 354 355
				xlsx.SheetData.Row[k].C[colAxis] = y
			}
		}
	}
}
356

357 358
// 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
359 360
// cell have a linked value. Reference
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating?forum=excel
361 362 363 364 365 366
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
367 368 369 370 371 372
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//            <v>100</v>
//         </c>
//    </row>
373 374 375
//
// to
//
376 377 378 379 380
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//        </c>
//    </row>
381
//
382 383
func (f *File) UpdateLinkedValue() {
	for i := 1; i <= f.SheetCount; i++ {
xurime's avatar
xurime 已提交
384
		xlsx := f.workSheetReader("sheet" + strconv.Itoa(i))
385 386
		for indexR, row := range xlsx.SheetData.Row {
			for indexC, col := range row.C {
387
				if col.F != nil && col.V != "" {
388 389
					xlsx.SheetData.Row[indexR].C[indexC].V = ""
					xlsx.SheetData.Row[indexR].C[indexC].T = ""
390 391 392 393 394
				}
			}
		}
	}
}