excelize.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. // Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
  2. // this source code is governed by a BSD-style license that can be found in
  3. // the LICENSE file.
  4. // Package excelize providing a set of functions that allow you to write to
  5. // and read from XLSX / XLSM / XLTM files. Supports reading and writing
  6. // spreadsheet documents generated by Microsoft Exce™ 2007 and later. Supports
  7. // complex components by high compatibility, and provided streaming API for
  8. // generating or reading data from a worksheet with huge amounts of data. This
  9. // library needs Go version 1.10 or later.
  10. //
  11. // See https://xuri.me/excelize for more information about this package.
  12. package excelize
  13. import (
  14. "archive/zip"
  15. "bytes"
  16. "encoding/xml"
  17. "errors"
  18. "fmt"
  19. "io"
  20. "io/ioutil"
  21. "os"
  22. "path"
  23. "path/filepath"
  24. "strconv"
  25. "strings"
  26. "sync"
  27. "golang.org/x/net/html/charset"
  28. )
  29. // File define a populated spreadsheet file struct.
  30. type File struct {
  31. sync.Mutex
  32. options *Options
  33. xmlAttr map[string][]xml.Attr
  34. checked map[string]bool
  35. sheetMap map[string]string
  36. streams map[string]*StreamWriter
  37. CalcChain *xlsxCalcChain
  38. Comments map[string]*xlsxComments
  39. ContentTypes *xlsxTypes
  40. Drawings map[string]*xlsxWsDr
  41. Path string
  42. SharedStrings *xlsxSST
  43. sharedStringsMap map[string]int
  44. Sheet map[string]*xlsxWorksheet
  45. SheetCount int
  46. Styles *xlsxStyleSheet
  47. Theme *xlsxTheme
  48. DecodeVMLDrawing map[string]*decodeVmlDrawing
  49. VMLDrawing map[string]*vmlDrawing
  50. WorkBook *xlsxWorkbook
  51. Relationships map[string]*xlsxRelationships
  52. XLSX map[string][]byte
  53. CharsetReader charsetTranscoderFn
  54. }
  55. type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
  56. // Options define the options for open spreadsheet.
  57. type Options struct {
  58. Password string
  59. }
  60. // OpenFile take the name of an spreadsheet file and returns a populated spreadsheet file struct
  61. // for it. For example, open spreadsheet with password protection:
  62. //
  63. // f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
  64. // if err != nil {
  65. // return
  66. // }
  67. //
  68. // Note that the excelize just support decrypt and not support encrypt currently, the spreadsheet
  69. // saved by Save and SaveAs will be without password unprotected.
  70. func OpenFile(filename string, opt ...Options) (*File, error) {
  71. file, err := os.Open(filename)
  72. if err != nil {
  73. return nil, err
  74. }
  75. defer file.Close()
  76. f, err := OpenReader(file, opt...)
  77. if err != nil {
  78. return nil, err
  79. }
  80. f.Path = filename
  81. return f, nil
  82. }
  83. // newFile is object builder
  84. func newFile() *File {
  85. return &File{
  86. xmlAttr: make(map[string][]xml.Attr),
  87. checked: make(map[string]bool),
  88. sheetMap: make(map[string]string),
  89. Comments: make(map[string]*xlsxComments),
  90. Drawings: make(map[string]*xlsxWsDr),
  91. sharedStringsMap: make(map[string]int),
  92. Sheet: make(map[string]*xlsxWorksheet),
  93. DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
  94. VMLDrawing: make(map[string]*vmlDrawing),
  95. Relationships: make(map[string]*xlsxRelationships),
  96. CharsetReader: charset.NewReaderLabel,
  97. }
  98. }
  99. // OpenReader read data stream from io.Reader and return a populated
  100. // spreadsheet file.
  101. func OpenReader(r io.Reader, opt ...Options) (*File, error) {
  102. b, err := ioutil.ReadAll(r)
  103. if err != nil {
  104. return nil, err
  105. }
  106. f := newFile()
  107. if bytes.Contains(b, oleIdentifier) && len(opt) > 0 {
  108. for _, o := range opt {
  109. f.options = &o
  110. }
  111. b, err = Decrypt(b, f.options)
  112. if err != nil {
  113. return nil, fmt.Errorf("decrypted file failed")
  114. }
  115. }
  116. zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
  117. if err != nil {
  118. return nil, err
  119. }
  120. file, sheetCount, err := ReadZipReader(zr)
  121. if err != nil {
  122. return nil, err
  123. }
  124. f.SheetCount, f.XLSX = sheetCount, file
  125. f.CalcChain = f.calcChainReader()
  126. f.sheetMap = f.getSheetMap()
  127. f.Styles = f.stylesReader()
  128. f.Theme = f.themeReader()
  129. return f, nil
  130. }
  131. // CharsetTranscoder Set user defined codepage transcoder function for open
  132. // XLSX from non UTF-8 encoding.
  133. func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f }
  134. // Creates new XML decoder with charset reader.
  135. func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
  136. ret = xml.NewDecoder(rdr)
  137. ret.CharsetReader = f.CharsetReader
  138. return
  139. }
  140. // setDefaultTimeStyle provides a function to set default numbers format for
  141. // time.Time type cell value by given worksheet name, cell coordinates and
  142. // number format code.
  143. func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
  144. s, err := f.GetCellStyle(sheet, axis)
  145. if err != nil {
  146. return err
  147. }
  148. if s == 0 {
  149. style, _ := f.NewStyle(&Style{NumFmt: format})
  150. err = f.SetCellStyle(sheet, axis, axis, style)
  151. }
  152. return err
  153. }
  154. // workSheetReader provides a function to get the pointer to the structure
  155. // after deserialization by given worksheet name.
  156. func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
  157. f.Lock()
  158. defer f.Unlock()
  159. var (
  160. name string
  161. ok bool
  162. )
  163. if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {
  164. err = fmt.Errorf("sheet %s is not exist", sheet)
  165. return
  166. }
  167. if xlsx = f.Sheet[name]; f.Sheet[name] == nil {
  168. if strings.HasPrefix(name, "xl/chartsheets") {
  169. err = fmt.Errorf("sheet %s is chart sheet", sheet)
  170. return
  171. }
  172. xlsx = new(xlsxWorksheet)
  173. if _, ok := f.xmlAttr[name]; !ok {
  174. d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name))))
  175. f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)
  176. }
  177. if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))).
  178. Decode(xlsx); err != nil && err != io.EOF {
  179. err = fmt.Errorf("xml decode error: %s", err)
  180. return
  181. }
  182. err = nil
  183. if f.checked == nil {
  184. f.checked = make(map[string]bool)
  185. }
  186. if ok = f.checked[name]; !ok {
  187. checkSheet(xlsx)
  188. if err = checkRow(xlsx); err != nil {
  189. return
  190. }
  191. f.checked[name] = true
  192. }
  193. f.Sheet[name] = xlsx
  194. }
  195. return
  196. }
  197. // checkSheet provides a function to fill each row element and make that is
  198. // continuous in a worksheet of XML.
  199. func checkSheet(xlsx *xlsxWorksheet) {
  200. var row int
  201. for _, r := range xlsx.SheetData.Row {
  202. if r.R != 0 && r.R > row {
  203. row = r.R
  204. continue
  205. }
  206. row++
  207. }
  208. sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
  209. row = 0
  210. for _, r := range xlsx.SheetData.Row {
  211. if r.R != 0 {
  212. sheetData.Row[r.R-1] = r
  213. row = r.R
  214. continue
  215. }
  216. row++
  217. r.R = row
  218. sheetData.Row[row-1] = r
  219. }
  220. for i := 1; i <= row; i++ {
  221. sheetData.Row[i-1].R = i
  222. }
  223. xlsx.SheetData = sheetData
  224. }
  225. // addRels provides a function to add relationships by given XML path,
  226. // relationship type, target and target mode.
  227. func (f *File) addRels(relPath, relType, target, targetMode string) int {
  228. var uniqPart = map[string]string{
  229. SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
  230. }
  231. rels := f.relsReader(relPath)
  232. if rels == nil {
  233. rels = &xlsxRelationships{}
  234. }
  235. var rID int
  236. for idx, rel := range rels.Relationships {
  237. ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
  238. if ID > rID {
  239. rID = ID
  240. }
  241. if relType == rel.Type {
  242. if partName, ok := uniqPart[rel.Type]; ok {
  243. rels.Relationships[idx].Target = partName
  244. return rID
  245. }
  246. }
  247. }
  248. rID++
  249. var ID bytes.Buffer
  250. ID.WriteString("rId")
  251. ID.WriteString(strconv.Itoa(rID))
  252. rels.Relationships = append(rels.Relationships, xlsxRelationship{
  253. ID: ID.String(),
  254. Type: relType,
  255. Target: target,
  256. TargetMode: targetMode,
  257. })
  258. f.Relationships[relPath] = rels
  259. return rID
  260. }
  261. // UpdateLinkedValue fix linked values within a spreadsheet are not updating in
  262. // Office Excel 2007 and 2010. This function will be remove value tag when met a
  263. // cell have a linked value. Reference
  264. // https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
  265. //
  266. // Notice: after open XLSX file Excel will be update linked value and generate
  267. // new value and will prompt save file or not.
  268. //
  269. // For example:
  270. //
  271. // <row r="19" spans="2:2">
  272. // <c r="B19">
  273. // <f>SUM(Sheet2!D2,Sheet2!D11)</f>
  274. // <v>100</v>
  275. // </c>
  276. // </row>
  277. //
  278. // to
  279. //
  280. // <row r="19" spans="2:2">
  281. // <c r="B19">
  282. // <f>SUM(Sheet2!D2,Sheet2!D11)</f>
  283. // </c>
  284. // </row>
  285. //
  286. func (f *File) UpdateLinkedValue() error {
  287. wb := f.workbookReader()
  288. // recalculate formulas
  289. wb.CalcPr = nil
  290. for _, name := range f.GetSheetList() {
  291. xlsx, err := f.workSheetReader(name)
  292. if err != nil {
  293. return err
  294. }
  295. for indexR := range xlsx.SheetData.Row {
  296. for indexC, col := range xlsx.SheetData.Row[indexR].C {
  297. if col.F != nil && col.V != "" {
  298. xlsx.SheetData.Row[indexR].C[indexC].V = ""
  299. xlsx.SheetData.Row[indexR].C[indexC].T = ""
  300. }
  301. }
  302. }
  303. }
  304. return nil
  305. }
  306. // AddVBAProject provides the method to add vbaProject.bin file which contains
  307. // functions and/or macros. The file extension should be .xlsm. For example:
  308. //
  309. // if err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1")); err != nil {
  310. // fmt.Println(err)
  311. // }
  312. // if err := f.AddVBAProject("vbaProject.bin"); err != nil {
  313. // fmt.Println(err)
  314. // }
  315. // if err := f.SaveAs("macros.xlsm"); err != nil {
  316. // fmt.Println(err)
  317. // }
  318. //
  319. func (f *File) AddVBAProject(bin string) error {
  320. var err error
  321. // Check vbaProject.bin exists first.
  322. if _, err = os.Stat(bin); os.IsNotExist(err) {
  323. return fmt.Errorf("stat %s: no such file or directory", bin)
  324. }
  325. if path.Ext(bin) != ".bin" {
  326. return errors.New("unsupported VBA project extension")
  327. }
  328. f.setContentTypePartVBAProjectExtensions()
  329. wbPath := f.getWorkbookPath()
  330. wbRelsPath := strings.TrimPrefix(filepath.Join(filepath.Dir(wbPath), "_rels", filepath.Base(wbPath)+".rels"), string(filepath.Separator))
  331. wb := f.relsReader(wbRelsPath)
  332. var rID int
  333. var ok bool
  334. for _, rel := range wb.Relationships {
  335. if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {
  336. ok = true
  337. continue
  338. }
  339. t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
  340. if t > rID {
  341. rID = t
  342. }
  343. }
  344. rID++
  345. if !ok {
  346. wb.Relationships = append(wb.Relationships, xlsxRelationship{
  347. ID: "rId" + strconv.Itoa(rID),
  348. Target: "vbaProject.bin",
  349. Type: SourceRelationshipVBAProject,
  350. })
  351. }
  352. file, _ := ioutil.ReadFile(bin)
  353. f.XLSX["xl/vbaProject.bin"] = file
  354. return err
  355. }
  356. // setContentTypePartVBAProjectExtensions provides a function to set the
  357. // content type for relationship parts and the main document part.
  358. func (f *File) setContentTypePartVBAProjectExtensions() {
  359. var ok bool
  360. content := f.contentTypesReader()
  361. for _, v := range content.Defaults {
  362. if v.Extension == "bin" {
  363. ok = true
  364. }
  365. }
  366. for idx, o := range content.Overrides {
  367. if o.PartName == "/xl/workbook.xml" {
  368. content.Overrides[idx].ContentType = ContentTypeMacro
  369. }
  370. }
  371. if !ok {
  372. content.Defaults = append(content.Defaults, xlsxDefault{
  373. Extension: "bin",
  374. ContentType: ContentTypeVBA,
  375. })
  376. }
  377. }