2019-01-01 13:20:14 +08:00
|
|
|
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
|
2018-09-14 00:44:23 +08:00
|
|
|
// this source code is governed by a BSD-style license that can be found in
|
|
|
|
// the LICENSE file.
|
2018-12-05 00:27:19 +08:00
|
|
|
|
2018-09-14 00:44:23 +08:00
|
|
|
// 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.
|
2018-09-14 00:58:48 +08:00
|
|
|
//
|
|
|
|
// See https://xuri.me/excelize for more information about this package.
|
2016-08-30 11:51:31 +08:00
|
|
|
package excelize
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/zip"
|
2016-09-04 19:25:31 +08:00
|
|
|
"bytes"
|
2016-08-30 11:51:31 +08:00
|
|
|
"encoding/xml"
|
2019-04-15 11:22:57 +08:00
|
|
|
"fmt"
|
2017-02-17 02:25:55 +08:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2016-08-30 11:51:31 +08:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2017-01-18 16:05:01 +08:00
|
|
|
// File define a populated XLSX file struct.
|
2016-09-05 16:37:15 +08:00
|
|
|
type File struct {
|
2019-02-25 00:29:58 +08:00
|
|
|
checked map[string]bool
|
|
|
|
sheetMap map[string]string
|
|
|
|
CalcChain *xlsxCalcChain
|
|
|
|
Comments map[string]*xlsxComments
|
|
|
|
ContentTypes *xlsxTypes
|
2019-02-25 22:14:34 +08:00
|
|
|
DrawingRels map[string]*xlsxWorkbookRels
|
|
|
|
Drawings map[string]*xlsxWsDr
|
2019-02-25 00:29:58 +08:00
|
|
|
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
|
2019-02-26 14:21:44 +08:00
|
|
|
WorkSheetRels map[string]*xlsxWorkbookRels
|
2019-02-25 00:29:58 +08:00
|
|
|
XLSX map[string][]byte
|
2016-08-30 11:51:31 +08:00
|
|
|
}
|
|
|
|
|
2017-01-18 16:05:01 +08:00
|
|
|
// OpenFile take the name of an XLSX file and returns a populated XLSX file
|
|
|
|
// struct for it.
|
2016-09-06 21:20:24 +08:00
|
|
|
func OpenFile(filename string) (*File, error) {
|
2017-02-17 02:25:55 +08:00
|
|
|
file, err := os.Open(filename)
|
2016-09-06 21:20:24 +08:00
|
|
|
if err != nil {
|
2017-02-17 02:25:55 +08:00
|
|
|
return nil, err
|
|
|
|
}
|
2017-02-17 02:30:05 +08:00
|
|
|
defer file.Close()
|
2017-02-17 02:38:57 +08:00
|
|
|
f, err := OpenReader(file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
f.Path = filename
|
|
|
|
return f, nil
|
2017-02-17 02:30:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// OpenReader take an io.Reader and return a populated XLSX file.
|
|
|
|
func OpenReader(r io.Reader) (*File, error) {
|
|
|
|
b, err := ioutil.ReadAll(r)
|
2017-02-17 02:25:55 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-02-17 02:30:05 +08:00
|
|
|
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
|
2017-02-17 02:25:55 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-02-17 02:30:05 +08:00
|
|
|
|
2017-02-17 02:25:55 +08:00
|
|
|
file, sheetCount, err := ReadZipReader(zr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2016-08-30 11:51:31 +08:00
|
|
|
}
|
2017-10-27 22:05:00 +08:00
|
|
|
f := &File{
|
2019-02-25 00:29:58 +08:00
|
|
|
checked: make(map[string]bool),
|
|
|
|
Comments: make(map[string]*xlsxComments),
|
2019-02-25 22:14:34 +08:00
|
|
|
DrawingRels: make(map[string]*xlsxWorkbookRels),
|
|
|
|
Drawings: make(map[string]*xlsxWsDr),
|
2019-02-25 00:29:58 +08:00
|
|
|
Sheet: make(map[string]*xlsxWorksheet),
|
|
|
|
SheetCount: sheetCount,
|
|
|
|
DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
|
|
|
|
VMLDrawing: make(map[string]*vmlDrawing),
|
2019-02-26 14:21:44 +08:00
|
|
|
WorkSheetRels: make(map[string]*xlsxWorkbookRels),
|
2019-02-25 00:29:58 +08:00
|
|
|
XLSX: file,
|
2017-10-27 22:05:00 +08:00
|
|
|
}
|
2019-02-25 00:29:58 +08:00
|
|
|
f.CalcChain = f.calcChainReader()
|
2017-10-27 22:05:00 +08:00
|
|
|
f.sheetMap = f.getSheetMap()
|
|
|
|
f.Styles = f.stylesReader()
|
2018-07-07 15:59:48 +08:00
|
|
|
f.Theme = f.themeReader()
|
2017-10-27 22:05:00 +08:00
|
|
|
return f, nil
|
2016-08-30 11:51:31 +08:00
|
|
|
}
|
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// setDefaultTimeStyle provides a function to set default numbers format for
|
2018-01-05 09:39:31 +08:00
|
|
|
// time.Time type cell value by given worksheet name, cell coordinates and
|
|
|
|
// number format code.
|
2019-03-23 20:08:06 +08:00
|
|
|
func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
|
|
|
|
s, err := f.GetCellStyle(sheet, axis)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if s == 0 {
|
2018-01-05 09:39:31 +08:00
|
|
|
style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`)
|
2017-06-29 19:41:00 +08:00
|
|
|
f.SetCellStyle(sheet, axis, axis, style)
|
2017-06-23 21:07:30 +08:00
|
|
|
}
|
2019-03-23 20:08:06 +08:00
|
|
|
return err
|
2017-06-23 21:07:30 +08:00
|
|
|
}
|
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// workSheetReader provides a function to get the pointer to the structure
|
|
|
|
// after deserialization by given worksheet name.
|
2019-04-15 11:22:57 +08:00
|
|
|
func (f *File) workSheetReader(sheet string) (*xlsxWorksheet, error) {
|
2017-10-18 19:07:35 +08:00
|
|
|
name, ok := f.sheetMap[trimSheetName(sheet)]
|
2017-09-13 22:00:33 +08:00
|
|
|
if !ok {
|
2019-04-15 11:22:57 +08:00
|
|
|
return nil, fmt.Errorf("Sheet %s is not exist", sheet)
|
2017-09-13 22:00:33 +08:00
|
|
|
}
|
2017-06-29 13:28:44 +08:00
|
|
|
if f.Sheet[name] == nil {
|
2017-03-12 20:38:26 +08:00
|
|
|
var xlsx xlsxWorksheet
|
2018-10-17 00:28:31 +08:00
|
|
|
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(name)), &xlsx)
|
2017-03-12 20:38:26 +08:00
|
|
|
if f.checked == nil {
|
|
|
|
f.checked = make(map[string]bool)
|
|
|
|
}
|
|
|
|
ok := f.checked[name]
|
|
|
|
if !ok {
|
2017-03-31 17:14:35 +08:00
|
|
|
checkSheet(&xlsx)
|
2017-03-12 20:38:26 +08:00
|
|
|
checkRow(&xlsx)
|
|
|
|
f.checked[name] = true
|
|
|
|
}
|
|
|
|
f.Sheet[name] = &xlsx
|
2017-02-12 19:03:24 +08:00
|
|
|
}
|
2019-04-15 11:22:57 +08:00
|
|
|
return f.Sheet[name], nil
|
2017-03-12 20:38:26 +08:00
|
|
|
}
|
2017-02-12 19:03:24 +08:00
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// checkSheet provides a function to fill each row element and make that is
|
2017-03-31 17:14:35 +08:00
|
|
|
// continuous in a worksheet of XML.
|
|
|
|
func checkSheet(xlsx *xlsxWorksheet) {
|
|
|
|
row := len(xlsx.SheetData.Row)
|
2017-05-24 14:17:35 +08:00
|
|
|
if row >= 1 {
|
2017-03-31 17:14:35 +08:00
|
|
|
lastRow := xlsx.SheetData.Row[row-1].R
|
|
|
|
if lastRow >= row {
|
|
|
|
row = lastRow
|
|
|
|
}
|
|
|
|
}
|
2016-12-20 14:40:36 +08:00
|
|
|
sheetData := xlsxSheetData{}
|
|
|
|
existsRows := map[int]int{}
|
2017-10-16 10:42:43 +08:00
|
|
|
for k := range xlsx.SheetData.Row {
|
|
|
|
existsRows[xlsx.SheetData.Row[k].R] = k
|
2016-12-20 14:40:36 +08:00
|
|
|
}
|
|
|
|
for i := 0; i < row; i++ {
|
|
|
|
_, ok := existsRows[i+1]
|
|
|
|
if ok {
|
|
|
|
sheetData.Row = append(sheetData.Row, xlsx.SheetData.Row[existsRows[i+1]])
|
2017-09-30 17:07:59 +08:00
|
|
|
} else {
|
|
|
|
sheetData.Row = append(sheetData.Row, xlsxRow{
|
|
|
|
R: i + 1,
|
|
|
|
})
|
2016-08-30 11:51:31 +08:00
|
|
|
}
|
2016-12-20 14:40:36 +08:00
|
|
|
}
|
|
|
|
xlsx.SheetData = sheetData
|
2016-08-30 11:51:31 +08:00
|
|
|
}
|
|
|
|
|
2018-08-06 10:21:24 +08:00
|
|
|
// replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
|
2017-03-31 17:14:35 +08:00
|
|
|
// xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
|
2017-01-18 16:05:01 +08:00
|
|
|
// Office Excel 2007.
|
2018-05-07 16:12:51 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-09-07 20:09:02 +08:00
|
|
|
// 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
|
2017-01-18 16:05:01 +08:00
|
|
|
// cell have a linked value. Reference
|
2019-01-06 14:12:31 +08:00
|
|
|
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
|
2016-09-06 21:20:24 +08:00
|
|
|
//
|
|
|
|
// Notice: after open XLSX file Excel will be update linked value and generate
|
|
|
|
// new value and will prompt save file or not.
|
|
|
|
//
|
|
|
|
// For example:
|
|
|
|
//
|
2016-09-09 19:39:41 +08:00
|
|
|
// <row r="19" spans="2:2">
|
|
|
|
// <c r="B19">
|
|
|
|
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
|
|
|
|
// <v>100</v>
|
|
|
|
// </c>
|
|
|
|
// </row>
|
2016-09-06 21:20:24 +08:00
|
|
|
//
|
|
|
|
// to
|
|
|
|
//
|
2016-09-09 19:39:41 +08:00
|
|
|
// <row r="19" spans="2:2">
|
|
|
|
// <c r="B19">
|
|
|
|
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
|
|
|
|
// </c>
|
|
|
|
// </row>
|
2017-01-17 19:06:42 +08:00
|
|
|
//
|
2019-04-15 11:22:57 +08:00
|
|
|
func (f *File) UpdateLinkedValue() error {
|
2017-09-13 22:00:33 +08:00
|
|
|
for _, name := range f.GetSheetMap() {
|
2019-04-15 11:22:57 +08:00
|
|
|
xlsx, err := f.workSheetReader(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-10-16 10:42:43 +08:00
|
|
|
for indexR := range xlsx.SheetData.Row {
|
|
|
|
for indexC, col := range xlsx.SheetData.Row[indexR].C {
|
2017-01-18 14:47:23 +08:00
|
|
|
if col.F != nil && col.V != "" {
|
2017-01-17 19:06:42 +08:00
|
|
|
xlsx.SheetData.Row[indexR].C[indexC].V = ""
|
|
|
|
xlsx.SheetData.Row[indexR].C[indexC].T = ""
|
2016-09-06 21:20:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-15 11:22:57 +08:00
|
|
|
return nil
|
2016-09-06 21:20:24 +08:00
|
|
|
}
|
2017-07-24 10:26:02 +08:00
|
|
|
|
Huge refactorig for consistent col/row numbering (#356)
* Huge refactorig for consistent col/row numbering
Started from simply changing ToALphaString()/TitleToNumber() logic and related fixes.
But have to go deeper, do fixes, after do related fixes and again and again.
Major improvements:
1. Tests made stronger again (But still be weak).
2. "Empty" returns for incorrect input replaces with panic.
3. Check for correct col/row/cell naming & addressing by default.
4. Removed huge amount of duplicated code.
5. Removed ToALphaString(), TitleToNumber() and it helpers functions at all,
and replaced with SplitCellName(), JoinCellName(), ColumnNameToNumber(), ColumnNumberToName(), CellNameToCoordinates(), CoordinatesToCellName().
6. Minor fixes for internal variable naming for code readability (ex. col, row for input params, colIdx, rowIdx for slice indexes etc).
* Formatting fixes
2019-03-20 00:14:41 +08:00
|
|
|
// GetMergeCells provides a function to get all merged cells from a worksheet currently.
|
2019-04-15 11:22:57 +08:00
|
|
|
func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
|
Huge refactorig for consistent col/row numbering (#356)
* Huge refactorig for consistent col/row numbering
Started from simply changing ToALphaString()/TitleToNumber() logic and related fixes.
But have to go deeper, do fixes, after do related fixes and again and again.
Major improvements:
1. Tests made stronger again (But still be weak).
2. "Empty" returns for incorrect input replaces with panic.
3. Check for correct col/row/cell naming & addressing by default.
4. Removed huge amount of duplicated code.
5. Removed ToALphaString(), TitleToNumber() and it helpers functions at all,
and replaced with SplitCellName(), JoinCellName(), ColumnNameToNumber(), ColumnNumberToName(), CellNameToCoordinates(), CoordinatesToCellName().
6. Minor fixes for internal variable naming for code readability (ex. col, row for input params, colIdx, rowIdx for slice indexes etc).
* Formatting fixes
2019-03-20 00:14:41 +08:00
|
|
|
var mergeCells []MergeCell
|
2019-04-15 11:22:57 +08:00
|
|
|
xlsx, err := f.workSheetReader(sheet)
|
|
|
|
if err != nil {
|
|
|
|
return mergeCells, err
|
|
|
|
}
|
2017-07-24 10:26:02 +08:00
|
|
|
if xlsx.MergeCells != nil {
|
Huge refactorig for consistent col/row numbering (#356)
* Huge refactorig for consistent col/row numbering
Started from simply changing ToALphaString()/TitleToNumber() logic and related fixes.
But have to go deeper, do fixes, after do related fixes and again and again.
Major improvements:
1. Tests made stronger again (But still be weak).
2. "Empty" returns for incorrect input replaces with panic.
3. Check for correct col/row/cell naming & addressing by default.
4. Removed huge amount of duplicated code.
5. Removed ToALphaString(), TitleToNumber() and it helpers functions at all,
and replaced with SplitCellName(), JoinCellName(), ColumnNameToNumber(), ColumnNumberToName(), CellNameToCoordinates(), CoordinatesToCellName().
6. Minor fixes for internal variable naming for code readability (ex. col, row for input params, colIdx, rowIdx for slice indexes etc).
* Formatting fixes
2019-03-20 00:14:41 +08:00
|
|
|
mergeCells = make([]MergeCell, 0, len(xlsx.MergeCells.Cells))
|
2017-07-24 10:26:02 +08:00
|
|
|
|
Huge refactorig for consistent col/row numbering (#356)
* Huge refactorig for consistent col/row numbering
Started from simply changing ToALphaString()/TitleToNumber() logic and related fixes.
But have to go deeper, do fixes, after do related fixes and again and again.
Major improvements:
1. Tests made stronger again (But still be weak).
2. "Empty" returns for incorrect input replaces with panic.
3. Check for correct col/row/cell naming & addressing by default.
4. Removed huge amount of duplicated code.
5. Removed ToALphaString(), TitleToNumber() and it helpers functions at all,
and replaced with SplitCellName(), JoinCellName(), ColumnNameToNumber(), ColumnNumberToName(), CellNameToCoordinates(), CoordinatesToCellName().
6. Minor fixes for internal variable naming for code readability (ex. col, row for input params, colIdx, rowIdx for slice indexes etc).
* Formatting fixes
2019-03-20 00:14:41 +08:00
|
|
|
for i := range xlsx.MergeCells.Cells {
|
2018-12-19 19:54:38 +08:00
|
|
|
ref := xlsx.MergeCells.Cells[i].Ref
|
|
|
|
axis := strings.Split(ref, ":")[0]
|
2019-03-23 20:08:06 +08:00
|
|
|
val, _ := f.GetCellValue(sheet, axis)
|
|
|
|
mergeCells = append(mergeCells, []string{ref, val})
|
2018-12-19 19:54:38 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-15 11:22:57 +08:00
|
|
|
return mergeCells, err
|
2018-12-19 19:54:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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]
|
|
|
|
}
|