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

import (
	"archive/zip"
5
	"bytes"
xurime's avatar
xurime 已提交
6 7 8 9 10
	"encoding/xml"
	"strconv"
	"strings"
)

11 12
// File define a populated xlsx.File struct.
type File struct {
13 14 15
	XLSX       map[string]string
	Path       string
	SheetCount int
xurime's avatar
xurime 已提交
16 17
}

xurime's avatar
xurime 已提交
18
// OpenFile take the name of an XLSX file and returns a populated
xurime's avatar
xurime 已提交
19
// xlsx.File struct for it.
20
func OpenFile(filename string) (*File, error) {
xurime's avatar
xurime 已提交
21
	var f *zip.ReadCloser
22
	var err error
23
	file := make(map[string]string)
24 25 26 27
	sheetCount := 0
	f, err = zip.OpenReader(filename)
	if err != nil {
		return &File{}, err
xurime's avatar
xurime 已提交
28
	}
29
	file, sheetCount, _ = ReadZip(f)
30 31 32 33 34
	return &File{
		XLSX:       file,
		Path:       filename,
		SheetCount: sheetCount,
	}, nil
xurime's avatar
xurime 已提交
35 36
}

37 38 39 40 41 42 43 44 45 46 47 48 49 50
// SetCellValue provide function to set int or string type value of a cell
func (f *File) SetCellValue(sheet string, axis string, value interface{}) {
	switch t := value.(type) {
	case int, int8, int16, int32, int64, float32, float64:
		f.SetCellInt(sheet, axis, value.(int))
	case string:
		f.SetCellStr(sheet, axis, t)
	case []byte:
		f.SetCellStr(sheet, axis, string(t))
	default:
		f.SetCellStr(sheet, axis, ``)
	}
}

xurime's avatar
xurime 已提交
51
// SetCellInt provide function to set int type value of a cell
52
func (f *File) SetCellInt(sheet string, axis string, value int) {
xurime's avatar
xurime 已提交
53 54
	axis = strings.ToUpper(axis)
	var xlsx xlsxWorksheet
55 56
	col := string(strings.Map(letterOnlyMapF, axis))
	row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
xurime's avatar
xurime 已提交
57 58 59
	xAxis := row - 1
	yAxis := titleToNumber(col)

xurime's avatar
xurime 已提交
60
	name := `xl/worksheets/` + strings.ToLower(sheet) + `.xml`
61
	xml.Unmarshal([]byte(f.readXML(name)), &xlsx)
xurime's avatar
xurime 已提交
62 63 64 65 66 67 68 69 70 71

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

	xlsx = completeRow(xlsx, rows, cell)
	xlsx = completeCol(xlsx, rows, cell)

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

72
	output, _ := xml.Marshal(xlsx)
73
	f.saveFileList(name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output))))
xurime's avatar
xurime 已提交
74 75
}

xurime's avatar
xurime 已提交
76
// SetCellStr provide function to set string type value of a cell
77
func (f *File) SetCellStr(sheet string, axis string, value string) {
xurime's avatar
xurime 已提交
78 79
	axis = strings.ToUpper(axis)
	var xlsx xlsxWorksheet
80 81
	col := string(strings.Map(letterOnlyMapF, axis))
	row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
xurime's avatar
xurime 已提交
82 83 84
	xAxis := row - 1
	yAxis := titleToNumber(col)

xurime's avatar
xurime 已提交
85
	name := `xl/worksheets/` + strings.ToLower(sheet) + `.xml`
86
	xml.Unmarshal([]byte(f.readXML(name)), &xlsx)
xurime's avatar
xurime 已提交
87 88 89 90 91 92 93

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

	xlsx = completeRow(xlsx, rows, cell)
	xlsx = completeCol(xlsx, rows, cell)

94
	xlsx.SheetData.Row[xAxis].C[yAxis].T = `str`
xurime's avatar
xurime 已提交
95 96
	xlsx.SheetData.Row[xAxis].C[yAxis].V = value

97
	output, _ := xml.Marshal(xlsx)
98
	f.saveFileList(name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output))))
xurime's avatar
xurime 已提交
99 100 101 102 103 104 105 106 107 108 109
}

// Completion column element tags of XML in a sheet
func completeCol(xlsx xlsxWorksheet, row int, cell int) xlsxWorksheet {
	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,
			})
		}
	}
110
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
111 112 113 114
	for k, v := range xlsx.SheetData.Row {
		if len(v.C) < cell {
			start := len(v.C)
			for iii := start; iii < cell; iii++ {
115 116
				buffer.WriteString(toAlphaString(iii + 1))
				buffer.WriteString(strconv.Itoa(k + 1))
xurime's avatar
xurime 已提交
117
				xlsx.SheetData.Row[k].C = append(xlsx.SheetData.Row[k].C, xlsxC{
118
					R: buffer.String(),
xurime's avatar
xurime 已提交
119
				})
120
				buffer.Reset()
xurime's avatar
xurime 已提交
121 122 123 124 125 126 127 128 129 130 131 132 133 134
			}
		}
	}
	return xlsx
}

// Completion row element tags of XML in a sheet
func completeRow(xlsx xlsxWorksheet, row int, cell int) xlsxWorksheet {
	if len(xlsx.SheetData.Row) < row {
		for i := len(xlsx.SheetData.Row); i < row; i++ {
			xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{
				R: i + 1,
			})
		}
135
		buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
136 137 138 139
		for ii := 0; ii < row; ii++ {
			start := len(xlsx.SheetData.Row[ii].C)
			if start == 0 {
				for iii := start; iii < cell; iii++ {
140 141
					buffer.WriteString(toAlphaString(iii + 1))
					buffer.WriteString(strconv.Itoa(ii + 1))
xurime's avatar
xurime 已提交
142
					xlsx.SheetData.Row[ii].C = append(xlsx.SheetData.Row[ii].C, xlsxC{
143
						R: buffer.String(),
xurime's avatar
xurime 已提交
144
					})
145
					buffer.Reset()
xurime's avatar
xurime 已提交
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
				}
			}
		}
	}
	return xlsx
}

// Replace xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Office Excel 2007
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)
	workbookMarshal = strings.Replace(workbookMarshal, `></sheetPr>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></dimension>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></selection>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></sheetFormatPr>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></printOptions>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></pageSetup>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></pageMargins>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></headerFooter>`, ` />`, -1)
	workbookMarshal = strings.Replace(workbookMarshal, `></drawing>`, ` />`, -1)
	return workbookMarshal
}

// Check XML tags and fix discontinuous case, for example:
//
172 173 174 175 176 177
//    <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 已提交
178 179 180
//
// in this case, we should to change it to
//
181 182 183 184 185 186 187 188 189
//    <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 已提交
190
//
191
// Noteice: this method could be very slow for large spreadsheets (more than 3000 rows one sheet).
xurime's avatar
xurime 已提交
192
func checkRow(xlsx xlsxWorksheet) xlsxWorksheet {
193
	buffer := bytes.Buffer{}
xurime's avatar
xurime 已提交
194 195
	for k, v := range xlsx.SheetData.Row {
		lenCol := len(v.C)
xurime's avatar
xurime 已提交
196 197 198
		if lenCol < 1 {
			continue
		}
199 200
		endR := string(strings.Map(letterOnlyMapF, v.C[lenCol-1].R))
		endRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, v.C[lenCol-1].R))
201
		endCol := titleToNumber(endR) + 1
xurime's avatar
xurime 已提交
202 203 204 205 206
		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++ {
207 208
				buffer.WriteString(toAlphaString(i + 1))
				buffer.WriteString(strconv.Itoa(endRow))
xurime's avatar
xurime 已提交
209
				tmp = append(tmp, xlsxC{
210
					R: buffer.String(),
xurime's avatar
xurime 已提交
211
				})
212
				buffer.Reset()
xurime's avatar
xurime 已提交
213 214 215
			}
			xlsx.SheetData.Row[k].C = tmp
			for _, y := range oldRow {
216
				colAxis := titleToNumber(string(strings.Map(letterOnlyMapF, y.R)))
xurime's avatar
xurime 已提交
217 218 219 220 221 222
				xlsx.SheetData.Row[k].C[colAxis] = y
			}
		}
	}
	return xlsx
}
223

224 225
// 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
226
// 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
227 228 229 230 231 232
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
233 234 235 236 237 238
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//            <v>100</v>
//         </c>
//    </row>
239 240 241
//
// to
//
242 243 244 245 246
//    <row r="19" spans="2:2">
//        <c r="B19">
//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>
//        </c>
//    </row>
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
func (f *File) UpdateLinkedValue() {
	for i := 1; i <= f.SheetCount; i++ {
		var xlsx xlsxWorksheet
		name := `xl/worksheets/sheet` + strconv.Itoa(i) + `.xml`
		xml.Unmarshal([]byte(f.readXML(name)), &xlsx)
		for indexR, row := range xlsx.SheetData.Row {
			for indexC, col := range row.C {
				if col.F != nil && col.V != `` {
					xlsx.SheetData.Row[indexR].C[indexC].V = ``
					xlsx.SheetData.Row[indexR].C[indexC].T = ``
				}
			}
		}
		output, _ := xml.Marshal(xlsx)
		f.saveFileList(name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output))))
	}
}