excelize.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // Copyright 2016 - 2019 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 files. Support reads and writes XLSX file generated by
  6. // Microsoft Excel™ 2007 and later. Support save file without losing original
  7. // charts of XLSX. This library needs Go version 1.8 or later.
  8. //
  9. // See https://xuri.me/excelize for more information about this package.
  10. package excelize
  11. import (
  12. "archive/zip"
  13. "bytes"
  14. "encoding/xml"
  15. "errors"
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "os"
  20. "path"
  21. "strconv"
  22. "strings"
  23. )
  24. // File define a populated XLSX file struct.
  25. type File struct {
  26. checked map[string]bool
  27. sheetMap map[string]string
  28. CalcChain *xlsxCalcChain
  29. Comments map[string]*xlsxComments
  30. ContentTypes *xlsxTypes
  31. DrawingRels map[string]*xlsxWorkbookRels
  32. Drawings map[string]*xlsxWsDr
  33. Path string
  34. SharedStrings *xlsxSST
  35. Sheet map[string]*xlsxWorksheet
  36. SheetCount int
  37. Styles *xlsxStyleSheet
  38. Theme *xlsxTheme
  39. DecodeVMLDrawing map[string]*decodeVmlDrawing
  40. VMLDrawing map[string]*vmlDrawing
  41. WorkBook *xlsxWorkbook
  42. WorkBookRels *xlsxWorkbookRels
  43. WorkSheetRels map[string]*xlsxWorkbookRels
  44. XLSX map[string][]byte
  45. }
  46. // OpenFile take the name of an XLSX file and returns a populated XLSX file
  47. // struct for it.
  48. func OpenFile(filename string) (*File, error) {
  49. file, err := os.Open(filename)
  50. if err != nil {
  51. return nil, err
  52. }
  53. defer file.Close()
  54. f, err := OpenReader(file)
  55. if err != nil {
  56. return nil, err
  57. }
  58. f.Path = filename
  59. return f, nil
  60. }
  61. // OpenReader take an io.Reader and return a populated XLSX file.
  62. func OpenReader(r io.Reader) (*File, error) {
  63. b, err := ioutil.ReadAll(r)
  64. if err != nil {
  65. return nil, err
  66. }
  67. zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
  68. if err != nil {
  69. identifier := []byte{
  70. // checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier
  71. 0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
  72. 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
  73. 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
  74. 0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
  75. 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  76. }
  77. if bytes.Contains(b, identifier) {
  78. return nil, errors.New("not support encrypted file currently")
  79. }
  80. return nil, err
  81. }
  82. file, sheetCount, err := ReadZipReader(zr)
  83. if err != nil {
  84. return nil, err
  85. }
  86. f := &File{
  87. checked: make(map[string]bool),
  88. Comments: make(map[string]*xlsxComments),
  89. DrawingRels: make(map[string]*xlsxWorkbookRels),
  90. Drawings: make(map[string]*xlsxWsDr),
  91. Sheet: make(map[string]*xlsxWorksheet),
  92. SheetCount: sheetCount,
  93. DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
  94. VMLDrawing: make(map[string]*vmlDrawing),
  95. WorkSheetRels: make(map[string]*xlsxWorkbookRels),
  96. XLSX: file,
  97. }
  98. f.CalcChain = f.calcChainReader()
  99. f.sheetMap = f.getSheetMap()
  100. f.Styles = f.stylesReader()
  101. f.Theme = f.themeReader()
  102. return f, nil
  103. }
  104. // setDefaultTimeStyle provides a function to set default numbers format for
  105. // time.Time type cell value by given worksheet name, cell coordinates and
  106. // number format code.
  107. func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
  108. s, err := f.GetCellStyle(sheet, axis)
  109. if err != nil {
  110. return err
  111. }
  112. if s == 0 {
  113. style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`)
  114. f.SetCellStyle(sheet, axis, axis, style)
  115. }
  116. return err
  117. }
  118. // workSheetReader provides a function to get the pointer to the structure
  119. // after deserialization by given worksheet name.
  120. func (f *File) workSheetReader(sheet string) (*xlsxWorksheet, error) {
  121. name, ok := f.sheetMap[trimSheetName(sheet)]
  122. if !ok {
  123. return nil, fmt.Errorf("sheet %s is not exist", sheet)
  124. }
  125. if f.Sheet[name] == nil {
  126. var xlsx xlsxWorksheet
  127. _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(name)), &xlsx)
  128. if f.checked == nil {
  129. f.checked = make(map[string]bool)
  130. }
  131. ok := f.checked[name]
  132. if !ok {
  133. checkSheet(&xlsx)
  134. checkRow(&xlsx)
  135. f.checked[name] = true
  136. }
  137. f.Sheet[name] = &xlsx
  138. }
  139. return f.Sheet[name], nil
  140. }
  141. // checkSheet provides a function to fill each row element and make that is
  142. // continuous in a worksheet of XML.
  143. func checkSheet(xlsx *xlsxWorksheet) {
  144. row := len(xlsx.SheetData.Row)
  145. if row >= 1 {
  146. lastRow := xlsx.SheetData.Row[row-1].R
  147. if lastRow >= row {
  148. row = lastRow
  149. }
  150. }
  151. sheetData := xlsxSheetData{}
  152. existsRows := map[int]int{}
  153. for k := range xlsx.SheetData.Row {
  154. existsRows[xlsx.SheetData.Row[k].R] = k
  155. }
  156. for i := 0; i < row; i++ {
  157. _, ok := existsRows[i+1]
  158. if ok {
  159. sheetData.Row = append(sheetData.Row, xlsx.SheetData.Row[existsRows[i+1]])
  160. } else {
  161. sheetData.Row = append(sheetData.Row, xlsxRow{
  162. R: i + 1,
  163. })
  164. }
  165. }
  166. xlsx.SheetData = sheetData
  167. }
  168. // replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
  169. // xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
  170. // Office Excel 2007.
  171. func replaceWorkSheetsRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
  172. var oldXmlns = []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
  173. 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">`)
  174. workbookMarshal = bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
  175. return workbookMarshal
  176. }
  177. // UpdateLinkedValue fix linked values within a spreadsheet are not updating in
  178. // Office Excel 2007 and 2010. This function will be remove value tag when met a
  179. // cell have a linked value. Reference
  180. // https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
  181. //
  182. // Notice: after open XLSX file Excel will be update linked value and generate
  183. // new value and will prompt save file or not.
  184. //
  185. // For example:
  186. //
  187. // <row r="19" spans="2:2">
  188. // <c r="B19">
  189. // <f>SUM(Sheet2!D2,Sheet2!D11)</f>
  190. // <v>100</v>
  191. // </c>
  192. // </row>
  193. //
  194. // to
  195. //
  196. // <row r="19" spans="2:2">
  197. // <c r="B19">
  198. // <f>SUM(Sheet2!D2,Sheet2!D11)</f>
  199. // </c>
  200. // </row>
  201. //
  202. func (f *File) UpdateLinkedValue() error {
  203. for _, name := range f.GetSheetMap() {
  204. xlsx, err := f.workSheetReader(name)
  205. if err != nil {
  206. return err
  207. }
  208. for indexR := range xlsx.SheetData.Row {
  209. for indexC, col := range xlsx.SheetData.Row[indexR].C {
  210. if col.F != nil && col.V != "" {
  211. xlsx.SheetData.Row[indexR].C[indexC].V = ""
  212. xlsx.SheetData.Row[indexR].C[indexC].T = ""
  213. }
  214. }
  215. }
  216. }
  217. return nil
  218. }
  219. // AddVBAProject provides the method to add vbaProject.bin file which contains
  220. // functions and/or macros. The file extension should be .xlsm. For example:
  221. //
  222. // err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1"))
  223. // if err != nil {
  224. // fmt.Println(err)
  225. // }
  226. // err = f.AddVBAProject("vbaProject.bin")
  227. // if err != nil {
  228. // fmt.Println(err)
  229. // }
  230. // err = f.SaveAs("macros.xlsm")
  231. // if err != nil {
  232. // fmt.Println(err)
  233. // }
  234. //
  235. func (f *File) AddVBAProject(bin string) error {
  236. var err error
  237. // Check vbaProject.bin exists first.
  238. if _, err = os.Stat(bin); os.IsNotExist(err) {
  239. return err
  240. }
  241. if path.Ext(bin) != ".bin" {
  242. return errors.New("unsupported VBA project extension")
  243. }
  244. f.setContentTypePartVBAProjectExtensions()
  245. wb := f.workbookRelsReader()
  246. var rID int
  247. var ok bool
  248. for _, rel := range wb.Relationships {
  249. if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {
  250. ok = true
  251. continue
  252. }
  253. t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
  254. if t > rID {
  255. rID = t
  256. }
  257. }
  258. rID++
  259. if !ok {
  260. wb.Relationships = append(wb.Relationships, xlsxWorkbookRelation{
  261. ID: "rId" + strconv.Itoa(rID),
  262. Target: "vbaProject.bin",
  263. Type: SourceRelationshipVBAProject,
  264. })
  265. }
  266. file, _ := ioutil.ReadFile(bin)
  267. f.XLSX["xl/vbaProject.bin"] = file
  268. return err
  269. }
  270. // setContentTypePartVBAProjectExtensions provides a function to set the
  271. // content type for relationship parts and the main document part.
  272. func (f *File) setContentTypePartVBAProjectExtensions() {
  273. var ok bool
  274. content := f.contentTypesReader()
  275. for _, v := range content.Defaults {
  276. if v.Extension == "bin" {
  277. ok = true
  278. }
  279. }
  280. for idx, o := range content.Overrides {
  281. if o.PartName == "/xl/workbook.xml" {
  282. content.Overrides[idx].ContentType = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
  283. }
  284. }
  285. if !ok {
  286. content.Defaults = append(content.Defaults, xlsxDefault{
  287. Extension: "bin",
  288. ContentType: "application/vnd.ms-office.vbaProject",
  289. })
  290. }
  291. }