xmlStyle.go 27 KB

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