xmlStyle.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. // xslx is a package designed to help with reading data from
  2. // spreadsheets stored in the XLSX format used in recent versions of
  3. // Microsoft's Excel spreadsheet.
  4. //
  5. // For a concise example of how to use this library why not check out
  6. // the source for xlsx2csv here: https://github.com/tealeg/xlsx2csv
  7. package xlsx
  8. import (
  9. "encoding/xml"
  10. "fmt"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. )
  15. // Excel styles can reference number formats that are built-in, all of which
  16. // have an id less than 164.
  17. const builtinNumFmtsCount = 163
  18. // Excel styles can reference number formats that are built-in, all of which
  19. // have an id less than 164. This is a possibly incomplete list comprised of as
  20. // many of them as I could find.
  21. var builtInNumFmt = map[int]string{
  22. 0: "general",
  23. 1: "0",
  24. 2: "0.00",
  25. 3: "#,##0",
  26. 4: "#,##0.00",
  27. 9: "0%",
  28. 10: "0.00%",
  29. 11: "0.00e+00",
  30. 12: "# ?/?",
  31. 13: "# ??/??",
  32. 14: "mm-dd-yy",
  33. 15: "d-mmm-yy",
  34. 16: "d-mmm",
  35. 17: "mmm-yy",
  36. 18: "h:mm am/pm",
  37. 19: "h:mm:ss am/pm",
  38. 20: "h:mm",
  39. 21: "h:mm:ss",
  40. 22: "m/d/yy h:mm",
  41. 37: "#,##0 ;(#,##0)",
  42. 38: "#,##0 ;[red](#,##0)",
  43. 39: "#,##0.00;(#,##0.00)",
  44. 40: "#,##0.00;[red](#,##0.00)",
  45. 41: `_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)`,
  46. 42: `_("$"* #,##0_);_("$* \(#,##0\);_("$"* "-"_);_(@_)`,
  47. 43: `_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)`,
  48. 44: `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`,
  49. 45: "mm:ss",
  50. 46: "[h]:mm:ss",
  51. 47: "mmss.0",
  52. 48: "##0.0e+0",
  53. 49: "@",
  54. }
  55. const (
  56. builtInNumFmtIndex_GENERAL = int(0)
  57. builtInNumFmtIndex_INT = int(1)
  58. builtInNumFmtIndex_FLOAT = int(2)
  59. builtInNumFmtIndex_DATE = int(14)
  60. builtInNumFmtIndex_STRING = int(49)
  61. )
  62. // xlsxStyle directly maps the styleSheet element in the namespace
  63. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  64. // currently I have not checked it for completeness - it does as much
  65. // as I need.
  66. type xlsxStyleSheet struct {
  67. XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main styleSheet"`
  68. Fonts xlsxFonts `xml:"fonts,omitempty"`
  69. Fills xlsxFills `xml:"fills,omitempty"`
  70. Borders xlsxBorders `xml:"borders,omitempty"`
  71. CellStyles *xlsxCellStyles `xml:"cellStyles,omitempty"`
  72. CellStyleXfs *xlsxCellStyleXfs `xml:"cellStyleXfs,omitempty"`
  73. CellXfs xlsxCellXfs `xml:"cellXfs,omitempty"`
  74. NumFmts xlsxNumFmts `xml:"numFmts,omitempty"`
  75. theme *theme
  76. styleCache map[int]*Style
  77. numFmtRefTable map[int]xlsxNumFmt
  78. lock *sync.RWMutex
  79. }
  80. func newXlsxStyleSheet(t *theme) *xlsxStyleSheet {
  81. stylesheet := new(xlsxStyleSheet)
  82. stylesheet.theme = t
  83. stylesheet.styleCache = make(map[int]*Style)
  84. stylesheet.lock = new(sync.RWMutex)
  85. return stylesheet
  86. }
  87. func (styles *xlsxStyleSheet) reset() {
  88. styles.Fonts = xlsxFonts{}
  89. styles.Fills = xlsxFills{}
  90. styles.Borders = xlsxBorders{}
  91. // Microsoft seems to want an emtpy border to start with
  92. styles.addBorder(xlsxBorder{})
  93. styles.CellStyleXfs = &xlsxCellStyleXfs{}
  94. // add default xf
  95. styles.CellXfs = xlsxCellXfs{Count: 1, Xf: []xlsxXf{{}}}
  96. styles.NumFmts = xlsxNumFmts{}
  97. }
  98. func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
  99. styles.lock.RLock()
  100. style, ok := styles.styleCache[styleIndex]
  101. styles.lock.RUnlock()
  102. if ok {
  103. return
  104. }
  105. var namedStyleXf xlsxXf
  106. style = &Style{}
  107. style.Border = Border{}
  108. style.Fill = Fill{}
  109. style.Font = Font{}
  110. xfCount := styles.CellXfs.Count
  111. if styleIndex > -1 && xfCount > 0 && styleIndex <= xfCount {
  112. xf := styles.CellXfs.Xf[styleIndex]
  113. if xf.XfId != nil && styles.CellStyleXfs != nil {
  114. namedStyleXf = styles.CellStyleXfs.Xf[*xf.XfId]
  115. style.NamedStyleIndex = xf.XfId
  116. } else {
  117. namedStyleXf = xlsxXf{}
  118. }
  119. style.ApplyBorder = xf.ApplyBorder || namedStyleXf.ApplyBorder
  120. style.ApplyFill = xf.ApplyFill || namedStyleXf.ApplyFill
  121. style.ApplyFont = xf.ApplyFont || namedStyleXf.ApplyFont
  122. style.ApplyAlignment = xf.ApplyAlignment || namedStyleXf.ApplyAlignment
  123. if xf.BorderId > -1 && xf.BorderId < styles.Borders.Count {
  124. var border xlsxBorder
  125. border = styles.Borders.Border[xf.BorderId]
  126. style.Border.Left = border.Left.Style
  127. style.Border.LeftColor = border.Left.Color.RGB
  128. style.Border.Right = border.Right.Style
  129. style.Border.RightColor = border.Right.Color.RGB
  130. style.Border.Top = border.Top.Style
  131. style.Border.TopColor = border.Top.Color.RGB
  132. style.Border.Bottom = border.Bottom.Style
  133. style.Border.BottomColor = border.Bottom.Color.RGB
  134. }
  135. if xf.FillId > -1 && xf.FillId < styles.Fills.Count {
  136. xFill := styles.Fills.Fill[xf.FillId]
  137. style.Fill.PatternType = xFill.PatternFill.PatternType
  138. style.Fill.FgColor = styles.argbValue(xFill.PatternFill.FgColor)
  139. style.Fill.BgColor = styles.argbValue(xFill.PatternFill.BgColor)
  140. }
  141. if xf.FontId > -1 && xf.FontId < styles.Fonts.Count {
  142. xfont := styles.Fonts.Font[xf.FontId]
  143. style.Font.Size, _ = strconv.Atoi(xfont.Sz.Val)
  144. style.Font.Name = xfont.Name.Val
  145. style.Font.Family, _ = strconv.Atoi(xfont.Family.Val)
  146. style.Font.Charset, _ = strconv.Atoi(xfont.Charset.Val)
  147. style.Font.Color = styles.argbValue(xfont.Color)
  148. if bold := xfont.B; bold != nil && bold.Val != "0" {
  149. style.Font.Bold = true
  150. }
  151. if italic := xfont.I; italic != nil && italic.Val != "0" {
  152. style.Font.Italic = true
  153. }
  154. if underline := xfont.U; underline != nil && underline.Val != "0" {
  155. style.Font.Underline = true
  156. }
  157. }
  158. if xf.Alignment.Horizontal != "" {
  159. style.Alignment.Horizontal = xf.Alignment.Horizontal
  160. }
  161. if xf.Alignment.Vertical != "" {
  162. style.Alignment.Vertical = xf.Alignment.Vertical
  163. }
  164. styles.lock.Lock()
  165. styles.styleCache[styleIndex] = style
  166. styles.lock.Unlock()
  167. }
  168. return style
  169. }
  170. func (styles *xlsxStyleSheet) argbValue(color xlsxColor) string {
  171. if color.Theme != nil && styles.theme != nil {
  172. return styles.theme.themeColor(int64(*color.Theme), color.Tint)
  173. } else {
  174. return color.RGB
  175. }
  176. }
  177. // Excel styles can reference number formats that are built-in, all of which
  178. // have an id less than 164. This is a possibly incomplete list comprised of as
  179. // many of them as I could find.
  180. func getBuiltinNumberFormat(numFmtId int) string {
  181. return builtInNumFmt[numFmtId]
  182. }
  183. func (styles *xlsxStyleSheet) getNumberFormat(styleIndex int) string {
  184. if styles.CellXfs.Xf == nil {
  185. return ""
  186. }
  187. var numberFormat string = ""
  188. if styleIndex > -1 && styleIndex <= styles.CellXfs.Count {
  189. xf := styles.CellXfs.Xf[styleIndex]
  190. if builtin := getBuiltinNumberFormat(xf.NumFmtId); builtin != "" {
  191. return builtin
  192. }
  193. if styles.numFmtRefTable != nil {
  194. numFmt := styles.numFmtRefTable[xf.NumFmtId]
  195. numberFormat = numFmt.FormatCode
  196. }
  197. }
  198. return strings.ToLower(numberFormat)
  199. }
  200. func (styles *xlsxStyleSheet) addFont(xFont xlsxFont) (index int) {
  201. var font xlsxFont
  202. if xFont.Name.Val == "" {
  203. return 0
  204. }
  205. for index, font = range styles.Fonts.Font {
  206. if font.Equals(xFont) {
  207. return index
  208. }
  209. }
  210. styles.Fonts.Font = append(styles.Fonts.Font, xFont)
  211. index = styles.Fonts.Count
  212. styles.Fonts.Count += 1
  213. return
  214. }
  215. func (styles *xlsxStyleSheet) addFill(xFill xlsxFill) (index int) {
  216. var fill xlsxFill
  217. for index, fill = range styles.Fills.Fill {
  218. if fill.Equals(xFill) {
  219. return index
  220. }
  221. }
  222. styles.Fills.Fill = append(styles.Fills.Fill, xFill)
  223. index = styles.Fills.Count
  224. styles.Fills.Count += 1
  225. return
  226. }
  227. func (styles *xlsxStyleSheet) addBorder(xBorder xlsxBorder) (index int) {
  228. var border xlsxBorder
  229. for index, border = range styles.Borders.Border {
  230. if border.Equals(xBorder) {
  231. return index
  232. }
  233. }
  234. styles.Borders.Border = append(styles.Borders.Border, xBorder)
  235. index = styles.Borders.Count
  236. styles.Borders.Count += 1
  237. return
  238. }
  239. func (styles *xlsxStyleSheet) addCellStyleXf(xCellStyleXf xlsxXf) (index int) {
  240. var cellStyleXf xlsxXf
  241. if styles.CellStyleXfs == nil {
  242. styles.CellStyleXfs = &xlsxCellStyleXfs{Count: 0}
  243. }
  244. for index, cellStyleXf = range styles.CellStyleXfs.Xf {
  245. if cellStyleXf.Equals(xCellStyleXf) {
  246. return index
  247. }
  248. }
  249. styles.CellStyleXfs.Xf = append(styles.CellStyleXfs.Xf, xCellStyleXf)
  250. index = styles.CellStyleXfs.Count
  251. styles.CellStyleXfs.Count += 1
  252. return
  253. }
  254. func (styles *xlsxStyleSheet) addCellXf(xCellXf xlsxXf) (index int) {
  255. var cellXf xlsxXf
  256. for index, cellXf = range styles.CellXfs.Xf {
  257. if cellXf.Equals(xCellXf) {
  258. return index
  259. }
  260. }
  261. styles.CellXfs.Xf = append(styles.CellXfs.Xf, xCellXf)
  262. index = styles.CellXfs.Count
  263. styles.CellXfs.Count += 1
  264. return
  265. }
  266. // newNumFmt generate a xlsxNumFmt according the format code. When the FormatCode is built in, it will return a xlsxNumFmt with the NumFmtId defined in ECMA document, otherwise it will generate a new NumFmtId greater than 164.
  267. func (styles *xlsxStyleSheet) newNumFmt(formatCode string) xlsxNumFmt {
  268. if formatCode == "" {
  269. return xlsxNumFmt{NumFmtId: 0, FormatCode: "general"}
  270. }
  271. // built in NumFmts in xmlStyle.go, traverse from the const.
  272. numFmts := make(map[string]int)
  273. for k, v := range builtInNumFmt {
  274. numFmts[v] = k
  275. }
  276. numFmtId, ok := numFmts[formatCode]
  277. if ok {
  278. return xlsxNumFmt{NumFmtId: numFmtId, FormatCode: formatCode}
  279. }
  280. // find the exist xlsxNumFmt
  281. for _, numFmt := range styles.NumFmts.NumFmt {
  282. if formatCode == numFmt.FormatCode {
  283. return numFmt
  284. }
  285. }
  286. // The user define NumFmtId. The one less than 164 in built in.
  287. numFmtId = builtinNumFmtsCount + 1
  288. styles.lock.Lock()
  289. defer styles.lock.Unlock()
  290. for {
  291. // get a unused NumFmtId
  292. if _, ok = styles.numFmtRefTable[numFmtId]; ok {
  293. numFmtId += 1
  294. } else {
  295. styles.addNumFmt(xlsxNumFmt{NumFmtId: numFmtId, FormatCode: formatCode})
  296. break
  297. }
  298. }
  299. return xlsxNumFmt{NumFmtId: numFmtId, FormatCode: formatCode}
  300. }
  301. // addNumFmt add xlsxNumFmt if its not exist.
  302. func (styles *xlsxStyleSheet) addNumFmt(xNumFmt xlsxNumFmt) {
  303. // don't add built in NumFmt
  304. if xNumFmt.NumFmtId <= builtinNumFmtsCount {
  305. return
  306. }
  307. _, ok := styles.numFmtRefTable[xNumFmt.NumFmtId]
  308. if !ok {
  309. if styles.numFmtRefTable == nil {
  310. styles.numFmtRefTable = make(map[int]xlsxNumFmt)
  311. }
  312. styles.NumFmts.NumFmt = append(styles.NumFmts.NumFmt, xNumFmt)
  313. styles.numFmtRefTable[xNumFmt.NumFmtId] = xNumFmt
  314. styles.NumFmts.Count += 1
  315. }
  316. }
  317. func (styles *xlsxStyleSheet) Marshal() (result string, err error) {
  318. var xNumFmts string
  319. var xfonts string
  320. var xfills string
  321. var xborders string
  322. var xcellStyleXfs string
  323. var xcellXfs string
  324. var xcellStyles string
  325. var outputFontMap map[int]int = make(map[int]int)
  326. var outputFillMap map[int]int = make(map[int]int)
  327. var outputBorderMap map[int]int = make(map[int]int)
  328. result = xml.Header
  329. result += `<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
  330. xNumFmts, err = styles.NumFmts.Marshal()
  331. if err != nil {
  332. return
  333. }
  334. result += xNumFmts
  335. xfonts, err = styles.Fonts.Marshal(outputFontMap)
  336. if err != nil {
  337. return
  338. }
  339. result += xfonts
  340. xfills, err = styles.Fills.Marshal(outputFillMap)
  341. if err != nil {
  342. return
  343. }
  344. result += xfills
  345. xborders, err = styles.Borders.Marshal(outputBorderMap)
  346. if err != nil {
  347. return
  348. }
  349. result += xborders
  350. if styles.CellStyleXfs != nil {
  351. xcellStyleXfs, err = styles.CellStyleXfs.Marshal(outputBorderMap, outputFillMap, outputFontMap)
  352. if err != nil {
  353. return
  354. }
  355. result += xcellStyleXfs
  356. }
  357. xcellXfs, err = styles.CellXfs.Marshal(outputBorderMap, outputFillMap, outputFontMap)
  358. if err != nil {
  359. return
  360. }
  361. result += xcellXfs
  362. if styles.CellStyles != nil {
  363. xcellStyles, err = styles.CellStyles.Marshal()
  364. if err != nil {
  365. return
  366. }
  367. result += xcellStyles
  368. }
  369. result += `</styleSheet>`
  370. return
  371. }
  372. // xlsxNumFmts directly maps the numFmts element in the namespace
  373. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  374. // currently I have not checked it for completeness - it does as much
  375. // as I need.
  376. type xlsxNumFmts struct {
  377. Count int `xml:"count,attr"`
  378. NumFmt []xlsxNumFmt `xml:"numFmt,omitempty"`
  379. }
  380. func (numFmts *xlsxNumFmts) Marshal() (result string, err error) {
  381. if numFmts.Count > 0 {
  382. result = fmt.Sprintf(`<numFmts count="%d">`, numFmts.Count)
  383. for _, numFmt := range numFmts.NumFmt {
  384. var xNumFmt string
  385. xNumFmt, err = numFmt.Marshal()
  386. if err != nil {
  387. return
  388. }
  389. result += xNumFmt
  390. }
  391. result += `</numFmts>`
  392. }
  393. return
  394. }
  395. // xlsxNumFmt directly maps the numFmt element in the namespace
  396. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  397. // currently I have not checked it for completeness - it does as much
  398. // as I need.
  399. type xlsxNumFmt struct {
  400. NumFmtId int `xml:"numFmtId,attr,omitempty"`
  401. FormatCode string `xml:"formatCode,attr,omitempty"`
  402. }
  403. func (numFmt *xlsxNumFmt) Marshal() (result string, err error) {
  404. return fmt.Sprintf(`<numFmt numFmtId="%d" formatCode="%s"/>`, numFmt.NumFmtId, numFmt.FormatCode), nil
  405. }
  406. // xlsxFonts directly maps the fonts element in the namespace
  407. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  408. // currently I have not checked it for completeness - it does as much
  409. // as I need.
  410. type xlsxFonts struct {
  411. XMLName xml.Name `xml:"fonts"`
  412. Count int `xml:"count,attr"`
  413. Font []xlsxFont `xml:"font,omitempty"`
  414. }
  415. func (fonts *xlsxFonts) Marshal(outputFontMap map[int]int) (result string, err error) {
  416. emittedCount := 0
  417. subparts := ""
  418. for i, font := range fonts.Font {
  419. var xfont string
  420. xfont, err = font.Marshal()
  421. if err != nil {
  422. return
  423. }
  424. if xfont != "" {
  425. outputFontMap[i] = emittedCount
  426. emittedCount += 1
  427. subparts += xfont
  428. }
  429. }
  430. if emittedCount > 0 {
  431. result = fmt.Sprintf(`<fonts count="%d">`, fonts.Count)
  432. result += subparts
  433. result += `</fonts>`
  434. }
  435. return
  436. }
  437. // xlsxFont directly maps the font element in the namespace
  438. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  439. // currently I have not checked it for completeness - it does as much
  440. // as I need.
  441. type xlsxFont struct {
  442. Sz xlsxVal `xml:"sz,omitempty"`
  443. Name xlsxVal `xml:"name,omitempty"`
  444. Family xlsxVal `xml:"family,omitempty"`
  445. Charset xlsxVal `xml:"charset,omitempty"`
  446. Color xlsxColor `xml:"color,omitempty"`
  447. B *xlsxVal `xml:"b,omitempty"`
  448. I *xlsxVal `xml:"i,omitempty"`
  449. U *xlsxVal `xml:"u,omitempty"`
  450. }
  451. func (font *xlsxFont) Equals(other xlsxFont) bool {
  452. if (font.B == nil && other.B != nil) || (font.B != nil && other.B == nil) {
  453. return false
  454. }
  455. if (font.I == nil && other.I != nil) || (font.I != nil && other.I == nil) {
  456. return false
  457. }
  458. if (font.U == nil && other.U != nil) || (font.U != nil && other.U == nil) {
  459. return false
  460. }
  461. return font.Sz.Equals(other.Sz) && font.Name.Equals(other.Name) && font.Family.Equals(other.Family) && font.Charset.Equals(other.Charset) && font.Color.Equals(other.Color)
  462. }
  463. func (font *xlsxFont) Marshal() (result string, err error) {
  464. result = `<font>`
  465. if font.Sz.Val != "" {
  466. result += fmt.Sprintf(`<sz val="%s"/>`, font.Sz.Val)
  467. }
  468. if font.Name.Val != "" {
  469. result += fmt.Sprintf(`<name val="%s"/>`, font.Name.Val)
  470. }
  471. if font.Family.Val != "" {
  472. result += fmt.Sprintf(`<family val="%s"/>`, font.Family.Val)
  473. }
  474. if font.Charset.Val != "" {
  475. result += fmt.Sprintf(`<charset val="%s"/>`, font.Charset.Val)
  476. }
  477. if font.Color.RGB != "" {
  478. result += fmt.Sprintf(`<color rgb="%s"/>`, font.Color.RGB)
  479. }
  480. if font.B != nil {
  481. result += "<b/>"
  482. }
  483. if font.I != nil {
  484. result += "<i/>"
  485. }
  486. if font.U != nil {
  487. result += "<u/>"
  488. }
  489. result += `</font>`
  490. return
  491. }
  492. // xlsxVal directly maps the val element in the namespace
  493. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  494. // currently I have not checked it for completeness - it does as much
  495. // as I need.
  496. type xlsxVal struct {
  497. Val string `xml:"val,attr,omitempty"`
  498. }
  499. func (val *xlsxVal) Equals(other xlsxVal) bool {
  500. return val.Val == other.Val
  501. }
  502. // xlsxFills directly maps the fills element in the namespace
  503. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  504. // currently I have not checked it for completeness - it does as much
  505. // as I need.
  506. type xlsxFills struct {
  507. Count int `xml:"count,attr"`
  508. Fill []xlsxFill `xml:"fill,omitempty"`
  509. }
  510. func (fills *xlsxFills) Marshal(outputFillMap map[int]int) (result string, err error) {
  511. emittedCount := 0
  512. subparts := ""
  513. for i, fill := range fills.Fill {
  514. var xfill string
  515. xfill, err = fill.Marshal()
  516. if err != nil {
  517. return
  518. }
  519. if xfill != "" {
  520. outputFillMap[i] = emittedCount
  521. emittedCount += 1
  522. subparts += xfill
  523. }
  524. }
  525. if emittedCount > 0 {
  526. result = fmt.Sprintf(`<fills count="%d">`, emittedCount)
  527. result += subparts
  528. result += `</fills>`
  529. }
  530. return
  531. }
  532. // xlsxFill directly maps the fill element in the namespace
  533. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  534. // currently I have not checked it for completeness - it does as much
  535. // as I need.
  536. type xlsxFill struct {
  537. PatternFill xlsxPatternFill `xml:"patternFill,omitempty"`
  538. }
  539. func (fill *xlsxFill) Equals(other xlsxFill) bool {
  540. return fill.PatternFill.Equals(other.PatternFill)
  541. }
  542. func (fill *xlsxFill) Marshal() (result string, err error) {
  543. if fill.PatternFill.PatternType != "" {
  544. var xpatternFill string
  545. result = `<fill>`
  546. xpatternFill, err = fill.PatternFill.Marshal()
  547. if err != nil {
  548. return
  549. }
  550. result += xpatternFill
  551. result += `</fill>`
  552. }
  553. return
  554. }
  555. // xlsxPatternFill directly maps the patternFill element in the namespace
  556. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  557. // currently I have not checked it for completeness - it does as much
  558. // as I need.
  559. type xlsxPatternFill struct {
  560. PatternType string `xml:"patternType,attr,omitempty"`
  561. FgColor xlsxColor `xml:"fgColor,omitempty"`
  562. BgColor xlsxColor `xml:"bgColor,omitempty"`
  563. }
  564. func (patternFill *xlsxPatternFill) Equals(other xlsxPatternFill) bool {
  565. return patternFill.PatternType == other.PatternType && patternFill.FgColor.Equals(other.FgColor) && patternFill.BgColor.Equals(other.BgColor)
  566. }
  567. func (patternFill *xlsxPatternFill) Marshal() (result string, err error) {
  568. result = fmt.Sprintf(`<patternFill patternType="%s"`, patternFill.PatternType)
  569. ending := `/>`
  570. terminator := ""
  571. subparts := ""
  572. if patternFill.FgColor.RGB != "" {
  573. ending = `>`
  574. terminator = "</patternFill>"
  575. subparts += fmt.Sprintf(`<fgColor rgb="%s"/>`, patternFill.FgColor.RGB)
  576. }
  577. if patternFill.BgColor.RGB != "" {
  578. ending = `>`
  579. terminator = "</patternFill>"
  580. subparts += fmt.Sprintf(`<bgColor rgb="%s"/>`, patternFill.BgColor.RGB)
  581. }
  582. result += ending
  583. result += subparts
  584. result += terminator
  585. return
  586. }
  587. // xlsxColor is a common mapping used for both the fgColor and bgColor
  588. // elements in the namespace
  589. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  590. // currently I have not checked it for completeness - it does as much
  591. // as I need.
  592. type xlsxColor struct {
  593. RGB string `xml:"rgb,attr,omitempty"`
  594. Theme *int `xml:"theme,attr,omitempty"`
  595. Tint float64 `xml:"tint,attr,omitempty"`
  596. }
  597. func (color *xlsxColor) Equals(other xlsxColor) bool {
  598. return color.RGB == other.RGB
  599. }
  600. // xlsxBorders directly maps the borders element in the namespace
  601. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  602. // currently I have not checked it for completeness - it does as much
  603. // as I need.
  604. type xlsxBorders struct {
  605. Count int `xml:"count,attr"`
  606. Border []xlsxBorder `xml:"border"`
  607. }
  608. func (borders *xlsxBorders) Marshal(outputBorderMap map[int]int) (result string, err error) {
  609. result = ""
  610. emittedCount := 0
  611. subparts := ""
  612. for i, border := range borders.Border {
  613. var xborder string
  614. xborder, err = border.Marshal()
  615. if err != nil {
  616. return
  617. }
  618. if xborder != "" {
  619. outputBorderMap[i] = emittedCount
  620. emittedCount += 1
  621. subparts += xborder
  622. }
  623. }
  624. if emittedCount > 0 {
  625. result += fmt.Sprintf(`<borders count="%d">`, emittedCount)
  626. result += subparts
  627. result += `</borders>`
  628. }
  629. return
  630. }
  631. // xlsxBorder directly maps the border element in the namespace
  632. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  633. // currently I have not checked it for completeness - it does as much
  634. // as I need.
  635. type xlsxBorder struct {
  636. Left xlsxLine `xml:"left,omitempty"`
  637. Right xlsxLine `xml:"right,omitempty"`
  638. Top xlsxLine `xml:"top,omitempty"`
  639. Bottom xlsxLine `xml:"bottom,omitempty"`
  640. }
  641. func (border *xlsxBorder) Equals(other xlsxBorder) bool {
  642. return border.Left.Equals(other.Left) && border.Right.Equals(other.Right) && border.Top.Equals(other.Top) && border.Bottom.Equals(other.Bottom)
  643. }
  644. // To get borders to work correctly in Excel, you have to always start with an
  645. // empty set of borders. There was logic in this function that would strip out
  646. // empty elements, but unfortunately that would cause the border to fail.
  647. func (border *xlsxBorder) Marshal() (result string, err error) {
  648. subparts := ""
  649. subparts += fmt.Sprintf(`<left style="%s">`, border.Left.Style)
  650. if border.Left.Color.RGB != "" {
  651. subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Left.Color.RGB)
  652. }
  653. subparts += `</left>`
  654. subparts += fmt.Sprintf(`<right style="%s">`, border.Right.Style)
  655. if border.Right.Color.RGB != "" {
  656. subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Right.Color.RGB)
  657. }
  658. subparts += `</right>`
  659. subparts += fmt.Sprintf(`<top style="%s">`, border.Top.Style)
  660. if border.Top.Color.RGB != "" {
  661. subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Top.Color.RGB)
  662. }
  663. subparts += `</top>`
  664. subparts += fmt.Sprintf(`<bottom style="%s">`, border.Bottom.Style)
  665. if border.Bottom.Color.RGB != "" {
  666. subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Bottom.Color.RGB)
  667. }
  668. subparts += `</bottom>`
  669. result += `<border>`
  670. result += subparts
  671. result += `</border>`
  672. return
  673. }
  674. // xlsxLine directly maps the line style element in the namespace
  675. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  676. // currently I have not checked it for completeness - it does as much
  677. // as I need.
  678. type xlsxLine struct {
  679. Style string `xml:"style,attr,omitempty"`
  680. Color xlsxColor `xml:"color,omitempty"`
  681. }
  682. func (line *xlsxLine) Equals(other xlsxLine) bool {
  683. return line.Style == other.Style && line.Color.Equals(other.Color)
  684. }
  685. type xlsxCellStyles struct {
  686. XMLName xml.Name `xml:"cellStyles"`
  687. Count int `xml:"count,attr"`
  688. CellStyle []xlsxCellStyle `xml:"cellStyle,omitempty"`
  689. }
  690. func (cellStyles *xlsxCellStyles) Marshal() (result string, err error) {
  691. if cellStyles.Count > 0 {
  692. result = fmt.Sprintf(`<cellStyles count="%d">`, cellStyles.Count)
  693. for _, cellStyle := range cellStyles.CellStyle {
  694. var xCellStyle []byte
  695. xCellStyle, err = xml.Marshal(cellStyle)
  696. if err != nil {
  697. return
  698. }
  699. result += string(xCellStyle)
  700. }
  701. result += `</cellStyles>`
  702. }
  703. return
  704. }
  705. type xlsxCellStyle struct {
  706. XMLName xml.Name `xml:"cellStyle"`
  707. BuiltInId *int `xml:"builtInId,attr,omitempty"`
  708. CustomBuiltIn *bool `xml:"customBuiltIn,attr,omitempty"`
  709. Hidden *bool `xml:"hidden,attr,omitempty"`
  710. ILevel *bool `xml:"iLevel,attr,omitempty"`
  711. Name string `xml:"name,attr"`
  712. XfId int `xml:"xfId,attr"`
  713. }
  714. // xlsxCellStyleXfs directly maps the cellStyleXfs element in the
  715. // namespace http://schemas.openxmlformats.org/spreadsheetml/2006/main
  716. // - currently I have not checked it for completeness - it does as
  717. // much as I need.
  718. type xlsxCellStyleXfs struct {
  719. Count int `xml:"count,attr"`
  720. Xf []xlsxXf `xml:"xf,omitempty"`
  721. }
  722. func (cellStyleXfs *xlsxCellStyleXfs) Marshal(outputBorderMap, outputFillMap, outputFontMap map[int]int) (result string, err error) {
  723. if cellStyleXfs.Count > 0 {
  724. result = fmt.Sprintf(`<cellStyleXfs count="%d">`, cellStyleXfs.Count)
  725. for _, xf := range cellStyleXfs.Xf {
  726. var xxf string
  727. xxf, err = xf.Marshal(outputBorderMap, outputFillMap, outputFontMap)
  728. if err != nil {
  729. return
  730. }
  731. result += xxf
  732. }
  733. result += `</cellStyleXfs>`
  734. }
  735. return
  736. }
  737. // xlsxCellXfs directly maps the cellXfs element in the namespace
  738. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  739. // currently I have not checked it for completeness - it does as much
  740. // as I need.
  741. type xlsxCellXfs struct {
  742. Count int `xml:"count,attr"`
  743. Xf []xlsxXf `xml:"xf,omitempty"`
  744. }
  745. func (cellXfs *xlsxCellXfs) Marshal(outputBorderMap, outputFillMap, outputFontMap map[int]int) (result string, err error) {
  746. if cellXfs.Count > 0 {
  747. result = fmt.Sprintf(`<cellXfs count="%d">`, cellXfs.Count)
  748. for _, xf := range cellXfs.Xf {
  749. var xxf string
  750. xxf, err = xf.Marshal(outputBorderMap, outputFillMap, outputFontMap)
  751. if err != nil {
  752. return
  753. }
  754. result += xxf
  755. }
  756. result += `</cellXfs>`
  757. }
  758. return
  759. }
  760. // xlsxXf directly maps the xf element in the namespace
  761. // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
  762. // currently I have not checked it for completeness - it does as much
  763. // as I need.
  764. type xlsxXf struct {
  765. ApplyAlignment bool `xml:"applyAlignment,attr"`
  766. ApplyBorder bool `xml:"applyBorder,attr"`
  767. ApplyFont bool `xml:"applyFont,attr"`
  768. ApplyFill bool `xml:"applyFill,attr"`
  769. ApplyNumberFormat bool `xml:"applyNumberFormat,attr"`
  770. ApplyProtection bool `xml:"applyProtection,attr"`
  771. BorderId int `xml:"borderId,attr"`
  772. FillId int `xml:"fillId,attr"`
  773. FontId int `xml:"fontId,attr"`
  774. NumFmtId int `xml:"numFmtId,attr"`
  775. XfId *int `xml:"xfId,attr,omitempty"`
  776. Alignment xlsxAlignment `xml:"alignment"`
  777. }
  778. func (xf *xlsxXf) Equals(other xlsxXf) bool {
  779. return xf.ApplyAlignment == other.ApplyAlignment &&
  780. xf.ApplyBorder == other.ApplyBorder &&
  781. xf.ApplyFont == other.ApplyFont &&
  782. xf.ApplyFill == other.ApplyFill &&
  783. xf.ApplyProtection == other.ApplyProtection &&
  784. xf.BorderId == other.BorderId &&
  785. xf.FillId == other.FillId &&
  786. xf.FontId == other.FontId &&
  787. xf.NumFmtId == other.NumFmtId &&
  788. (xf.XfId == other.XfId ||
  789. ((xf.XfId != nil && other.XfId != nil) &&
  790. *xf.XfId == *other.XfId)) &&
  791. xf.Alignment.Equals(other.Alignment)
  792. }
  793. func (xf *xlsxXf) Marshal(outputBorderMap, outputFillMap, outputFontMap map[int]int) (result string, err error) {
  794. var xAlignment string
  795. result = fmt.Sprintf(`<xf applyAlignment="%b" applyBorder="%b" applyFont="%b" applyFill="%b" applyNumberFormat="%b" applyProtection="%b" borderId="%d" fillId="%d" fontId="%d" numFmtId="%d"`, bool2Int(xf.ApplyAlignment), bool2Int(xf.ApplyBorder), bool2Int(xf.ApplyFont), bool2Int(xf.ApplyFill), bool2Int(xf.ApplyNumberFormat), bool2Int(xf.ApplyProtection), outputBorderMap[xf.BorderId], outputFillMap[xf.FillId], outputFontMap[xf.FontId], xf.NumFmtId)
  796. if xf.XfId != nil {
  797. result += fmt.Sprintf(` xfId="%d"`, *xf.XfId)
  798. }
  799. result += ">"
  800. xAlignment, err = xf.Alignment.Marshal()
  801. if err != nil {
  802. return
  803. }
  804. result += xAlignment
  805. result += `</xf>`
  806. return
  807. }
  808. type xlsxAlignment struct {
  809. Horizontal string `xml:"horizontal,attr"`
  810. Indent int `xml:"indent,attr"`
  811. ShrinkToFit bool `xml:"shrinkToFit,attr"`
  812. TextRotation int `xml:"textRotation,attr"`
  813. Vertical string `xml:"vertical,attr"`
  814. WrapText bool `xml:"wrapText,attr"`
  815. }
  816. func (alignment *xlsxAlignment) Equals(other xlsxAlignment) bool {
  817. return alignment.Horizontal == other.Horizontal &&
  818. alignment.Indent == other.Indent &&
  819. alignment.ShrinkToFit == other.ShrinkToFit &&
  820. alignment.TextRotation == other.TextRotation &&
  821. alignment.Vertical == other.Vertical &&
  822. alignment.WrapText == other.WrapText
  823. }
  824. func (alignment *xlsxAlignment) Marshal() (result string, err error) {
  825. if alignment.Horizontal == "" {
  826. alignment.Horizontal = "general"
  827. }
  828. if alignment.Vertical == "" {
  829. alignment.Vertical = "bottom"
  830. }
  831. result = fmt.Sprintf(`<alignment horizontal="%s" indent="%d" shrinkToFit="%b" textRotation="%d" vertical="%s" wrapText="%b"/>`, alignment.Horizontal, alignment.Indent, bool2Int(alignment.ShrinkToFit), alignment.TextRotation, alignment.Vertical, bool2Int(alignment.WrapText))
  832. return
  833. }
  834. func bool2Int(b bool) int {
  835. if b {
  836. return 1
  837. }
  838. return 0
  839. }