file.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package xlsx
  2. import (
  3. "archive/zip"
  4. "encoding/xml"
  5. "fmt"
  6. "io"
  7. "os"
  8. "strconv"
  9. )
  10. // File is a high level structure providing a slice of Sheet structs
  11. // to the user.
  12. type File struct {
  13. worksheets map[string]*zip.File
  14. numFmtRefTable NumFmtRefTable
  15. referenceTable *RefTable
  16. Date1904 bool
  17. styles *xlsxStyleSheet
  18. Sheets []*Sheet
  19. Sheet map[string]*Sheet
  20. }
  21. // Create a new File
  22. func NewFile() (file *File) {
  23. file = &File{}
  24. file.numFmtRefTable = make(NumFmtRefTable)
  25. file.Sheet = make(map[string]*Sheet)
  26. file.Sheets = make([]*Sheet, 0)
  27. return
  28. }
  29. // OpenFile() take the name of an XLSX file and returns a populated
  30. // xlsx.File struct for it.
  31. func OpenFile(filename string) (*File, error) {
  32. var f *zip.ReadCloser
  33. f, err := zip.OpenReader(filename)
  34. if err != nil {
  35. return nil, err
  36. }
  37. return ReadZip(f)
  38. }
  39. // A convenient wrapper around File.ToSlice, FileToSlice will
  40. // return the raw data contained in an Excel XLSX file as three
  41. // dimensional slice. The first index represents the sheet number,
  42. // the second the row number, and the third the cell number.
  43. //
  44. // For example:
  45. //
  46. // var mySlice [][][]string
  47. // var value string
  48. // mySlice = xlsx.FileToSlice("myXLSX.xlsx")
  49. // value = mySlice[0][0][0]
  50. //
  51. // Here, value would be set to the raw value of the cell A1 in the
  52. // first sheet in the XLSX file.
  53. func FileToSlice(path string) ([][][]string, error) {
  54. f, err := OpenFile(path)
  55. if err != nil {
  56. return nil, err
  57. }
  58. return f.ToSlice()
  59. }
  60. // Save the File to an xlsx file at the provided path.
  61. func (f *File) Save(path string) (err error) {
  62. var parts map[string]string
  63. var target *os.File
  64. var zipWriter *zip.Writer
  65. parts, err = f.MarshallParts()
  66. if err != nil {
  67. return
  68. }
  69. target, err = os.Create(path)
  70. if err != nil {
  71. return
  72. }
  73. zipWriter = zip.NewWriter(target)
  74. for partName, part := range parts {
  75. var writer io.Writer
  76. writer, err = zipWriter.Create(partName)
  77. if err != nil {
  78. return
  79. }
  80. _, err = writer.Write([]byte(part))
  81. if err != nil {
  82. return
  83. }
  84. }
  85. err = zipWriter.Close()
  86. if err != nil {
  87. return
  88. }
  89. return target.Close()
  90. }
  91. // Add a new Sheet, with the provided name, to a File
  92. func (f *File) AddSheet(sheetName string) (sheet *Sheet) {
  93. sheet = &Sheet{Name: sheetName, File: *f}
  94. f.Sheet[sheetName] = sheet
  95. f.Sheets = append(f.Sheets, sheet)
  96. return sheet
  97. }
  98. func (f *File) makeWorkbook() xlsxWorkbook {
  99. var workbook xlsxWorkbook
  100. workbook = xlsxWorkbook{}
  101. workbook.FileVersion = xlsxFileVersion{}
  102. workbook.FileVersion.AppName = "Go XLSX"
  103. workbook.WorkbookPr = xlsxWorkbookPr{
  104. BackupFile: false,
  105. ShowObjects: "all"}
  106. workbook.BookViews = xlsxBookViews{}
  107. workbook.BookViews.WorkBookView = make([]xlsxWorkBookView, 1)
  108. workbook.BookViews.WorkBookView[0] = xlsxWorkBookView{
  109. ActiveTab: 0,
  110. FirstSheet: 0,
  111. ShowHorizontalScroll: true,
  112. ShowSheetTabs: true,
  113. ShowVerticalScroll: true,
  114. TabRatio: 204,
  115. WindowHeight: 8192,
  116. WindowWidth: 16384,
  117. XWindow: "0",
  118. YWindow: "0"}
  119. workbook.Sheets = xlsxSheets{}
  120. workbook.Sheets.Sheet = make([]xlsxSheet, len(f.Sheets))
  121. workbook.CalcPr.IterateCount = 100
  122. workbook.CalcPr.RefMode = "A1"
  123. workbook.CalcPr.Iterate = false
  124. workbook.CalcPr.IterateDelta = 0.001
  125. return workbook
  126. }
  127. // Construct a map of file name to XML content representing the file
  128. // in terms of the structure of an XLSX file.
  129. func (f *File) MarshallParts() (map[string]string, error) {
  130. var parts map[string]string
  131. var refTable *RefTable = NewSharedStringRefTable()
  132. refTable.isWrite = true
  133. var workbookRels WorkBookRels = make(WorkBookRels)
  134. var err error
  135. var workbook xlsxWorkbook
  136. var types xlsxTypes = MakeDefaultContentTypes()
  137. marshal := func(thing interface{}) (string, error) {
  138. body, err := xml.Marshal(thing)
  139. if err != nil {
  140. return "", err
  141. }
  142. return xml.Header + string(body), nil
  143. }
  144. parts = make(map[string]string)
  145. workbook = f.makeWorkbook()
  146. sheetIndex := 1
  147. styles := &xlsxStyleSheet{}
  148. for _, sheet := range f.Sheets {
  149. xSheet := sheet.makeXLSXSheet(refTable, styles)
  150. rId := fmt.Sprintf("rId%d", sheetIndex)
  151. sheetId := strconv.Itoa(sheetIndex)
  152. sheetPath := fmt.Sprintf("worksheets/sheet%d.xml", sheetIndex)
  153. partName := "xl/" + sheetPath
  154. types.Overrides = append(
  155. types.Overrides,
  156. xlsxOverride{
  157. PartName: "/" + partName,
  158. ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"})
  159. workbookRels[rId] = sheetPath
  160. workbook.Sheets.Sheet[sheetIndex-1] = xlsxSheet{
  161. Name: sheet.Name,
  162. SheetId: sheetId,
  163. Id: rId,
  164. State: "visible"}
  165. parts[partName], err = marshal(xSheet)
  166. if err != nil {
  167. return parts, err
  168. }
  169. sheetIndex++
  170. }
  171. parts["xl/workbook.xml"], err = marshal(workbook)
  172. if err != nil {
  173. return parts, err
  174. }
  175. parts["_rels/.rels"] = TEMPLATE__RELS_DOT_RELS
  176. parts["docProps/app.xml"] = TEMPLATE_DOCPROPS_APP
  177. // TODO - do this properly, modification and revision information
  178. parts["docProps/core.xml"] = TEMPLATE_DOCPROPS_CORE
  179. parts["xl/theme/theme1.xml"] = TEMPLATE_XL_THEME_THEME
  180. xSST := refTable.makeXLSXSST()
  181. parts["xl/sharedStrings.xml"], err = marshal(xSST)
  182. if err != nil {
  183. return parts, err
  184. }
  185. xWRel := workbookRels.MakeXLSXWorkbookRels()
  186. parts["xl/_rels/workbook.xml.rels"], err = marshal(xWRel)
  187. if err != nil {
  188. return parts, err
  189. }
  190. parts["[Content_Types].xml"], err = marshal(types)
  191. if err != nil {
  192. return parts, err
  193. }
  194. parts["xl/styles.xml"], err = styles.Marshal()
  195. if err != nil {
  196. return parts, err
  197. }
  198. return parts, nil
  199. }
  200. // Return the raw data contained in the File as three
  201. // dimensional slice. The first index represents the sheet number,
  202. // the second the row number, and the third the cell number.
  203. //
  204. // For example:
  205. //
  206. // var mySlice [][][]string
  207. // var value string
  208. // mySlice = xlsx.FileToSlice("myXLSX.xlsx")
  209. // value = mySlice[0][0][0]
  210. //
  211. // Here, value would be set to the raw value of the cell A1 in the
  212. // first sheet in the XLSX file.
  213. func (file *File) ToSlice() (output [][][]string, err error) {
  214. output = [][][]string{}
  215. for _, sheet := range file.Sheets {
  216. s := [][]string{}
  217. for _, row := range sheet.Rows {
  218. if row == nil {
  219. continue
  220. }
  221. r := []string{}
  222. for _, cell := range row.Cells {
  223. r = append(r, cell.String())
  224. }
  225. s = append(s, r)
  226. }
  227. output = append(output, s)
  228. }
  229. return output, nil
  230. }