excelize.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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.10 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. "golang.org/x/net/html/charset"
  24. )
  25. // File define a populated XLSX file struct.
  26. type File struct {
  27. checked map[string]bool
  28. sheetMap map[string]string
  29. CalcChain *xlsxCalcChain
  30. Comments map[string]*xlsxComments
  31. ContentTypes *xlsxTypes
  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. Relationships map[string]*xlsxRelationships
  43. XLSX map[string][]byte
  44. CharsetReader charsetTranscoderFn
  45. }
  46. type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
  47. // OpenFile take the name of an XLSX file and returns a populated XLSX file
  48. // struct for it.
  49. func OpenFile(filename string) (*File, error) {
  50. file, err := os.Open(filename)
  51. if err != nil {
  52. return nil, err
  53. }
  54. defer file.Close()
  55. f, err := OpenReader(file)
  56. if err != nil {
  57. return nil, err
  58. }
  59. f.Path = filename
  60. return f, nil
  61. }
  62. // object builder
  63. func newFile() *File {
  64. return &File{
  65. checked: make(map[string]bool),
  66. sheetMap: make(map[string]string),
  67. Comments: make(map[string]*xlsxComments),
  68. Drawings: make(map[string]*xlsxWsDr),
  69. Sheet: make(map[string]*xlsxWorksheet),
  70. DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
  71. VMLDrawing: make(map[string]*vmlDrawing),
  72. Relationships: make(map[string]*xlsxRelationships),
  73. CharsetReader: charset.NewReaderLabel,
  74. }
  75. }
  76. // OpenReader take an io.Reader and return a populated XLSX file.
  77. func OpenReader(r io.Reader) (*File, error) {
  78. b, err := ioutil.ReadAll(r)
  79. if err != nil {
  80. return nil, err
  81. }
  82. zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
  83. if err != nil {
  84. identifier := []byte{
  85. // checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier
  86. 0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
  87. 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
  88. 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
  89. 0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
  90. 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  91. }
  92. if bytes.Contains(b, identifier) {
  93. return nil, errors.New("not support encrypted file currently")
  94. }
  95. return nil, err
  96. }
  97. file, sheetCount, err := ReadZipReader(zr)
  98. if err != nil {
  99. return nil, err
  100. }
  101. f := newFile()
  102. f.SheetCount, f.XLSX = sheetCount, file
  103. f.CalcChain = f.calcChainReader()
  104. f.sheetMap = f.getSheetMap()
  105. f.Styles = f.stylesReader()
  106. f.Theme = f.themeReader()
  107. return f, nil
  108. }
  109. // CharsetTranscoder Set user defined codepage transcoder function for open XLSX from non UTF-8 encoding
  110. func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f }
  111. // Creates new XML decoder with charset reader
  112. func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
  113. ret = xml.NewDecoder(rdr)
  114. ret.CharsetReader = f.CharsetReader
  115. return
  116. }
  117. // setDefaultTimeStyle provides a function to set default numbers format for
  118. // time.Time type cell value by given worksheet name, cell coordinates and
  119. // number format code.
  120. func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
  121. s, err := f.GetCellStyle(sheet, axis)
  122. if err != nil {
  123. return err
  124. }
  125. if s == 0 {
  126. style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`)
  127. f.SetCellStyle(sheet, axis, axis, style)
  128. }
  129. return err
  130. }
  131. // workSheetReader provides a function to get the pointer to the structure
  132. // after deserialization by given worksheet name.
  133. func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
  134. var (
  135. name string
  136. ok bool
  137. )
  138. if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {
  139. err = fmt.Errorf("sheet %s is not exist", sheet)
  140. return
  141. }
  142. if xlsx = f.Sheet[name]; f.Sheet[name] == nil {
  143. xlsx = new(xlsxWorksheet)
  144. if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))).
  145. Decode(xlsx); err != nil && err != io.EOF {
  146. err = fmt.Errorf("xml decode error: %s", err)
  147. return
  148. }
  149. err = nil
  150. if f.checked == nil {
  151. f.checked = make(map[string]bool)
  152. }
  153. if ok = f.checked[name]; !ok {
  154. checkSheet(xlsx)
  155. if err = checkRow(xlsx); err != nil {
  156. return
  157. }
  158. f.checked[name] = true
  159. }
  160. f.Sheet[name] = xlsx
  161. }
  162. return
  163. }
  164. // checkSheet provides a function to fill each row element and make that is
  165. // continuous in a worksheet of XML.
  166. func checkSheet(xlsx *xlsxWorksheet) {
  167. row := len(xlsx.SheetData.Row)
  168. if row >= 1 {
  169. lastRow := xlsx.SheetData.Row[row-1].R
  170. if lastRow >= row {
  171. row = lastRow
  172. }
  173. }
  174. sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
  175. for _, r := range xlsx.SheetData.Row {
  176. sheetData.Row[r.R-1] = r
  177. }
  178. for i := 1; i <= row; i++ {
  179. sheetData.Row[i-1].R = i
  180. }
  181. xlsx.SheetData = sheetData
  182. }
  183. // addRels provides a function to add relationships by given XML path,
  184. // relationship type, target and target mode.
  185. func (f *File) addRels(relPath, relType, target, targetMode string) int {
  186. rels := f.relsReader(relPath)
  187. rID := 0
  188. if rels == nil {
  189. rels = &xlsxRelationships{}
  190. }
  191. rID = len(rels.Relationships) + 1
  192. var ID bytes.Buffer
  193. ID.WriteString("rId")
  194. ID.WriteString(strconv.Itoa(rID))
  195. rels.Relationships = append(rels.Relationships, xlsxRelationship{
  196. ID: ID.String(),
  197. Type: relType,
  198. Target: target,
  199. TargetMode: targetMode,
  200. })
  201. f.Relationships[relPath] = rels
  202. return rID
  203. }
  204. // replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
  205. // xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
  206. // Office Excel 2007.
  207. func replaceWorkSheetsRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
  208. var oldXmlns = []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
  209. var newXmlns = []byte(`<worksheet` + templateNamespaceIDMap)
  210. workbookMarshal = bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
  211. return workbookMarshal
  212. }
  213. // replaceStyleRelationshipsNameSpaceBytes provides a function to replace
  214. // xl/styles.xml XML tags to self-closing for compatible Microsoft Office
  215. // Excel 2007.
  216. func replaceStyleRelationshipsNameSpaceBytes(contentMarshal []byte) []byte {
  217. var oldXmlns = []byte(`<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
  218. var newXmlns = []byte(`<styleSheet` + templateNamespaceIDMap)
  219. contentMarshal = bytes.Replace(contentMarshal, oldXmlns, newXmlns, -1)
  220. return contentMarshal
  221. }
  222. // UpdateLinkedValue fix linked values within a spreadsheet are not updating in
  223. // Office Excel 2007 and 2010. This function will be remove value tag when met a
  224. // cell have a linked value. Reference
  225. // https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
  226. //
  227. // Notice: after open XLSX file Excel will be update linked value and generate
  228. // new value and will prompt save file or not.
  229. //
  230. // For example:
  231. //
  232. // <row r="19" spans="2:2">
  233. // <c r="B19">
  234. // <f>SUM(Sheet2!D2,Sheet2!D11)</f>
  235. // <v>100</v>
  236. // </c>
  237. // </row>
  238. //
  239. // to
  240. //
  241. // <row r="19" spans="2:2">
  242. // <c r="B19">
  243. // <f>SUM(Sheet2!D2,Sheet2!D11)</f>
  244. // </c>
  245. // </row>
  246. //
  247. func (f *File) UpdateLinkedValue() error {
  248. for _, name := range f.GetSheetMap() {
  249. xlsx, err := f.workSheetReader(name)
  250. if err != nil {
  251. return err
  252. }
  253. for indexR := range xlsx.SheetData.Row {
  254. for indexC, col := range xlsx.SheetData.Row[indexR].C {
  255. if col.F != nil && col.V != "" {
  256. xlsx.SheetData.Row[indexR].C[indexC].V = ""
  257. xlsx.SheetData.Row[indexR].C[indexC].T = ""
  258. }
  259. }
  260. }
  261. }
  262. return nil
  263. }
  264. // AddVBAProject provides the method to add vbaProject.bin file which contains
  265. // functions and/or macros. The file extension should be .xlsm. For example:
  266. //
  267. // err := f.SetSheetPrOptions("Sheet1", excelize.CodeName("Sheet1"))
  268. // if err != nil {
  269. // fmt.Println(err)
  270. // }
  271. // err = f.AddVBAProject("vbaProject.bin")
  272. // if err != nil {
  273. // fmt.Println(err)
  274. // }
  275. // err = f.SaveAs("macros.xlsm")
  276. // if err != nil {
  277. // fmt.Println(err)
  278. // }
  279. //
  280. func (f *File) AddVBAProject(bin string) error {
  281. var err error
  282. // Check vbaProject.bin exists first.
  283. if _, err = os.Stat(bin); os.IsNotExist(err) {
  284. return err
  285. }
  286. if path.Ext(bin) != ".bin" {
  287. return errors.New("unsupported VBA project extension")
  288. }
  289. f.setContentTypePartVBAProjectExtensions()
  290. wb := f.relsReader("xl/_rels/workbook.xml.rels")
  291. var rID int
  292. var ok bool
  293. for _, rel := range wb.Relationships {
  294. if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {
  295. ok = true
  296. continue
  297. }
  298. t, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
  299. if t > rID {
  300. rID = t
  301. }
  302. }
  303. rID++
  304. if !ok {
  305. wb.Relationships = append(wb.Relationships, xlsxRelationship{
  306. ID: "rId" + strconv.Itoa(rID),
  307. Target: "vbaProject.bin",
  308. Type: SourceRelationshipVBAProject,
  309. })
  310. }
  311. file, _ := ioutil.ReadFile(bin)
  312. f.XLSX["xl/vbaProject.bin"] = file
  313. return err
  314. }
  315. // setContentTypePartVBAProjectExtensions provides a function to set the
  316. // content type for relationship parts and the main document part.
  317. func (f *File) setContentTypePartVBAProjectExtensions() {
  318. var ok bool
  319. content := f.contentTypesReader()
  320. for _, v := range content.Defaults {
  321. if v.Extension == "bin" {
  322. ok = true
  323. }
  324. }
  325. for idx, o := range content.Overrides {
  326. if o.PartName == "/xl/workbook.xml" {
  327. content.Overrides[idx].ContentType = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
  328. }
  329. }
  330. if !ok {
  331. content.Defaults = append(content.Defaults, xlsxDefault{
  332. Extension: "bin",
  333. ContentType: "application/vnd.ms-office.vbaProject",
  334. })
  335. }
  336. }