file.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package xlsx
  2. import (
  3. "archive/zip"
  4. "encoding/xml"
  5. "fmt"
  6. "io"
  7. "os"
  8. "strconv"
  9. "strings"
  10. )
  11. // File is a high level structure providing a slice of Sheet structs
  12. // to the user.
  13. type File struct {
  14. worksheets map[string]*zip.File
  15. referenceTable *RefTable
  16. Date1904 bool
  17. styles *xlsxStyleSheet
  18. Sheets []*Sheet
  19. Sheet map[string]*Sheet
  20. theme *theme
  21. }
  22. // Create a new File
  23. func NewFile() (file *File) {
  24. file = &File{}
  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 *File, err error) {
  32. var f *zip.ReadCloser
  33. f, err = zip.OpenReader(filename)
  34. if err != nil {
  35. return nil, err
  36. }
  37. file, err = ReadZip(f)
  38. return
  39. }
  40. // A convenient wrapper around File.ToSlice, FileToSlice will
  41. // return the raw data contained in an Excel XLSX file as three
  42. // dimensional slice. The first index represents the sheet number,
  43. // the second the row number, and the third the cell number.
  44. //
  45. // For example:
  46. //
  47. // var mySlice [][][]string
  48. // var value string
  49. // mySlice = xlsx.FileToSlice("myXLSX.xlsx")
  50. // value = mySlice[0][0][0]
  51. //
  52. // Here, value would be set to the raw value of the cell A1 in the
  53. // first sheet in the XLSX file.
  54. func FileToSlice(path string) ([][][]string, error) {
  55. f, err := OpenFile(path)
  56. if err != nil {
  57. return nil, err
  58. }
  59. return f.ToSlice()
  60. }
  61. // Save the File to an xlsx file at the provided path.
  62. func (f *File) Save(path string) (err error) {
  63. var target *os.File
  64. target, err = os.Create(path)
  65. if err != nil {
  66. return
  67. }
  68. err = f.Write(target)
  69. if err != nil {
  70. return
  71. }
  72. return target.Close()
  73. }
  74. // Write the File to io.Writer as xlsx
  75. func (f *File) Write(writer io.Writer) (err error) {
  76. var parts map[string]string
  77. var zipWriter *zip.Writer
  78. parts, err = f.MarshallParts()
  79. if err != nil {
  80. return
  81. }
  82. zipWriter = zip.NewWriter(writer)
  83. for partName, part := range parts {
  84. var writer io.Writer
  85. writer, err = zipWriter.Create(partName)
  86. if err != nil {
  87. return
  88. }
  89. _, err = writer.Write([]byte(part))
  90. if err != nil {
  91. return
  92. }
  93. }
  94. err = zipWriter.Close()
  95. return
  96. }
  97. // Add a new Sheet, with the provided name, to a File
  98. func (f *File) AddSheet(sheetName string) (sheet *Sheet) {
  99. sheet = &Sheet{Name: sheetName, File: f}
  100. if len(f.Sheets) == 0 {
  101. sheet.Selected = true
  102. }
  103. f.Sheet[sheetName] = sheet
  104. f.Sheets = append(f.Sheets, sheet)
  105. return sheet
  106. }
  107. func (f *File) makeWorkbook() xlsxWorkbook {
  108. var workbook xlsxWorkbook
  109. workbook = xlsxWorkbook{}
  110. workbook.FileVersion = xlsxFileVersion{}
  111. workbook.FileVersion.AppName = "Go XLSX"
  112. workbook.WorkbookPr = xlsxWorkbookPr{
  113. BackupFile: false,
  114. ShowObjects: "all"}
  115. workbook.BookViews = xlsxBookViews{}
  116. workbook.BookViews.WorkBookView = make([]xlsxWorkBookView, 1)
  117. workbook.BookViews.WorkBookView[0] = xlsxWorkBookView{
  118. ActiveTab: 0,
  119. FirstSheet: 0,
  120. ShowHorizontalScroll: true,
  121. ShowSheetTabs: true,
  122. ShowVerticalScroll: true,
  123. TabRatio: 204,
  124. WindowHeight: 8192,
  125. WindowWidth: 16384,
  126. XWindow: "0",
  127. YWindow: "0"}
  128. workbook.Sheets = xlsxSheets{}
  129. workbook.Sheets.Sheet = make([]xlsxSheet, len(f.Sheets))
  130. workbook.CalcPr.IterateCount = 100
  131. workbook.CalcPr.RefMode = "A1"
  132. workbook.CalcPr.Iterate = false
  133. workbook.CalcPr.IterateDelta = 0.001
  134. return workbook
  135. }
  136. // Some tools that read XLSX files have very strict requirements about
  137. // the structure of the input XML. In particular both Numbers on the Mac
  138. // and SAS dislike inline XML namespace declarations, or namespace
  139. // prefixes that don't match the ones that Excel itself uses. This is a
  140. // problem because the Go XML library doesn't multiple namespace
  141. // declarations in a single element of a document. This function is a
  142. // horrible hack to fix that after the XML marshalling is completed.
  143. func replaceRelationshipsNameSpace(workbookMarshal string) string {
  144. newWorkbook := strings.Replace(workbookMarshal, `xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id`, `r:id`, -1)
  145. // Dirty hack to fix issues #63 and #91; encoding/xml currently
  146. // "doesn't allow for additional namespaces to be defined in the
  147. // root element of the document," as described by @tealeg in the
  148. // comments for #63.
  149. oldXmlns := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
  150. newXmlns := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">`
  151. return strings.Replace(newWorkbook, oldXmlns, newXmlns, 1)
  152. }
  153. // Construct a map of file name to XML content representing the file
  154. // in terms of the structure of an XLSX file.
  155. func (f *File) MarshallParts() (map[string]string, error) {
  156. var parts map[string]string
  157. var refTable *RefTable = NewSharedStringRefTable()
  158. refTable.isWrite = true
  159. var workbookRels WorkBookRels = make(WorkBookRels)
  160. var err error
  161. var workbook xlsxWorkbook
  162. var types xlsxTypes = MakeDefaultContentTypes()
  163. marshal := func(thing interface{}) (string, error) {
  164. body, err := xml.Marshal(thing)
  165. if err != nil {
  166. return "", err
  167. }
  168. return xml.Header + string(body), nil
  169. }
  170. parts = make(map[string]string)
  171. workbook = f.makeWorkbook()
  172. sheetIndex := 1
  173. if f.styles == nil {
  174. f.styles = newXlsxStyleSheet(f.theme)
  175. }
  176. f.styles.reset()
  177. for _, sheet := range f.Sheets {
  178. xSheet := sheet.makeXLSXSheet(refTable, f.styles)
  179. rId := fmt.Sprintf("rId%d", sheetIndex)
  180. sheetId := strconv.Itoa(sheetIndex)
  181. sheetPath := fmt.Sprintf("worksheets/sheet%d.xml", sheetIndex)
  182. partName := "xl/" + sheetPath
  183. types.Overrides = append(
  184. types.Overrides,
  185. xlsxOverride{
  186. PartName: "/" + partName,
  187. ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"})
  188. workbookRels[rId] = sheetPath
  189. workbook.Sheets.Sheet[sheetIndex-1] = xlsxSheet{
  190. Name: sheet.Name,
  191. SheetId: sheetId,
  192. Id: rId,
  193. State: "visible"}
  194. parts[partName], err = marshal(xSheet)
  195. if err != nil {
  196. return parts, err
  197. }
  198. sheetIndex++
  199. }
  200. workbookMarshal, err := marshal(workbook)
  201. if err != nil {
  202. return parts, err
  203. }
  204. workbookMarshal = replaceRelationshipsNameSpace(workbookMarshal)
  205. parts["xl/workbook.xml"] = workbookMarshal
  206. if err != nil {
  207. return parts, err
  208. }
  209. parts["_rels/.rels"] = TEMPLATE__RELS_DOT_RELS
  210. parts["docProps/app.xml"] = TEMPLATE_DOCPROPS_APP
  211. // TODO - do this properly, modification and revision information
  212. parts["docProps/core.xml"] = TEMPLATE_DOCPROPS_CORE
  213. parts["xl/theme/theme1.xml"] = TEMPLATE_XL_THEME_THEME
  214. xSST := refTable.makeXLSXSST()
  215. parts["xl/sharedStrings.xml"], err = marshal(xSST)
  216. if err != nil {
  217. return parts, err
  218. }
  219. xWRel := workbookRels.MakeXLSXWorkbookRels()
  220. parts["xl/_rels/workbook.xml.rels"], err = marshal(xWRel)
  221. if err != nil {
  222. return parts, err
  223. }
  224. parts["[Content_Types].xml"], err = marshal(types)
  225. if err != nil {
  226. return parts, err
  227. }
  228. parts["xl/styles.xml"], err = f.styles.Marshal()
  229. if err != nil {
  230. return parts, err
  231. }
  232. return parts, nil
  233. }
  234. // Return the raw data contained in the File as three
  235. // dimensional slice. The first index represents the sheet number,
  236. // the second the row number, and the third the cell number.
  237. //
  238. // For example:
  239. //
  240. // var mySlice [][][]string
  241. // var value string
  242. // mySlice = xlsx.FileToSlice("myXLSX.xlsx")
  243. // value = mySlice[0][0][0]
  244. //
  245. // Here, value would be set to the raw value of the cell A1 in the
  246. // first sheet in the XLSX file.
  247. func (file *File) ToSlice() (output [][][]string, err error) {
  248. output = [][][]string{}
  249. for _, sheet := range file.Sheets {
  250. s := [][]string{}
  251. for _, row := range sheet.Rows {
  252. if row == nil {
  253. continue
  254. }
  255. r := []string{}
  256. for _, cell := range row.Cells {
  257. r = append(r, cell.String())
  258. }
  259. s = append(s, r)
  260. }
  261. output = append(output, s)
  262. }
  263. return output, nil
  264. }