| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 | // Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of// this source code is governed by a BSD-style license that can be found in// the LICENSE file.// Package excelize providing a set of functions that allow you to write to// and read from XLSX / XLSM / XLTM files. Supports reading and writing// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports// complex components by high compatibility, and provided streaming API for// generating or reading data from a worksheet with huge amounts of data. This// library needs Go version 1.15 or later.//// See https://xuri.me/excelize for more information about this package.package excelizeimport (	"archive/zip"	"bytes"	"encoding/xml"	"fmt"	"io"	"io/ioutil"	"os"	"path"	"strconv"	"strings"	"sync"	"golang.org/x/net/html/charset")// File define a populated spreadsheet file struct.type File struct {	sync.Mutex	options          *Options	xmlAttr          map[string][]xml.Attr	checked          map[string]bool	sheetMap         map[string]string	streams          map[string]*StreamWriter	CalcChain        *xlsxCalcChain	Comments         map[string]*xlsxComments	ContentTypes     *xlsxTypes	Drawings         sync.Map	Path             string	SharedStrings    *xlsxSST	sharedStringsMap map[string]int	Sheet            sync.Map	SheetCount       int	Styles           *xlsxStyleSheet	Theme            *xlsxTheme	DecodeVMLDrawing map[string]*decodeVmlDrawing	VMLDrawing       map[string]*vmlDrawing	WorkBook         *xlsxWorkbook	Relationships    sync.Map	Pkg              sync.Map	CharsetReader    charsetTranscoderFn}type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)// Options define the options for open spreadsheet.type Options struct {	Password string}// OpenFile take the name of an spreadsheet file and returns a populated spreadsheet file struct// for it. For example, open spreadsheet with password protection:////    f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})//    if err != nil {//        return//    }//// Note that the excelize just support decrypt and not support encrypt currently, the spreadsheet// saved by Save and SaveAs will be without password unprotected.func OpenFile(filename string, opt ...Options) (*File, error) {	file, err := os.Open(filename)	if err != nil {		return nil, err	}	defer file.Close()	f, err := OpenReader(file, opt...)	if err != nil {		return nil, err	}	f.Path = filename	return f, nil}// newFile is object builderfunc newFile() *File {	return &File{		xmlAttr:          make(map[string][]xml.Attr),		checked:          make(map[string]bool),		sheetMap:         make(map[string]string),		Comments:         make(map[string]*xlsxComments),		Drawings:         sync.Map{},		sharedStringsMap: make(map[string]int),		Sheet:            sync.Map{},		DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),		VMLDrawing:       make(map[string]*vmlDrawing),		Relationships:    sync.Map{},		CharsetReader:    charset.NewReaderLabel,	}}// OpenReader read data stream from io.Reader and return a populated// spreadsheet file.func OpenReader(r io.Reader, opt ...Options) (*File, error) {	b, err := ioutil.ReadAll(r)	if err != nil {		return nil, err	}	f := newFile()	if bytes.Contains(b, oleIdentifier) && len(opt) > 0 {		for _, o := range opt {			f.options = &o		}		b, err = Decrypt(b, f.options)		if err != nil {			return nil, fmt.Errorf("decrypted file failed")		}	}	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))	if err != nil {		return nil, err	}	file, sheetCount, err := ReadZipReader(zr)	if err != nil {		return nil, err	}	f.SheetCount = sheetCount	for k, v := range file {		f.Pkg.Store(k, v)	}	f.CalcChain = f.calcChainReader()	f.sheetMap = f.getSheetMap()	f.Styles = f.stylesReader()	f.Theme = f.themeReader()	return f, nil}// CharsetTranscoder Set user defined codepage transcoder function for open// XLSX from non UTF-8 encoding.func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f }// Creates new XML decoder with charset reader.func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {	ret = xml.NewDecoder(rdr)	ret.CharsetReader = f.CharsetReader	return}// setDefaultTimeStyle provides a function to set default numbers format for// time.Time type cell value by given worksheet name, cell coordinates and// number format code.func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {	s, err := f.GetCellStyle(sheet, axis)	if err != nil {		return err	}	if s == 0 {		style, _ := f.NewStyle(&Style{NumFmt: format})		err = f.SetCellStyle(sheet, axis, axis, style)	}	return err}// workSheetReader provides a function to get the pointer to the structure// after deserialization by given worksheet name.func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {	f.Lock()	defer f.Unlock()	var (		name string		ok   bool	)	if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {		err = fmt.Errorf("sheet %s is not exist", sheet)		return	}	if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {		ws = worksheet.(*xlsxWorksheet)		return	}	if strings.HasPrefix(name, "xl/chartsheets") {		err = fmt.Errorf("sheet %s is chart sheet", sheet)		return	}	ws = new(xlsxWorksheet)	if _, ok := f.xmlAttr[name]; !ok {		d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name))))		f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)	}	if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))).		Decode(ws); err != nil && err != io.EOF {		err = fmt.Errorf("xml decode error: %s", err)		return	}	err = nil	if f.checked == nil {		f.checked = make(map[string]bool)	}	if ok = f.checked[name]; !ok {		checkSheet(ws)		if err = checkRow(ws); err != nil {			return		}		f.checked[name] = true	}	f.Sheet.Store(name, ws)	return}// checkSheet provides a function to fill each row element and make that is// continuous in a worksheet of XML.func checkSheet(ws *xlsxWorksheet) {	var row int	for _, r := range ws.SheetData.Row {		if r.R != 0 && r.R > row {			row = r.R			continue		}		if r.R != row {			row++		}	}	sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}	row = 0	for _, r := range ws.SheetData.Row {		if r.R == row {			sheetData.Row[r.R-1].C = append(sheetData.Row[r.R-1].C, r.C...)			continue		}		if r.R != 0 {			sheetData.Row[r.R-1] = r			row = r.R			continue		}		row++		r.R = row		sheetData.Row[row-1] = r	}	for i := 1; i <= row; i++ {		sheetData.Row[i-1].R = i	}	ws.SheetData = sheetData}// addRels provides a function to add relationships by given XML path,// relationship type, target and target mode.func (f *File) addRels(relPath, relType, target, targetMode string) int {	var uniqPart = map[string]string{		SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",	}	rels := f.relsReader(relPath)	if rels == nil {		rels = &xlsxRelationships{}	}	rels.Lock()	defer rels.Unlock()	var rID int	for idx, rel := range rels.Relationships {		ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))		if ID > rID {			rID = ID		}		if relType == rel.Type {			if partName, ok := uniqPart[rel.Type]; ok {				rels.Relationships[idx].Target = partName				return rID			}		}	}	rID++	var ID bytes.Buffer	ID.WriteString("rId")	ID.WriteString(strconv.Itoa(rID))	rels.Relationships = append(rels.Relationships, xlsxRelationship{		ID:         ID.String(),		Type:       relType,		Target:     target,		TargetMode: targetMode,	})	f.Relationships.Store(relPath, rels)	return rID}// 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// 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//// Notice: after open XLSX file Excel will be update linked value and generate// new value and will prompt save file or not.//// For example:////    <row r="19" spans="2:2">//        <c r="B19">//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>//            <v>100</v>//         </c>//    </row>//// to////    <row r="19" spans="2:2">//        <c r="B19">//            <f>SUM(Sheet2!D2,Sheet2!D11)</f>//        </c>//    </row>//func (f *File) UpdateLinkedValue() error {	wb := f.workbookReader()	// recalculate formulas	wb.CalcPr = nil	for _, name := range f.GetSheetList() {		xlsx, err := f.workSheetReader(name)		if err != nil {			if err.Error() == fmt.Sprintf("sheet %s is chart sheet", trimSheetName(name)) {				continue			}			return err		}		for indexR := range xlsx.SheetData.Row {			for indexC, col := range xlsx.SheetData.Row[indexR].C {				if col.F != nil && col.V != "" {					xlsx.SheetData.Row[indexR].C[indexC].V = ""					xlsx.SheetData.Row[indexR].C[indexC].T = ""				}			}		}	}	return nil}// AddVBAProject provides the method to add vbaProject.bin file which contains// functions and/or macros. The file extension should be .xlsm. For example:////    if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil {//        fmt.Println(err)//    }//    if err := f.AddVBAProject("vbaProject.bin"); err != nil {//        fmt.Println(err)//    }//    if err := f.SaveAs("macros.xlsm"); err != nil {//        fmt.Println(err)//    }//func (f *File) AddVBAProject(bin string) error {	var err error	// Check vbaProject.bin exists first.	if _, err = os.Stat(bin); os.IsNotExist(err) {		return fmt.Errorf("stat %s: no such file or directory", bin)	}	if path.Ext(bin) != ".bin" {		return ErrAddVBAProject	}	f.setContentTypePartVBAProjectExtensions()	wb := f.relsReader(f.getWorkbookRelsPath())	wb.Lock()	defer wb.Unlock()	var rID int	var ok bool	for _, rel := range wb.Relationships {		if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {			ok = true			continue		}		t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))		if t > rID {			rID = t		}	}	rID++	if !ok {		wb.Relationships = append(wb.Relationships, xlsxRelationship{			ID:     "rId" + strconv.Itoa(rID),			Target: "vbaProject.bin",			Type:   SourceRelationshipVBAProject,		})	}	file, _ := ioutil.ReadFile(bin)	f.Pkg.Store("xl/vbaProject.bin", file)	return err}// setContentTypePartVBAProjectExtensions provides a function to set the// content type for relationship parts and the main document part.func (f *File) setContentTypePartVBAProjectExtensions() {	var ok bool	content := f.contentTypesReader()	content.Lock()	defer content.Unlock()	for _, v := range content.Defaults {		if v.Extension == "bin" {			ok = true		}	}	for idx, o := range content.Overrides {		if o.PartName == "/xl/workbook.xml" {			content.Overrides[idx].ContentType = ContentTypeMacro		}	}	if !ok {		content.Defaults = append(content.Defaults, xlsxDefault{			Extension:   "bin",			ContentType: ContentTypeVBA,		})	}}
 |