// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.16 or later. package excelize import ( "bytes" "encoding/xml" "fmt" "io" "math" "reflect" "sort" "strconv" "strings" ) // Excel styles can reference number formats that are built-in, all of which // have an id less than 164. Note that this number format code list is under // English localization. var builtInNumFmt = map[int]string{ 0: "general", 1: "0", 2: "0.00", 3: "#,##0", 4: "#,##0.00", 9: "0%", 10: "0.00%", 11: "0.00e+00", 12: "# ?/?", 13: "# ??/??", 14: "mm-dd-yy", 15: "d-mmm-yy", 16: "d-mmm", 17: "mmm-yy", 18: "h:mm am/pm", 19: "h:mm:ss am/pm", 20: "hh:mm", 21: "hh:mm:ss", 22: "m/d/yy hh:mm", 37: "#,##0 ;(#,##0)", 38: "#,##0 ;[red](#,##0)", 39: "#,##0.00;(#,##0.00)", 40: "#,##0.00;[red](#,##0.00)", 41: `_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)`, 42: `_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)`, 43: `_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)`, 44: `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`, 45: "mm:ss", 46: "[h]:mm:ss", 47: "mmss.0", 48: "##0.0e+0", 49: "@", } // langNumFmt defined number format code (with unicode values provided for // language glyphs where they occur) in different language. var langNumFmt = map[string]map[int]string{ "zh-tw": { 27: "[$-404]e/m/d", 28: `[$-404]e"年"m"月"d"日"`, 29: `[$-404]e"年"m"月"d"日"`, 30: "m/d/yy", 31: `yyyy"年"m"月"d"日"`, 32: `hh"時"mm"分"`, 33: `hh"時"mm"分"ss"秒"`, 34: `上午/下午 hh"時"mm"分"`, 35: `上午/下午 hh"時"mm"分"ss"秒"`, 36: "[$-404]e/m/d", 50: "[$-404]e/m/d", 51: `[$-404]e"年"m"月"d"日"`, 52: `上午/下午 hh"時"mm"分"`, 53: `上午/下午 hh"時"mm"分"ss"秒"`, 54: `[$-404]e"年"m"月"d"日"`, 55: `上午/下午 hh"時"mm"分"`, 56: `上午/下午 hh"時"mm"分"ss"秒"`, 57: "[$-404]e/m/d", 58: `[$-404]e"年"m"月"d"日"`, }, "zh-cn": { 27: `yyyy"年"m"月"`, 28: `m"月"d"日"`, 29: `m"月"d"日"`, 30: "m-d-yy", 31: `yyyy"年"m"月"d"日"`, 32: `h"时"mm"分"`, 33: `h"时"mm"分"ss"秒"`, 34: `上午/下午 h"时"mm"分"`, 35: `上午/下午 h"时"mm"分"ss"秒"`, 36: `yyyy"年"m"月"`, 50: `yyyy"年"m"月"`, 51: `m"月"d"日"`, 52: `yyyy"年"m"月"`, 53: `m"月"d"日"`, 54: `m"月"d"日"`, 55: `上午/下午 h"时"mm"分"`, 56: `上午/下午 h"时"mm"分"ss"秒"`, 57: `yyyy"年"m"月"`, 58: `m"月"d"日"`, }, "zh-tw_unicode": { 27: "[$-404]e/m/d", 28: `[$-404]e"5E74"m"6708"d"65E5"`, 29: `[$-404]e"5E74"m"6708"d"65E5"`, 30: "m/d/yy", 31: `yyyy"5E74"m"6708"d"65E5"`, 32: `hh"6642"mm"5206"`, 33: `hh"6642"mm"5206"ss"79D2"`, 34: `4E0A5348/4E0B5348hh"6642"mm"5206"`, 35: `4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2"`, 36: "[$-404]e/m/d", 50: "[$-404]e/m/d", 51: `[$-404]e"5E74"m"6708"d"65E5"`, 52: `4E0A5348/4E0B5348hh"6642"mm"5206"`, 53: `4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2"`, 54: `[$-404]e"5E74"m"6708"d"65E5"`, 55: `4E0A5348/4E0B5348hh"6642"mm"5206"`, 56: `4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2"`, 57: "[$-404]e/m/d", 58: `[$-404]e"5E74"m"6708"d"65E5"`, }, "zh-cn_unicode": { 27: `yyyy"5E74"m"6708"`, 28: `m"6708"d"65E5"`, 29: `m"6708"d"65E5"`, 30: "m-d-yy", 31: `yyyy"5E74"m"6708"d"65E5"`, 32: `h"65F6"mm"5206"`, 33: `h"65F6"mm"5206"ss"79D2"`, 34: `4E0A5348/4E0B5348h"65F6"mm"5206"`, 35: `4E0A5348/4E0B5348h"65F6"mm"5206"ss"79D2"`, 36: `yyyy"5E74"m"6708"`, 50: `yyyy"5E74"m"6708"`, 51: `m"6708"d"65E5"`, 52: `yyyy"5E74"m"6708"`, 53: `m"6708"d"65E5"`, 54: `m"6708"d"65E5"`, 55: `4E0A5348/4E0B5348h"65F6"mm"5206"`, 56: `4E0A5348/4E0B5348h"65F6"mm"5206"ss"79D2"`, 57: `yyyy"5E74"m"6708"`, 58: `m"6708"d"65E5"`, }, "ja-jp": { 27: "[$-411]ge.m.d", 28: `[$-411]ggge"年"m"月"d"日"`, 29: `[$-411]ggge"年"m"月"d"日"`, 30: "m/d/yy", 31: `yyyy"年"m"月"d"日"`, 32: `h"時"mm"分"`, 33: `h"時"mm"分"ss"秒"`, 34: `yyyy"年"m"月"`, 35: `m"月"d"日"`, 36: "[$-411]ge.m.d", 50: "[$-411]ge.m.d", 51: `[$-411]ggge"年"m"月"d"日"`, 52: `yyyy"年"m"月"`, 53: `m"月"d"日"`, 54: `[$-411]ggge"年"m"月"d"日"`, 55: `yyyy"年"m"月"`, 56: `m"月"d"日"`, 57: "[$-411]ge.m.d", 58: `[$-411]ggge"年"m"月"d"日"`, }, "ko-kr": { 27: `yyyy"年" mm"月" dd"日"`, 28: "mm-dd", 29: "mm-dd", 30: "mm-dd-yy", 31: `yyyy"년" mm"월" dd"일"`, 32: `h"시" mm"분"`, 33: `h"시" mm"분" ss"초"`, 34: `yyyy-mm-dd`, 35: `yyyy-mm-dd`, 36: `yyyy"年" mm"月" dd"日"`, 50: `yyyy"年" mm"月" dd"日"`, 51: "mm-dd", 52: "yyyy-mm-dd", 53: "yyyy-mm-dd", 54: "mm-dd", 55: "yyyy-mm-dd", 56: "yyyy-mm-dd", 57: `yyyy"年" mm"月" dd"日"`, 58: "mm-dd", }, "ja-jp_unicode": { 27: "[$-411]ge.m.d", 28: `[$-411]ggge"5E74"m"6708"d"65E5"`, 29: `[$-411]ggge"5E74"m"6708"d"65E5"`, 30: "m/d/yy", 31: `yyyy"5E74"m"6708"d"65E5"`, 32: `h"6642"mm"5206"`, 33: `h"6642"mm"5206"ss"79D2"`, 34: `yyyy"5E74"m"6708"`, 35: `m"6708"d"65E5"`, 36: "[$-411]ge.m.d", 50: "[$-411]ge.m.d", 51: `[$-411]ggge"5E74"m"6708"d"65E5"`, 52: `yyyy"5E74"m"6708"`, 53: `m"6708"d"65E5"`, 54: `[$-411]ggge"5E74"m"6708"d"65E5"`, 55: `yyyy"5E74"m"6708"`, 56: `m"6708"d"65E5"`, 57: "[$-411]ge.m.d", 58: `[$-411]ggge"5E74"m"6708"d"65E5"`, }, "ko-kr_unicode": { 27: `yyyy"5E74" mm"6708" dd"65E5"`, 28: "mm-dd", 29: "mm-dd", 30: "mm-dd-yy", 31: `yyyy"B144" mm"C6D4" dd"C77C"`, 32: `h"C2DC" mm"BD84"`, 33: `h"C2DC" mm"BD84" ss"CD08"`, 34: "yyyy-mm-dd", 35: "yyyy-mm-dd", 36: `yyyy"5E74" mm"6708" dd"65E5"`, 50: `yyyy"5E74" mm"6708" dd"65E5"`, 51: "mm-dd", 52: "yyyy-mm-dd", 53: "yyyy-mm-dd", 54: "mm-dd", 55: "yyyy-mm-dd", 56: "yyyy-mm-dd", 57: `yyyy"5E74" mm"6708" dd"65E5"`, 58: "mm-dd", }, "th-th": { 59: "t0", 60: "t0.00", 61: "t#,##0", 62: "t#,##0.00", 67: "t0%", 68: "t0.00%", 69: "t# ?/?", 70: "t# ??/??", 71: "ว/ด/ปปปป", 72: "ว-ดดด-ปป", 73: "ว-ดดด", 74: "ดดด-ปป", 75: "ช:นน", 76: "ช:นน:ทท", 77: "ว/ด/ปปปป ช:นน", 78: "นน:ทท", 79: "[ช]:นน:ทท", 80: "นน:ทท.0", 81: "d/m/bb", }, "th-th_unicode": { 59: "t0", 60: "t0.00", 61: "t#,##0", 62: "t#,##0.00", 67: "t0%", 68: "t0.00%", 69: "t# ?/?", 70: "t# ??/??", 71: "0E27/0E14/0E1B0E1B0E1B0E1B", 72: "0E27-0E140E140E14-0E1B0E1B", 73: "0E27-0E140E140E14", 74: "0E140E140E14-0E1B0E1B", 75: "0E0A:0E190E19", 76: "0E0A:0E190E19:0E170E17", 77: "0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E19", 78: "0E190E19:0E170E17", 79: "[0E0A]:0E190E19:0E170E17", 80: "0E190E19:0E170E17.0", 81: "d/m/bb", }, } // currencyNumFmt defined the currency number format map. var currencyNumFmt = map[int]string{ 164: `"¥"#,##0.00`, 165: "[$$-409]#,##0.00", 166: "[$$-45C]#,##0.00", 167: "[$$-1004]#,##0.00", 168: "[$$-404]#,##0.00", 169: "[$$-C09]#,##0.00", 170: "[$$-2809]#,##0.00", 171: "[$$-1009]#,##0.00", 172: "[$$-2009]#,##0.00", 173: "[$$-1409]#,##0.00", 174: "[$$-4809]#,##0.00", 175: "[$$-2C09]#,##0.00", 176: "[$$-2409]#,##0.00", 177: "[$$-1000]#,##0.00", 178: `#,##0.00\ [$$-C0C]`, 179: "[$$-475]#,##0.00", 180: "[$$-83E]#,##0.00", 181: `[$$-86B]\ #,##0.00`, 182: `[$$-340A]\ #,##0.00`, 183: "[$$-240A]#,##0.00", 184: `[$$-300A]\ #,##0.00`, 185: "[$$-440A]#,##0.00", 186: "[$$-80A]#,##0.00", 187: "[$$-500A]#,##0.00", 188: "[$$-540A]#,##0.00", 189: `[$$-380A]\ #,##0.00`, 190: "[$£-809]#,##0.00", 191: "[$£-491]#,##0.00", 192: "[$£-452]#,##0.00", 193: "[$¥-804]#,##0.00", 194: "[$¥-411]#,##0.00", 195: "[$¥-478]#,##0.00", 196: "[$¥-451]#,##0.00", 197: "[$¥-480]#,##0.00", 198: "#,##0.00\\ [$\u058F-42B]", 199: "[$\u060B-463]#,##0.00", 200: "[$\u060B-48C]#,##0.00", 201: "[$\u09F3-845]\\ #,##0.00", 202: "#,##0.00[$\u17DB-453]", 203: "[$\u20A1-140A]#,##0.00", 204: "[$\u20A6-468]\\ #,##0.00", 205: "[$\u20A6-470]\\ #,##0.00", 206: "[$\u20A9-412]#,##0.00", 207: "[$\u20AA-40D]\\ #,##0.00", 208: "#,##0.00\\ [$\u20AB-42A]", 209: "#,##0.00\\ [$\u20AC-42D]", 210: "#,##0.00\\ [$\u20AC-47E]", 211: "#,##0.00\\ [$\u20AC-403]", 212: "#,##0.00\\ [$\u20AC-483]", 213: "[$\u20AC-813]\\ #,##0.00", 214: "[$\u20AC-413]\\ #,##0.00", 215: "[$\u20AC-1809]#,##0.00", 216: "#,##0.00\\ [$\u20AC-425]", 217: "[$\u20AC-2]\\ #,##0.00", 218: "#,##0.00\\ [$\u20AC-1]", 219: "#,##0.00\\ [$\u20AC-40B]", 220: "#,##0.00\\ [$\u20AC-80C]", 221: "#,##0.00\\ [$\u20AC-40C]", 222: "#,##0.00\\ [$\u20AC-140C]", 223: "#,##0.00\\ [$\u20AC-180C]", 224: "[$\u20AC-200C]#,##0.00", 225: "#,##0.00\\ [$\u20AC-456]", 226: "#,##0.00\\ [$\u20AC-C07]", 227: "#,##0.00\\ [$\u20AC-407]", 228: "#,##0.00\\ [$\u20AC-1007]", 229: "#,##0.00\\ [$\u20AC-408]", 230: "#,##0.00\\ [$\u20AC-243B]", 231: "[$\u20AC-83C]#,##0.00", 232: "[$\u20AC-410]\\ #,##0.00", 233: "[$\u20AC-476]#,##0.00", 234: "#,##0.00\\ [$\u20AC-2C1A]", 235: "[$\u20AC-426]\\ #,##0.00", 236: "#,##0.00\\ [$\u20AC-427]", 237: "#,##0.00\\ [$\u20AC-82E]", 238: "#,##0.00\\ [$\u20AC-46E]", 239: "[$\u20AC-43A]#,##0.00", 240: "#,##0.00\\ [$\u20AC-C3B]", 241: "#,##0.00\\ [$\u20AC-482]", 242: "#,##0.00\\ [$\u20AC-816]", 243: "#,##0.00\\ [$\u20AC-301A]", 244: "#,##0.00\\ [$\u20AC-203B]", 245: "#,##0.00\\ [$\u20AC-41B]", 246: "#,##0.00\\ [$\u20AC-424]", 247: "#,##0.00\\ [$\u20AC-C0A]", 248: "#,##0.00\\ [$\u20AC-81D]", 249: "#,##0.00\\ [$\u20AC-484]", 250: "#,##0.00\\ [$\u20AC-42E]", 251: "[$\u20AC-462]\\ #,##0.00", 252: "#,##0.00\\ [$₭-454]", 253: "#,##0.00\\ [$₮-450]", 254: "[$\u20AE-C50]#,##0.00", 255: "[$\u20B1-3409]#,##0.00", 256: "[$\u20B1-464]#,##0.00", 257: "#,##0.00[$\u20B4-422]", 258: "[$\u20B8-43F]#,##0.00", 259: "[$\u20B9-460]#,##0.00", 260: "[$\u20B9-4009]\\ #,##0.00", 261: "[$\u20B9-447]\\ #,##0.00", 262: "[$\u20B9-439]\\ #,##0.00", 263: "[$\u20B9-44B]\\ #,##0.00", 264: "[$\u20B9-860]#,##0.00", 265: "[$\u20B9-457]\\ #,##0.00", 266: "[$\u20B9-458]#,##0.00", 267: "[$\u20B9-44E]\\ #,##0.00", 268: "[$\u20B9-861]#,##0.00", 269: "[$\u20B9-448]\\ #,##0.00", 270: "[$\u20B9-446]\\ #,##0.00", 271: "[$\u20B9-44F]\\ #,##0.00", 272: "[$\u20B9-459]#,##0.00", 273: "[$\u20B9-449]\\ #,##0.00", 274: "[$\u20B9-820]#,##0.00", 275: "#,##0.00\\ [$\u20BA-41F]", 276: "#,##0.00\\ [$\u20BC-42C]", 277: "#,##0.00\\ [$\u20BC-82C]", 278: "#,##0.00\\ [$\u20BD-419]", 279: "#,##0.00[$\u20BD-485]", 280: "#,##0.00\\ [$\u20BE-437]", 281: "[$B/.-180A]\\ #,##0.00", 282: "[$Br-472]#,##0.00", 283: "[$Br-477]#,##0.00", 284: "#,##0.00[$Br-473]", 285: "[$Bs-46B]\\ #,##0.00", 286: "[$Bs-400A]\\ #,##0.00", 287: "[$Bs.-200A]\\ #,##0.00", 288: "[$BWP-832]\\ #,##0.00", 289: "[$C$-4C0A]#,##0.00", 290: "[$CA$-85D]#,##0.00", 291: "[$CA$-47C]#,##0.00", 292: "[$CA$-45D]#,##0.00", 293: "[$CFA-340C]#,##0.00", 294: "[$CFA-280C]#,##0.00", 295: "#,##0.00\\ [$CFA-867]", 296: "#,##0.00\\ [$CFA-488]", 297: "#,##0.00\\ [$CHF-100C]", 298: "[$CHF-1407]\\ #,##0.00", 299: "[$CHF-807]\\ #,##0.00", 300: "[$CHF-810]\\ #,##0.00", 301: "[$CHF-417]\\ #,##0.00", 302: "[$CLP-47A]\\ #,##0.00", 303: "[$CN¥-850]#,##0.00", 304: "#,##0.00\\ [$DZD-85F]", 305: "[$FCFA-2C0C]#,##0.00", 306: "#,##0.00\\ [$Ft-40E]", 307: "[$G-3C0C]#,##0.00", 308: "[$Gs.-3C0A]\\ #,##0.00", 309: "[$GTQ-486]#,##0.00", 310: "[$HK$-C04]#,##0.00", 311: "[$HK$-3C09]#,##0.00", 312: "#,##0.00\\ [$HRK-41A]", 313: "[$IDR-3809]#,##0.00", 314: "[$IQD-492]#,##0.00", 315: "#,##0.00\\ [$ISK-40F]", 316: "[$K-455]#,##0.00", 317: "#,##0.00\\ [$K\u010D-405]", 318: "#,##0.00\\ [$KM-141A]", 319: "#,##0.00\\ [$KM-101A]", 320: "#,##0.00\\ [$KM-181A]", 321: "[$kr-438]\\ #,##0.00", 322: "[$kr-43B]\\ #,##0.00", 323: "#,##0.00\\ [$kr-83B]", 324: "[$kr-414]\\ #,##0.00", 325: "[$kr-814]\\ #,##0.00", 326: "#,##0.00\\ [$kr-41D]", 327: "[$kr.-406]\\ #,##0.00", 328: "[$kr.-46F]\\ #,##0.00", 329: "[$Ksh-441]#,##0.00", 330: "[$L-818]#,##0.00", 331: "[$L-819]#,##0.00", 332: "[$L-480A]\\ #,##0.00", 333: "#,##0.00\\ [$Lek\u00EB-41C]", 334: "[$MAD-45F]#,##0.00", 335: "[$MAD-380C]#,##0.00", 336: "#,##0.00\\ [$MAD-105F]", 337: "[$MOP$-1404]#,##0.00", 338: "#,##0.00\\ [$MVR-465]_-", 339: "#,##0.00[$Nfk-873]", 340: "[$NGN-466]#,##0.00", 341: "[$NGN-467]#,##0.00", 342: "[$NGN-469]#,##0.00", 343: "[$NGN-471]#,##0.00", 344: "[$NOK-103B]\\ #,##0.00", 345: "[$NOK-183B]\\ #,##0.00", 346: "[$NZ$-481]#,##0.00", 347: "[$PKR-859]\\ #,##0.00", 348: "[$PYG-474]#,##0.00", 349: "[$Q-100A]#,##0.00", 350: "[$R-436]\\ #,##0.00", 351: "[$R-1C09]\\ #,##0.00", 352: "[$R-435]\\ #,##0.00", 353: "[$R$-416]\\ #,##0.00", 354: "[$RD$-1C0A]#,##0.00", 355: "#,##0.00\\ [$RF-487]", 356: "[$RM-4409]#,##0.00", 357: "[$RM-43E]#,##0.00", 358: "#,##0.00\\ [$RON-418]", 359: "[$Rp-421]#,##0.00", 360: "[$Rs-420]#,##0.00_-", 361: "[$Rs.-849]\\ #,##0.00", 362: "#,##0.00\\ [$RSD-81A]", 363: "#,##0.00\\ [$RSD-C1A]", 364: "#,##0.00\\ [$RUB-46D]", 365: "#,##0.00\\ [$RUB-444]", 366: "[$S/.-C6B]\\ #,##0.00", 367: "[$S/.-280A]\\ #,##0.00", 368: "#,##0.00\\ [$SEK-143B]", 369: "#,##0.00\\ [$SEK-1C3B]", 370: "#,##0.00\\ [$so\u02BBm-443]", 371: "#,##0.00\\ [$so\u02BBm-843]", 372: "#,##0.00\\ [$SYP-45A]", 373: "[$THB-41E]#,##0.00", 374: "#,##0.00[$TMT-442]", 375: "[$US$-3009]#,##0.00", 376: "[$ZAR-46C]\\ #,##0.00", 377: "[$ZAR-430]#,##0.00", 378: "[$ZAR-431]#,##0.00", 379: "[$ZAR-432]\\ #,##0.00", 380: "[$ZAR-433]#,##0.00", 381: "[$ZAR-434]\\ #,##0.00", 382: "#,##0.00\\ [$z\u0142-415]", 383: "#,##0.00\\ [$\u0434\u0435\u043D-42F]", 384: "#,##0.00\\ [$КМ-201A]", 385: "#,##0.00\\ [$КМ-1C1A]", 386: "#,##0.00\\ [$\u043B\u0432.-402]", 387: "#,##0.00\\ [$р.-423]", 388: "#,##0.00\\ [$\u0441\u043E\u043C-440]", 389: "#,##0.00\\ [$\u0441\u043E\u043C-428]", 390: "[$\u062C.\u0645.-C01]\\ #,##0.00_-", 391: "[$\u062F.\u0623.-2C01]\\ #,##0.00_-", 392: "[$\u062F.\u0625.-3801]\\ #,##0.00_-", 393: "[$\u062F.\u0628.-3C01]\\ #,##0.00_-", 394: "[$\u062F.\u062A.-1C01]\\ #,##0.00_-", 395: "[$\u062F.\u062C.-1401]\\ #,##0.00_-", 396: "[$\u062F.\u0639.-801]\\ #,##0.00_-", 397: "[$\u062F.\u0643.-3401]\\ #,##0.00_-", 398: "[$\u062F.\u0644.-1001]#,##0.00_-", 399: "[$\u062F.\u0645.-1801]\\ #,##0.00_-", 400: "[$\u0631-846]\\ #,##0.00", 401: "[$\u0631.\u0633.-401]\\ #,##0.00_-", 402: "[$\u0631.\u0639.-2001]\\ #,##0.00_-", 403: "[$\u0631.\u0642.-4001]\\ #,##0.00_-", 404: "[$\u0631.\u064A.-2401]\\ #,##0.00_-", 405: "[$\u0631\u06CC\u0627\u0644-429]#,##0.00_-", 406: "[$\u0644.\u0633.-2801]\\ #,##0.00_-", 407: "[$\u0644.\u0644.-3001]\\ #,##0.00_-", 408: "[$\u1265\u122D-45E]#,##0.00", 409: "[$\u0930\u0942-461]#,##0.00", 410: "[$\u0DBB\u0DD4.-45B]\\ #,##0.00", 411: "[$ADP]\\ #,##0.00", 412: "[$AED]\\ #,##0.00", 413: "[$AFA]\\ #,##0.00", 414: "[$AFN]\\ #,##0.00", 415: "[$ALL]\\ #,##0.00", 416: "[$AMD]\\ #,##0.00", 417: "[$ANG]\\ #,##0.00", 418: "[$AOA]\\ #,##0.00", 419: "[$ARS]\\ #,##0.00", 420: "[$ATS]\\ #,##0.00", 421: "[$AUD]\\ #,##0.00", 422: "[$AWG]\\ #,##0.00", 423: "[$AZM]\\ #,##0.00", 424: "[$AZN]\\ #,##0.00", 425: "[$BAM]\\ #,##0.00", 426: "[$BBD]\\ #,##0.00", 427: "[$BDT]\\ #,##0.00", 428: "[$BEF]\\ #,##0.00", 429: "[$BGL]\\ #,##0.00", 430: "[$BGN]\\ #,##0.00", 431: "[$BHD]\\ #,##0.00", 432: "[$BIF]\\ #,##0.00", 433: "[$BMD]\\ #,##0.00", 434: "[$BND]\\ #,##0.00", 435: "[$BOB]\\ #,##0.00", 436: "[$BOV]\\ #,##0.00", 437: "[$BRL]\\ #,##0.00", 438: "[$BSD]\\ #,##0.00", 439: "[$BTN]\\ #,##0.00", 440: "[$BWP]\\ #,##0.00", 441: "[$BYR]\\ #,##0.00", 442: "[$BZD]\\ #,##0.00", 443: "[$CAD]\\ #,##0.00", 444: "[$CDF]\\ #,##0.00", 445: "[$CHE]\\ #,##0.00", 446: "[$CHF]\\ #,##0.00", 447: "[$CHW]\\ #,##0.00", 448: "[$CLF]\\ #,##0.00", 449: "[$CLP]\\ #,##0.00", 450: "[$CNY]\\ #,##0.00", 451: "[$COP]\\ #,##0.00", 452: "[$COU]\\ #,##0.00", 453: "[$CRC]\\ #,##0.00", 454: "[$CSD]\\ #,##0.00", 455: "[$CUC]\\ #,##0.00", 456: "[$CVE]\\ #,##0.00", 457: "[$CYP]\\ #,##0.00", 458: "[$CZK]\\ #,##0.00", 459: "[$DEM]\\ #,##0.00", 460: "[$DJF]\\ #,##0.00", 461: "[$DKK]\\ #,##0.00", 462: "[$DOP]\\ #,##0.00", 463: "[$DZD]\\ #,##0.00", 464: "[$ECS]\\ #,##0.00", 465: "[$ECV]\\ #,##0.00", 466: "[$EEK]\\ #,##0.00", 467: "[$EGP]\\ #,##0.00", 468: "[$ERN]\\ #,##0.00", 469: "[$ESP]\\ #,##0.00", 470: "[$ETB]\\ #,##0.00", 471: "[$EUR]\\ #,##0.00", 472: "[$FIM]\\ #,##0.00", 473: "[$FJD]\\ #,##0.00", 474: "[$FKP]\\ #,##0.00", 475: "[$FRF]\\ #,##0.00", 476: "[$GBP]\\ #,##0.00", 477: "[$GEL]\\ #,##0.00", 478: "[$GHC]\\ #,##0.00", 479: "[$GHS]\\ #,##0.00", 480: "[$GIP]\\ #,##0.00", 481: "[$GMD]\\ #,##0.00", 482: "[$GNF]\\ #,##0.00", 483: "[$GRD]\\ #,##0.00", 484: "[$GTQ]\\ #,##0.00", 485: "[$GYD]\\ #,##0.00", 486: "[$HKD]\\ #,##0.00", 487: "[$HNL]\\ #,##0.00", 488: "[$HRK]\\ #,##0.00", 489: "[$HTG]\\ #,##0.00", 490: "[$HUF]\\ #,##0.00", 491: "[$IDR]\\ #,##0.00", 492: "[$IEP]\\ #,##0.00", 493: "[$ILS]\\ #,##0.00", 494: "[$INR]\\ #,##0.00", 495: "[$IQD]\\ #,##0.00", 496: "[$IRR]\\ #,##0.00", 497: "[$ISK]\\ #,##0.00", 498: "[$ITL]\\ #,##0.00", 499: "[$JMD]\\ #,##0.00", 500: "[$JOD]\\ #,##0.00", 501: "[$JPY]\\ #,##0.00", 502: "[$KAF]\\ #,##0.00", 503: "[$KES]\\ #,##0.00", 504: "[$KGS]\\ #,##0.00", 505: "[$KHR]\\ #,##0.00", 506: "[$KMF]\\ #,##0.00", 507: "[$KPW]\\ #,##0.00", 508: "[$KRW]\\ #,##0.00", 509: "[$KWD]\\ #,##0.00", 510: "[$KYD]\\ #,##0.00", 511: "[$KZT]\\ #,##0.00", 512: "[$LAK]\\ #,##0.00", 513: "[$LBP]\\ #,##0.00", 514: "[$LKR]\\ #,##0.00", 515: "[$LRD]\\ #,##0.00", 516: "[$LSL]\\ #,##0.00", 517: "[$LTL]\\ #,##0.00", 518: "[$LUF]\\ #,##0.00", 519: "[$LVL]\\ #,##0.00", 520: "[$LYD]\\ #,##0.00", 521: "[$MAD]\\ #,##0.00", 522: "[$MDL]\\ #,##0.00", 523: "[$MGA]\\ #,##0.00", 524: "[$MGF]\\ #,##0.00", 525: "[$MKD]\\ #,##0.00", 526: "[$MMK]\\ #,##0.00", 527: "[$MNT]\\ #,##0.00", 528: "[$MOP]\\ #,##0.00", 529: "[$MRO]\\ #,##0.00", 530: "[$MTL]\\ #,##0.00", 531: "[$MUR]\\ #,##0.00", 532: "[$MVR]\\ #,##0.00", 533: "[$MWK]\\ #,##0.00", 534: "[$MXN]\\ #,##0.00", 535: "[$MXV]\\ #,##0.00", 536: "[$MYR]\\ #,##0.00", 537: "[$MZM]\\ #,##0.00", 538: "[$MZN]\\ #,##0.00", 539: "[$NAD]\\ #,##0.00", 540: "[$NGN]\\ #,##0.00", 541: "[$NIO]\\ #,##0.00", 542: "[$NLG]\\ #,##0.00", 543: "[$NOK]\\ #,##0.00", 544: "[$NPR]\\ #,##0.00", 545: "[$NTD]\\ #,##0.00", 546: "[$NZD]\\ #,##0.00", 547: "[$OMR]\\ #,##0.00", 548: "[$PAB]\\ #,##0.00", 549: "[$PEN]\\ #,##0.00", 550: "[$PGK]\\ #,##0.00", 551: "[$PHP]\\ #,##0.00", 552: "[$PKR]\\ #,##0.00", 553: "[$PLN]\\ #,##0.00", 554: "[$PTE]\\ #,##0.00", 555: "[$PYG]\\ #,##0.00", 556: "[$QAR]\\ #,##0.00", 557: "[$ROL]\\ #,##0.00", 558: "[$RON]\\ #,##0.00", 559: "[$RSD]\\ #,##0.00", 560: "[$RUB]\\ #,##0.00", 561: "[$RUR]\\ #,##0.00", 562: "[$RWF]\\ #,##0.00", 563: "[$SAR]\\ #,##0.00", 564: "[$SBD]\\ #,##0.00", 565: "[$SCR]\\ #,##0.00", 566: "[$SDD]\\ #,##0.00", 567: "[$SDG]\\ #,##0.00", 568: "[$SDP]\\ #,##0.00", 569: "[$SEK]\\ #,##0.00", 570: "[$SGD]\\ #,##0.00", 571: "[$SHP]\\ #,##0.00", 572: "[$SIT]\\ #,##0.00", 573: "[$SKK]\\ #,##0.00", 574: "[$SLL]\\ #,##0.00", 575: "[$SOS]\\ #,##0.00", 576: "[$SPL]\\ #,##0.00", 577: "[$SRD]\\ #,##0.00", 578: "[$SRG]\\ #,##0.00", 579: "[$STD]\\ #,##0.00", 580: "[$SVC]\\ #,##0.00", 581: "[$SYP]\\ #,##0.00", 582: "[$SZL]\\ #,##0.00", 583: "[$THB]\\ #,##0.00", 584: "[$TJR]\\ #,##0.00", 585: "[$TJS]\\ #,##0.00", 586: "[$TMM]\\ #,##0.00", 587: "[$TMT]\\ #,##0.00", 588: "[$TND]\\ #,##0.00", 589: "[$TOP]\\ #,##0.00", 590: "[$TRL]\\ #,##0.00", 591: "[$TRY]\\ #,##0.00", 592: "[$TTD]\\ #,##0.00", 593: "[$TWD]\\ #,##0.00", 594: "[$TZS]\\ #,##0.00", 595: "[$UAH]\\ #,##0.00", 596: "[$UGX]\\ #,##0.00", 597: "[$USD]\\ #,##0.00", 598: "[$USN]\\ #,##0.00", 599: "[$USS]\\ #,##0.00", 600: "[$UYI]\\ #,##0.00", 601: "[$UYU]\\ #,##0.00", 602: "[$UZS]\\ #,##0.00", 603: "[$VEB]\\ #,##0.00", 604: "[$VEF]\\ #,##0.00", 605: "[$VND]\\ #,##0.00", 606: "[$VUV]\\ #,##0.00", 607: "[$WST]\\ #,##0.00", 608: "[$XAF]\\ #,##0.00", 609: "[$XAG]\\ #,##0.00", 610: "[$XAU]\\ #,##0.00", 611: "[$XB5]\\ #,##0.00", 612: "[$XBA]\\ #,##0.00", 613: "[$XBB]\\ #,##0.00", 614: "[$XBC]\\ #,##0.00", 615: "[$XBD]\\ #,##0.00", 616: "[$XCD]\\ #,##0.00", 617: "[$XDR]\\ #,##0.00", 618: "[$XFO]\\ #,##0.00", 619: "[$XFU]\\ #,##0.00", 620: "[$XOF]\\ #,##0.00", 621: "[$XPD]\\ #,##0.00", 622: "[$XPF]\\ #,##0.00", 623: "[$XPT]\\ #,##0.00", 624: "[$XTS]\\ #,##0.00", 625: "[$XXX]\\ #,##0.00", 626: "[$YER]\\ #,##0.00", 627: "[$YUM]\\ #,##0.00", 628: "[$ZAR]\\ #,##0.00", 629: "[$ZMK]\\ #,##0.00", 630: "[$ZMW]\\ #,##0.00", 631: "[$ZWD]\\ #,##0.00", 632: "[$ZWL]\\ #,##0.00", 633: "[$ZWN]\\ #,##0.00", 634: "[$ZWR]\\ #,##0.00", } // builtInNumFmtFunc defined the format conversion functions map. Partial format // code doesn't support currently and will return original string. var builtInNumFmtFunc = map[int]func(v, format string, date1904 bool) string{ 0: format, 1: formatToInt, 2: formatToFloat, 3: formatToIntSeparator, 4: formatToFloat, 9: formatToC, 10: formatToD, 11: formatToE, 12: format, // Doesn't support currently 13: format, // Doesn't support currently 14: format, 15: format, 16: format, 17: format, 18: format, 19: format, 20: format, 21: format, 22: format, 37: formatToA, 38: formatToA, 39: formatToB, 40: formatToB, 41: format, // Doesn't support currently 42: format, // Doesn't support currently 43: format, // Doesn't support currently 44: format, // Doesn't support currently 45: format, 46: format, 47: format, 48: formatToE, 49: format, } // validType defined the list of valid validation types. var validType = map[string]string{ "cell": "cellIs", "date": "date", // Doesn't support currently "time": "time", // Doesn't support currently "average": "aboveAverage", "duplicate": "duplicateValues", "unique": "uniqueValues", "top": "top10", "bottom": "top10", "text": "text", // Doesn't support currently "time_period": "timePeriod", // Doesn't support currently "blanks": "containsBlanks", // Doesn't support currently "no_blanks": "notContainsBlanks", // Doesn't support currently "errors": "containsErrors", // Doesn't support currently "no_errors": "notContainsErrors", // Doesn't support currently "2_color_scale": "2_color_scale", "3_color_scale": "3_color_scale", "data_bar": "dataBar", "formula": "expression", "iconSet": "iconSet", } // criteriaType defined the list of valid criteria types. var criteriaType = map[string]string{ "between": "between", "not between": "notBetween", "equal to": "equal", "=": "equal", "==": "equal", "not equal to": "notEqual", "!=": "notEqual", "<>": "notEqual", "greater than": "greaterThan", ">": "greaterThan", "less than": "lessThan", "<": "lessThan", "greater than or equal to": "greaterThanOrEqual", ">=": "greaterThanOrEqual", "less than or equal to": "lessThanOrEqual", "<=": "lessThanOrEqual", "containing": "containsText", "not containing": "notContains", "begins with": "beginsWith", "ends with": "endsWith", "yesterday": "yesterday", "today": "today", "last 7 days": "last7Days", "last week": "lastWeek", "this week": "thisWeek", "continue week": "continueWeek", "last month": "lastMonth", "this month": "thisMonth", "continue month": "continueMonth", } // operatorType defined the list of valid operator types. var operatorType = map[string]string{ "lastMonth": "last month", "between": "between", "notEqual": "not equal to", "greaterThan": "greater than", "lessThanOrEqual": "less than or equal to", "today": "today", "equal": "equal to", "notContains": "not containing", "thisWeek": "this week", "endsWith": "ends with", "yesterday": "yesterday", "lessThan": "less than", "beginsWith": "begins with", "last7Days": "last 7 days", "thisMonth": "this month", "containsText": "containing", "lastWeek": "last week", "continueWeek": "continue week", "continueMonth": "continue month", "notBetween": "not between", "greaterThanOrEqual": "greater than or equal to", } // printCommaSep format number with thousands separator. func printCommaSep(text string) string { var ( target strings.Builder subStr = strings.Split(text, ".") length = len(subStr[0]) ) for i := 0; i < length; i++ { if i > 0 && (length-i)%3 == 0 { target.WriteString(",") } target.WriteString(string(text[i])) } if len(subStr) == 2 { target.WriteString(".") target.WriteString(subStr[1]) } return target.String() } // formatToInt provides a function to convert original string to integer // format as string type by given built-in number formats code and cell // string. func formatToInt(v, format string, date1904 bool) string { if strings.Contains(v, "_") { return v } f, err := strconv.ParseFloat(v, 64) if err != nil { return v } return strconv.FormatFloat(math.Round(f), 'f', -1, 64) } // formatToFloat provides a function to convert original string to float // format as string type by given built-in number formats code and cell // string. func formatToFloat(v, format string, date1904 bool) string { if strings.Contains(v, "_") { return v } f, err := strconv.ParseFloat(v, 64) if err != nil { return v } source := strconv.FormatFloat(f, 'f', -1, 64) if !strings.Contains(source, ".") { return source + ".00" } return fmt.Sprintf("%.2f", f) } // formatToIntSeparator provides a function to convert original string to // integer format as string type by given built-in number formats code and cell // string. func formatToIntSeparator(v, format string, date1904 bool) string { if strings.Contains(v, "_") { return v } f, err := strconv.ParseFloat(v, 64) if err != nil { return v } return printCommaSep(strconv.FormatFloat(math.Round(f), 'f', -1, 64)) } // formatToA provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. func formatToA(v, format string, date1904 bool) string { if strings.Contains(v, "_") { return v } f, err := strconv.ParseFloat(v, 64) if err != nil { return v } var target strings.Builder if f < 0 { target.WriteString("(") } target.WriteString(printCommaSep(strconv.FormatFloat(math.Abs(math.Round(f)), 'f', -1, 64))) if f < 0 { target.WriteString(")") } else { target.WriteString(" ") } return target.String() } // formatToB provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. func formatToB(v, format string, date1904 bool) string { if strings.Contains(v, "_") { return v } f, err := strconv.ParseFloat(v, 64) if err != nil { return v } var target strings.Builder if f < 0 { target.WriteString("(") } source := strconv.FormatFloat(math.Abs(f), 'f', -1, 64) var text string if !strings.Contains(source, ".") { text = printCommaSep(source + ".00") } else { text = printCommaSep(fmt.Sprintf("%.2f", math.Abs(f))) } target.WriteString(text) if f < 0 { target.WriteString(")") } else { target.WriteString(" ") } return target.String() } // formatToC provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. func formatToC(v, format string, date1904 bool) string { if strings.Contains(v, "_") { return v } f, err := strconv.ParseFloat(v, 64) if err != nil { return v } source := strconv.FormatFloat(f, 'f', -1, 64) if !strings.Contains(source, ".") { return source + "00%" } return fmt.Sprintf("%.f%%", f*100) } // formatToD provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. func formatToD(v, format string, date1904 bool) string { if strings.Contains(v, "_") { return v } f, err := strconv.ParseFloat(v, 64) if err != nil { return v } source := strconv.FormatFloat(f, 'f', -1, 64) if !strings.Contains(source, ".") { return source + "00.00%" } return fmt.Sprintf("%.2f%%", f*100) } // formatToE provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. func formatToE(v, format string, date1904 bool) string { if strings.Contains(v, "_") { return v } f, err := strconv.ParseFloat(v, 64) if err != nil { return v } return fmt.Sprintf("%.2E", f) } // stylesReader provides a function to get the pointer to the structure after // deserialization of xl/styles.xml. func (f *File) stylesReader() (*xlsxStyleSheet, error) { if f.Styles == nil { f.Styles = new(xlsxStyleSheet) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathStyles)))). Decode(f.Styles); err != nil && err != io.EOF { return f.Styles, err } } return f.Styles, nil } // styleSheetWriter provides a function to save xl/styles.xml after serialize // structure. func (f *File) styleSheetWriter() { if f.Styles != nil { output, _ := xml.Marshal(f.Styles) f.saveFileList(defaultXMLPathStyles, f.replaceNameSpaceBytes(defaultXMLPathStyles, output)) } } // themeWriter provides a function to save xl/theme/theme1.xml after serialize // structure. func (f *File) themeWriter() { if f.Theme != nil { output, _ := xml.Marshal(f.Theme) f.saveFileList(defaultXMLPathTheme, f.replaceNameSpaceBytes(defaultXMLPathTheme, output)) } } // sharedStringsWriter provides a function to save xl/sharedStrings.xml after // serialize structure. func (f *File) sharedStringsWriter() { if f.SharedStrings != nil { output, _ := xml.Marshal(f.SharedStrings) f.saveFileList(defaultXMLPathSharedStrings, f.replaceNameSpaceBytes(defaultXMLPathSharedStrings, output)) } } // parseFormatStyleSet provides a function to parse the format settings of the // cells and conditional formats. func parseFormatStyleSet(style *Style) (*Style, error) { var err error if style.Font != nil { if len(style.Font.Family) > MaxFontFamilyLength { return style, ErrFontLength } if style.Font.Size > MaxFontSize { return style, ErrFontSize } } if style.CustomNumFmt != nil && len(*style.CustomNumFmt) == 0 { err = ErrCustomNumFmt } return style, err } // NewStyle provides a function to create the style for cells by given style // options. This function is concurrency safe. Note that the 'Font.Color' field // uses an RGB color represented in 'RRGGBB' hexadecimal notation. // // The following table shows the border types used in 'Border.Type' supported by // excelize: // // Type | Description // --------------+------------------ // left | Left border // top | Top border // right | Right border // bottom | Bottom border // diagonalDown | Diagonal down border // diagonalUp | Diagonal up border // // The following table shows the border styles used in 'Border.Style' supported // by excelize index number: // // Index | Name | Weight | Style // -------+---------------+--------+------------- // 0 | None | 0 | // 1 | Continuous | 1 | ----------- // 2 | Continuous | 2 | ----------- // 3 | Dash | 1 | - - - - - - // 4 | Dot | 1 | . . . . . . // 5 | Continuous | 3 | ----------- // 6 | Double | 3 | =========== // 7 | Continuous | 0 | ----------- // 8 | Dash | 2 | - - - - - - // 9 | Dash Dot | 1 | - . - . - . // 10 | Dash Dot | 2 | - . - . - . // 11 | Dash Dot Dot | 1 | - . . - . . // 12 | Dash Dot Dot | 2 | - . . - . . // 13 | SlantDash Dot | 2 | / - . / - . // // The following table shows the border styles used in 'Border.Style' in the // order shown in the Excel dialog: // // Index | Style | Index | Style // -------+-------------+-------+------------- // 0 | None | 12 | - . . - . . // 7 | ----------- | 13 | / - . / - . // 4 | . . . . . . | 10 | - . - . - . // 11 | - . . - . . | 8 | - - - - - - // 9 | - . - . - . | 2 | ----------- // 3 | - - - - - - | 5 | ----------- // 1 | ----------- | 6 | =========== // // The following table shows the shading styles used in 'Fill.Shading' supported // by excelize index number: // // Index | Style | Index | Style // -------+-----------------+-------+----------------- // 0-2 | Horizontal | 9-11 | Diagonal down // 3-5 | Vertical | 12-15 | From corner // 6-8 | Diagonal Up | 16 | From center // // The following table shows the pattern styles used in 'Fill.Pattern' supported // by excelize index number: // // Index | Style | Index | Style // -------+-----------------+-------+----------------- // 0 | None | 10 | darkTrellis // 1 | solid | 11 | lightHorizontal // 2 | mediumGray | 12 | lightVertical // 3 | darkGray | 13 | lightDown // 4 | lightGray | 14 | lightUp // 5 | darkHorizontal | 15 | lightGrid // 6 | darkVertical | 16 | lightTrellis // 7 | darkDown | 17 | gray125 // 8 | darkUp | 18 | gray0625 // 9 | darkGrid | | // // The following table shows the type of cells' horizontal alignment used // in 'Alignment.Horizontal': // // Style // ------------------ // left // center // right // fill // justify // centerContinuous // distributed // // The following table shows the type of cells' vertical alignment used in // 'Alignment.Vertical': // // Style // ------------------ // top // center // justify // distributed // // The following table shows the type of font underline style used in // 'Font.Underline': // // Style // ------------------ // none // single // double // // Excel's built-in all languages formats are shown in the following table: // // Index | Format String // -------+---------------------------------------------------- // 0 | General // 1 | 0 // 2 | 0.00 // 3 | #,##0 // 4 | #,##0.00 // 5 | ($#,##0_);($#,##0) // 6 | ($#,##0_);[Red]($#,##0) // 7 | ($#,##0.00_);($#,##0.00) // 8 | ($#,##0.00_);[Red]($#,##0.00) // 9 | 0% // 10 | 0.00% // 11 | 0.00E+00 // 12 | # ?/? // 13 | # ??/?? // 14 | m/d/yy // 15 | d-mmm-yy // 16 | d-mmm // 17 | mmm-yy // 18 | h:mm AM/PM // 19 | h:mm:ss AM/PM // 20 | h:mm // 21 | h:mm:ss // 22 | m/d/yy h:mm // ... | ... // 37 | (#,##0_);(#,##0) // 38 | (#,##0_);[Red](#,##0) // 39 | (#,##0.00_);(#,##0.00) // 40 | (#,##0.00_);[Red](#,##0.00) // 41 | _(* #,##0_);_(* (#,##0);_(* "-"_);_(@_) // 42 | _($* #,##0_);_($* (#,##0);_($* "-"_);_(@_) // 43 | _(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_) // 44 | _($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_) // 45 | mm:ss // 46 | [h]:mm:ss // 47 | mm:ss.0 // 48 | ##0.0E+0 // 49 | @ // // Number format code in zh-tw language: // // Index | Symbol // -------+------------------------------------------- // 27 | [$-404]e/m/d // 28 | [$-404]e"年"m"月"d"日" // 29 | [$-404]e"年"m"月"d"日" // 30 | m/d/yy // 31 | yyyy"年"m"月"d"日" // 32 | hh"時"mm"分" // 33 | hh"時"mm"分"ss"秒" // 34 | 上午/下午 hh"時"mm"分" // 35 | 上午/下午 hh"時"mm"分"ss"秒" // 36 | [$-404]e/m/d // 50 | [$-404]e/m/d // 51 | [$-404]e"年"m"月"d"日" // 52 | 上午/下午 hh"時"mm"分" // 53 | 上午/下午 hh"時"mm"分"ss"秒" // 54 | [$-404]e"年"m"月"d"日" // 55 | 上午/下午 hh"時"mm"分" // 56 | 上午/下午 hh"時"mm"分"ss"秒" // 57 | [$-404]e/m/d // 58 | [$-404]e"年"m"月"d"日" // // Number format code in zh-cn language: // // Index | Symbol // -------+------------------------------------------- // 27 | yyyy"年"m"月" // 28 | m"月"d"日" // 29 | m"月"d"日" // 30 | m-d-yy // 31 | yyyy"年"m"月"d"日" // 32 | h"时"mm"分" // 33 | h"时"mm"分"ss"秒" // 34 | 上午/下午 h"时"mm"分" // 35 | 上午/下午 h"时"mm"分"ss"秒 // 36 | yyyy"年"m"月 // 50 | yyyy"年"m"月 // 51 | m"月"d"日 // 52 | yyyy"年"m"月 // 53 | m"月"d"日 // 54 | m"月"d"日 // 55 | 上午/下午 h"时"mm"分 // 56 | 上午/下午 h"时"mm"分"ss"秒 // 57 | yyyy"年"m"月 // 58 | m"月"d"日" // // Number format code with unicode values provided for language glyphs where // they occur in zh-tw language: // // Index | Symbol // -------+------------------------------------------- // 27 | [$-404]e/m/ // 28 | [$-404]e"5E74"m"6708"d"65E5 // 29 | [$-404]e"5E74"m"6708"d"65E5 // 30 | m/d/y // 31 | yyyy"5E74"m"6708"d"65E5 // 32 | hh"6642"mm"5206 // 33 | hh"6642"mm"5206"ss"79D2 // 34 | 4E0A5348/4E0B5348hh"6642"mm"5206 // 35 | 4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2 // 36 | [$-404]e/m/ // 50 | [$-404]e/m/ // 51 | [$-404]e"5E74"m"6708"d"65E5 // 52 | 4E0A5348/4E0B5348hh"6642"mm"5206 // 53 | 4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2 // 54 | [$-404]e"5E74"m"6708"d"65E5 // 55 | 4E0A5348/4E0B5348hh"6642"mm"5206 // 56 | 4E0A5348/4E0B5348hh"6642"mm"5206"ss"79D2 // 57 | [$-404]e/m/ // 58 | [$-404]e"5E74"m"6708"d"65E5" // // Number format code with unicode values provided for language glyphs where // they occur in zh-cn language: // // Index | Symbol // -------+------------------------------------------- // 27 | yyyy"5E74"m"6708 // 28 | m"6708"d"65E5 // 29 | m"6708"d"65E5 // 30 | m-d-y // 31 | yyyy"5E74"m"6708"d"65E5 // 32 | h"65F6"mm"5206 // 33 | h"65F6"mm"5206"ss"79D2 // 34 | 4E0A5348/4E0B5348h"65F6"mm"5206 // 35 | 4E0A5348/4E0B5348h"65F6"mm"5206"ss"79D2 // 36 | yyyy"5E74"m"6708 // 50 | yyyy"5E74"m"6708 // 51 | m"6708"d"65E5 // 52 | yyyy"5E74"m"6708 // 53 | m"6708"d"65E5 // 54 | m"6708"d"65E5 // 55 | 4E0A5348/4E0B5348h"65F6"mm"5206 // 56 | 4E0A5348/4E0B5348h"65F6"mm"5206"ss"79D2 // 57 | yyyy"5E74"m"6708 // 58 | m"6708"d"65E5" // // Number format code in ja-jp language: // // Index | Symbol // -------+------------------------------------------- // 27 | [$-411]ge.m.d // 28 | [$-411]ggge"年"m"月"d"日 // 29 | [$-411]ggge"年"m"月"d"日 // 30 | m/d/y // 31 | yyyy"年"m"月"d"日 // 32 | h"時"mm"分 // 33 | h"時"mm"分"ss"秒 // 34 | yyyy"年"m"月 // 35 | m"月"d"日 // 36 | [$-411]ge.m.d // 50 | [$-411]ge.m.d // 51 | [$-411]ggge"年"m"月"d"日 // 52 | yyyy"年"m"月 // 53 | m"月"d"日 // 54 | [$-411]ggge"年"m"月"d"日 // 55 | yyyy"年"m"月 // 56 | m"月"d"日 // 57 | [$-411]ge.m.d // 58 | [$-411]ggge"年"m"月"d"日" // // Number format code in ko-kr language: // // Index | Symbol // -------+------------------------------------------- // 27 | yyyy"年" mm"月" dd"日 // 28 | mm-d // 29 | mm-d // 30 | mm-dd-y // 31 | yyyy"년" mm"월" dd"일 // 32 | h"시" mm"분 // 33 | h"시" mm"분" ss"초 // 34 | yyyy-mm-d // 35 | yyyy-mm-d // 36 | yyyy"年" mm"月" dd"日 // 50 | yyyy"年" mm"月" dd"日 // 51 | mm-d // 52 | yyyy-mm-d // 53 | yyyy-mm-d // 54 | mm-d // 55 | yyyy-mm-d // 56 | yyyy-mm-d // 57 | yyyy"年" mm"月" dd"日 // 58 | mm-dd // // Number format code with unicode values provided for language glyphs where // they occur in ja-jp language: // // Index | Symbol // -------+------------------------------------------- // 27 | [$-411]ge.m.d // 28 | [$-411]ggge"5E74"m"6708"d"65E5 // 29 | [$-411]ggge"5E74"m"6708"d"65E5 // 30 | m/d/y // 31 | yyyy"5E74"m"6708"d"65E5 // 32 | h"6642"mm"5206 // 33 | h"6642"mm"5206"ss"79D2 // 34 | yyyy"5E74"m"6708 // 35 | m"6708"d"65E5 // 36 | [$-411]ge.m.d // 50 | [$-411]ge.m.d // 51 | [$-411]ggge"5E74"m"6708"d"65E5 // 52 | yyyy"5E74"m"6708 // 53 | m"6708"d"65E5 // 54 | [$-411]ggge"5E74"m"6708"d"65E5 // 55 | yyyy"5E74"m"6708 // 56 | m"6708"d"65E5 // 57 | [$-411]ge.m.d // 58 | [$-411]ggge"5E74"m"6708"d"65E5" // // Number format code with unicode values provided for language glyphs where // they occur in ko-kr language: // // Index | Symbol // -------+------------------------------------------- // 27 | yyyy"5E74" mm"6708" dd"65E5 // 28 | mm-d // 29 | mm-d // 30 | mm-dd-y // 31 | yyyy"B144" mm"C6D4" dd"C77C // 32 | h"C2DC" mm"BD84 // 33 | h"C2DC" mm"BD84" ss"CD08 // 34 | yyyy-mm-d // 35 | yyyy-mm-d // 36 | yyyy"5E74" mm"6708" dd"65E5 // 50 | yyyy"5E74" mm"6708" dd"65E5 // 51 | mm-d // 52 | yyyy-mm-d // 53 | yyyy-mm-d // 54 | mm-d // 55 | yyyy-mm-d // 56 | yyyy-mm-d // 57 | yyyy"5E74" mm"6708" dd"65E5 // 58 | mm-dd // // Number format code in th-th language: // // Index | Symbol // -------+------------------------------------------- // 59 | t // 60 | t0.0 // 61 | t#,## // 62 | t#,##0.0 // 67 | t0 // 68 | t0.00 // 69 | t# ?/ // 70 | t# ??/? // 71 | ว/ด/ปปป // 72 | ว-ดดด-ป // 73 | ว-ดด // 74 | ดดด-ป // 75 | ช:น // 76 | ช:นน:ท // 77 | ว/ด/ปปปป ช:น // 78 | นน:ท // 79 | [ช]:นน:ท // 80 | นน:ทท. // 81 | d/m/bb // // Number format code with unicode values provided for language glyphs where // they occur in th-th language: // // Index | Symbol // -------+------------------------------------------- // 59 | t // 60 | t0.0 // 61 | t#,## // 62 | t#,##0.0 // 67 | t0 // 68 | t0.00 // 69 | t# ?/ // 70 | t# ??/? // 71 | 0E27/0E14/0E1B0E1B0E1B0E1 // 72 | 0E27-0E140E140E14-0E1B0E1 // 73 | 0E27-0E140E140E1 // 74 | 0E140E140E14-0E1B0E1 // 75 | 0E0A:0E190E1 // 76 | 0E0A:0E190E19:0E170E1 // 77 | 0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E1 // 78 | 0E190E19:0E170E1 // 79 | [0E0A]:0E190E19:0E170E1 // 80 | 0E190E19:0E170E17. // 81 | d/m/bb // // Excelize built-in currency formats are shown in the following table, only // support these types in the following table (Index number is used only for // markup and is not used inside an Excel file and you can't get formatted value // by the function GetCellValue) currently: // // Index | Symbol // -------+--------------------------------------------------------------- // 164 | ¥ // 165 | $ English (United States) // 166 | $ Cherokee (United States) // 167 | $ Chinese (Singapore) // 168 | $ Chinese (Taiwan) // 169 | $ English (Australia) // 170 | $ English (Belize) // 171 | $ English (Canada) // 172 | $ English (Jamaica) // 173 | $ English (New Zealand) // 174 | $ English (Singapore) // 175 | $ English (Trinidad & Tobago) // 176 | $ English (U.S. Virgin Islands) // 177 | $ English (United States) // 178 | $ French (Canada) // 179 | $ Hawaiian (United States) // 180 | $ Malay (Brunei) // 181 | $ Quechua (Ecuador) // 182 | $ Spanish (Chile) // 183 | $ Spanish (Colombia) // 184 | $ Spanish (Ecuador) // 185 | $ Spanish (El Salvador) // 186 | $ Spanish (Mexico) // 187 | $ Spanish (Puerto Rico) // 188 | $ Spanish (United States) // 189 | $ Spanish (Uruguay) // 190 | £ English (United Kingdom) // 191 | £ Scottish Gaelic (United Kingdom) // 192 | £ Welsh (United Kindom) // 193 | ¥ Chinese (China) // 194 | ¥ Japanese (Japan) // 195 | ¥ Sichuan Yi (China) // 196 | ¥ Tibetan (China) // 197 | ¥ Uyghur (China) // 198 | ֏ Armenian (Armenia) // 199 | ؋ Pashto (Afghanistan) // 200 | ؋ Persian (Afghanistan) // 201 | ৳ Bengali (Bangladesh) // 202 | ៛ Khmer (Cambodia) // 203 | ₡ Spanish (Costa Rica) // 204 | ₦ Hausa (Nigeria) // 205 | ₦ Igbo (Nigeria) // 206 | ₩ Korean (South Korea) // 207 | ₪ Hebrew (Israel) // 208 | ₫ Vietnamese (Vietnam) // 209 | € Basque (Spain) // 210 | € Breton (France) // 211 | € Catalan (Spain) // 212 | € Corsican (France) // 213 | € Dutch (Belgium) // 214 | € Dutch (Netherlands) // 215 | € English (Ireland) // 216 | € Estonian (Estonia) // 217 | € Euro (€ 123) // 218 | € Euro (123 €) // 219 | € Finnish (Finland) // 220 | € French (Belgium) // 221 | € French (France) // 222 | € French (Luxembourg) // 223 | € French (Monaco) // 224 | € French (Réunion) // 225 | € Galician (Spain) // 226 | € German (Austria) // 227 | € German (German) // 228 | € German (Luxembourg) // 229 | € Greek (Greece) // 230 | € Inari Sami (Finland) // 231 | € Irish (Ireland) // 232 | € Italian (Italy) // 233 | € Latin (Italy) // 234 | € Latin, Serbian (Montenegro) // 235 | € Larvian (Latvia) // 236 | € Lithuanian (Lithuania) // 237 | € Lower Sorbian (Germany) // 238 | € Luxembourgish (Luxembourg) // 239 | € Maltese (Malta) // 240 | € Northern Sami (Finland) // 241 | € Occitan (France) // 242 | € Portuguese (Portugal) // 243 | € Serbian (Montenegro) // 244 | € Skolt Sami (Finland) // 245 | € Slovak (Slovakia) // 246 | € Slovenian (Slovenia) // 247 | € Spanish (Spain) // 248 | € Swedish (Finland) // 249 | € Swiss German (France) // 250 | € Upper Sorbian (Germany) // 251 | € Western Frisian (Netherlands) // 252 | ₭ Lao (Laos) // 253 | ₮ Mongolian (Mongolia) // 254 | ₮ Mongolian, Mongolian (Mongolia) // 255 | ₱ English (Philippines) // 256 | ₱ Filipino (Philippines) // 257 | ₴ Ukrainian (Ukraine) // 258 | ₸ Kazakh (Kazakhstan) // 259 | ₹ Arabic, Kashmiri (India) // 260 | ₹ English (India) // 261 | ₹ Gujarati (India) // 262 | ₹ Hindi (India) // 263 | ₹ Kannada (India) // 264 | ₹ Kashmiri (India) // 265 | ₹ Konkani (India) // 266 | ₹ Manipuri (India) // 267 | ₹ Marathi (India) // 268 | ₹ Nepali (India) // 269 | ₹ Oriya (India) // 270 | ₹ Punjabi (India) // 271 | ₹ Sanskrit (India) // 272 | ₹ Sindhi (India) // 273 | ₹ Tamil (India) // 274 | ₹ Urdu (India) // 275 | ₺ Turkish (Turkey) // 276 | ₼ Azerbaijani (Azerbaijan) // 277 | ₼ Cyrillic, Azerbaijani (Azerbaijan) // 278 | ₽ Russian (Russia) // 279 | ₽ Sakha (Russia) // 280 | ₾ Georgian (Georgia) // 281 | B/. Spanish (Panama) // 282 | Br Oromo (Ethiopia) // 283 | Br Somali (Ethiopia) // 284 | Br Tigrinya (Ethiopia) // 285 | Bs Quechua (Bolivia) // 286 | Bs Spanish (Bolivia) // 287 | BS. Spanish (Venezuela) // 288 | BWP Tswana (Botswana) // 289 | C$ Spanish (Nicaragua) // 290 | CA$ Latin, Inuktitut (Canada) // 291 | CA$ Mohawk (Canada) // 292 | CA$ Unified Canadian Aboriginal Syllabics, Inuktitut (Canada) // 293 | CFA French (Mali) // 294 | CFA French (Senegal) // 295 | CFA Fulah (Senegal) // 296 | CFA Wolof (Senegal) // 297 | CHF French (Switzerland) // 298 | CHF German (Liechtenstein) // 299 | CHF German (Switzerland) // 300 | CHF Italian (Switzerland) // 301 | CHF Romansh (Switzerland) // 302 | CLP Mapuche (Chile) // 303 | CN¥ Mongolian, Mongolian (China) // 304 | DZD Central Atlas Tamazight (Algeria) // 305 | FCFA French (Cameroon) // 306 | Ft Hungarian (Hungary) // 307 | G French (Haiti) // 308 | Gs. Spanish (Paraguay) // 309 | GTQ K'iche' (Guatemala) // 310 | HK$ Chinese (Hong Kong (China)) // 311 | HK$ English (Hong Kong (China)) // 312 | HRK Croatian (Croatia) // 313 | IDR English (Indonesia) // 314 | IQD Arbic, Central Kurdish (Iraq) // 315 | ISK Icelandic (Iceland) // 316 | K Burmese (Myanmar (Burma)) // 317 | Kč Czech (Czech Republic) // 318 | KM Bosnian (Bosnia & Herzegovina) // 319 | KM Croatian (Bosnia & Herzegovina) // 320 | KM Latin, Serbian (Bosnia & Herzegovina) // 321 | kr Faroese (Faroe Islands) // 322 | kr Northern Sami (Norway) // 323 | kr Northern Sami (Sweden) // 324 | kr Norwegian Bokmål (Norway) // 325 | kr Norwegian Nynorsk (Norway) // 326 | kr Swedish (Sweden) // 327 | kr. Danish (Denmark) // 328 | kr. Kalaallisut (Greenland) // 329 | Ksh Swahili (kenya) // 330 | L Romanian (Moldova) // 331 | L Russian (Moldova) // 332 | L Spanish (Honduras) // 333 | Lekë Albanian (Albania) // 334 | MAD Arabic, Central Atlas Tamazight (Morocco) // 335 | MAD French (Morocco) // 336 | MAD Tifinagh, Central Atlas Tamazight (Morocco) // 337 | MOP$ Chinese (Macau (China)) // 338 | MVR Divehi (Maldives) // 339 | Nfk Tigrinya (Eritrea) // 340 | NGN Bini (Nigeria) // 341 | NGN Fulah (Nigeria) // 342 | NGN Ibibio (Nigeria) // 343 | NGN Kanuri (Nigeria) // 344 | NOK Lule Sami (Norway) // 345 | NOK Southern Sami (Norway) // 346 | NZ$ Maori (New Zealand) // 347 | PKR Sindhi (Pakistan) // 348 | PYG Guarani (Paraguay) // 349 | Q Spanish (Guatemala) // 350 | R Afrikaans (South Africa) // 351 | R English (South Africa) // 352 | R Zulu (South Africa) // 353 | R$ Portuguese (Brazil) // 354 | RD$ Spanish (Dominican Republic) // 355 | RF Kinyarwanda (Rwanda) // 356 | RM English (Malaysia) // 357 | RM Malay (Malaysia) // 358 | RON Romanian (Romania) // 359 | Rp Indonesoan (Indonesia) // 360 | Rs Urdu (Pakistan) // 361 | Rs. Tamil (Sri Lanka) // 362 | RSD Latin, Serbian (Serbia) // 363 | RSD Serbian (Serbia) // 364 | RUB Bashkir (Russia) // 365 | RUB Tatar (Russia) // 366 | S/. Quechua (Peru) // 367 | S/. Spanish (Peru) // 368 | SEK Lule Sami (Sweden) // 369 | SEK Southern Sami (Sweden) // 370 | soʻm Latin, Uzbek (Uzbekistan) // 371 | soʻm Uzbek (Uzbekistan) // 372 | SYP Syriac (Syria) // 373 | THB Thai (Thailand) // 374 | TMT Turkmen (Turkmenistan) // 375 | US$ English (Zimbabwe) // 376 | ZAR Northern Sotho (South Africa) // 377 | ZAR Southern Sotho (South Africa) // 378 | ZAR Tsonga (South Africa) // 379 | ZAR Tswana (south Africa) // 380 | ZAR Venda (South Africa) // 381 | ZAR Xhosa (South Africa) // 382 | zł Polish (Poland) // 383 | ден Macedonian (Macedonia) // 384 | KM Cyrillic, Bosnian (Bosnia & Herzegovina) // 385 | KM Serbian (Bosnia & Herzegovina) // 386 | лв. Bulgarian (Bulgaria) // 387 | p. Belarusian (Belarus) // 388 | сом Kyrgyz (Kyrgyzstan) // 389 | сом Tajik (Tajikistan) // 390 | ج.م. Arabic (Egypt) // 391 | د.أ. Arabic (Jordan) // 392 | د.أ. Arabic (United Arab Emirates) // 393 | د.ب. Arabic (Bahrain) // 394 | د.ت. Arabic (Tunisia) // 395 | د.ج. Arabic (Algeria) // 396 | د.ع. Arabic (Iraq) // 397 | د.ك. Arabic (Kuwait) // 398 | د.ل. Arabic (Libya) // 399 | د.م. Arabic (Morocco) // 400 | ر Punjabi (Pakistan) // 401 | ر.س. Arabic (Saudi Arabia) // 402 | ر.ع. Arabic (Oman) // 403 | ر.ق. Arabic (Qatar) // 404 | ر.ي. Arabic (Yemen) // 405 | ریال Persian (Iran) // 406 | ل.س. Arabic (Syria) // 407 | ل.ل. Arabic (Lebanon) // 408 | ብር Amharic (Ethiopia) // 409 | रू Nepaol (Nepal) // 410 | රු. Sinhala (Sri Lanka) // 411 | ADP // 412 | AED // 413 | AFA // 414 | AFN // 415 | ALL // 416 | AMD // 417 | ANG // 418 | AOA // 419 | ARS // 420 | ATS // 421 | AUD // 422 | AWG // 423 | AZM // 424 | AZN // 425 | BAM // 426 | BBD // 427 | BDT // 428 | BEF // 429 | BGL // 430 | BGN // 431 | BHD // 432 | BIF // 433 | BMD // 434 | BND // 435 | BOB // 436 | BOV // 437 | BRL // 438 | BSD // 439 | BTN // 440 | BWP // 441 | BYR // 442 | BZD // 443 | CAD // 444 | CDF // 445 | CHE // 446 | CHF // 447 | CHW // 448 | CLF // 449 | CLP // 450 | CNY // 451 | COP // 452 | COU // 453 | CRC // 454 | CSD // 455 | CUC // 456 | CVE // 457 | CYP // 458 | CZK // 459 | DEM // 460 | DJF // 461 | DKK // 462 | DOP // 463 | DZD // 464 | ECS // 465 | ECV // 466 | EEK // 467 | EGP // 468 | ERN // 469 | ESP // 470 | ETB // 471 | EUR // 472 | FIM // 473 | FJD // 474 | FKP // 475 | FRF // 476 | GBP // 477 | GEL // 478 | GHC // 479 | GHS // 480 | GIP // 481 | GMD // 482 | GNF // 483 | GRD // 484 | GTQ // 485 | GYD // 486 | HKD // 487 | HNL // 488 | HRK // 489 | HTG // 490 | HUF // 491 | IDR // 492 | IEP // 493 | ILS // 494 | INR // 495 | IQD // 496 | IRR // 497 | ISK // 498 | ITL // 499 | JMD // 500 | JOD // 501 | JPY // 502 | KAF // 503 | KES // 504 | KGS // 505 | KHR // 506 | KMF // 507 | KPW // 508 | KRW // 509 | KWD // 510 | KYD // 511 | KZT // 512 | LAK // 513 | LBP // 514 | LKR // 515 | LRD // 516 | LSL // 517 | LTL // 518 | LUF // 519 | LVL // 520 | LYD // 521 | MAD // 522 | MDL // 523 | MGA // 524 | MGF // 525 | MKD // 526 | MMK // 527 | MNT // 528 | MOP // 529 | MRO // 530 | MTL // 531 | MUR // 532 | MVR // 533 | MWK // 534 | MXN // 535 | MXV // 536 | MYR // 537 | MZM // 538 | MZN // 539 | NAD // 540 | NGN // 541 | NIO // 542 | NLG // 543 | NOK // 544 | NPR // 545 | NTD // 546 | NZD // 547 | OMR // 548 | PAB // 549 | PEN // 550 | PGK // 551 | PHP // 552 | PKR // 553 | PLN // 554 | PTE // 555 | PYG // 556 | QAR // 557 | ROL // 558 | RON // 559 | RSD // 560 | RUB // 561 | RUR // 562 | RWF // 563 | SAR // 564 | SBD // 565 | SCR // 566 | SDD // 567 | SDG // 568 | SDP // 569 | SEK // 570 | SGD // 571 | SHP // 572 | SIT // 573 | SKK // 574 | SLL // 575 | SOS // 576 | SPL // 577 | SRD // 578 | SRG // 579 | STD // 580 | SVC // 581 | SYP // 582 | SZL // 583 | THB // 584 | TJR // 585 | TJS // 586 | TMM // 587 | TMT // 588 | TND // 589 | TOP // 590 | TRL // 591 | TRY // 592 | TTD // 593 | TWD // 594 | TZS // 595 | UAH // 596 | UGX // 597 | USD // 598 | USN // 599 | USS // 600 | UYI // 601 | UYU // 602 | UZS // 603 | VEB // 604 | VEF // 605 | VND // 606 | VUV // 607 | WST // 608 | XAF // 609 | XAG // 610 | XAU // 611 | XB5 // 612 | XBA // 613 | XBB // 614 | XBC // 615 | XBD // 616 | XCD // 617 | XDR // 618 | XFO // 619 | XFU // 620 | XOF // 621 | XPD // 622 | XPF // 623 | XPT // 624 | XTS // 625 | XXX // 626 | YER // 627 | YUM // 628 | ZAR // 629 | ZMK // 630 | ZMW // 631 | ZWD // 632 | ZWL // 633 | ZWN // 634 | ZWR // // Excelize support set custom number format for cell. For example, set number // as date type in Uruguay (Spanish) format for Sheet1!A6: // // f := excelize.NewFile() // defer func() { // if err := f.Close(); err != nil { // fmt.Println(err) // } // }() // if err := f.SetCellValue("Sheet1", "A6", 42920.5); err != nil { // fmt.Println(err) // return // } // exp := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@" // style, err := f.NewStyle(&excelize.Style{CustomNumFmt: &exp}) // if err != nil { // fmt.Println(err) // return // } // err = f.SetCellStyle("Sheet1", "A6", "A6", style) // // Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017 func (f *File) NewStyle(style *Style) (int, error) { var ( fs *Style font *xlsxFont err error cellXfsID, fontID, borderID, fillID int ) if style == nil { return cellXfsID, err } fs, err = parseFormatStyleSet(style) if err != nil { return cellXfsID, err } if fs.DecimalPlaces == 0 { fs.DecimalPlaces = 2 } s, err := f.stylesReader() if err != nil { return cellXfsID, err } s.Lock() defer s.Unlock() // check given style already exist. if cellXfsID, err = f.getStyleID(s, fs); err != nil || cellXfsID != -1 { return cellXfsID, err } numFmtID := newNumFmt(s, fs) if fs.Font != nil { fontID, _ = f.getFontID(s, fs) if fontID == -1 { s.Fonts.Count++ font, _ = f.newFont(fs) s.Fonts.Font = append(s.Fonts.Font, font) fontID = s.Fonts.Count - 1 } } borderID = getBorderID(s, fs) if borderID == -1 { if len(fs.Border) == 0 { borderID = 0 } else { s.Borders.Count++ s.Borders.Border = append(s.Borders.Border, newBorders(fs)) borderID = s.Borders.Count - 1 } } if fillID = getFillID(s, fs); fillID == -1 { if fill := newFills(fs, true); fill != nil { s.Fills.Count++ s.Fills.Fill = append(s.Fills.Fill, fill) fillID = s.Fills.Count - 1 } else { fillID = 0 } } applyAlignment, alignment := fs.Alignment != nil, newAlignment(fs) applyProtection, protection := fs.Protection != nil, newProtection(fs) return setCellXfs(s, fontID, numFmtID, fillID, borderID, applyAlignment, applyProtection, alignment, protection) } var getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{ "numFmt": func(numFmtID int, xf xlsxXf, style *Style) bool { if style.CustomNumFmt == nil && numFmtID == -1 { return xf.NumFmtID != nil && *xf.NumFmtID == 0 } if style.NegRed || style.Lang != "" || style.DecimalPlaces != 2 { return false } return xf.NumFmtID != nil && *xf.NumFmtID == numFmtID }, "font": func(fontID int, xf xlsxXf, style *Style) bool { if style.Font == nil { return (xf.FontID == nil || *xf.FontID == 0) && (xf.ApplyFont == nil || !*xf.ApplyFont) } return xf.FontID != nil && *xf.FontID == fontID && xf.ApplyFont != nil && *xf.ApplyFont }, "fill": func(fillID int, xf xlsxXf, style *Style) bool { if style.Fill.Type == "" { return (xf.FillID == nil || *xf.FillID == 0) && (xf.ApplyFill == nil || !*xf.ApplyFill) } return xf.FillID != nil && *xf.FillID == fillID && xf.ApplyFill != nil && *xf.ApplyFill }, "border": func(borderID int, xf xlsxXf, style *Style) bool { if len(style.Border) == 0 { return (xf.BorderID == nil || *xf.BorderID == 0) && (xf.ApplyBorder == nil || !*xf.ApplyBorder) } return xf.BorderID != nil && *xf.BorderID == borderID && xf.ApplyBorder != nil && *xf.ApplyBorder }, "alignment": func(ID int, xf xlsxXf, style *Style) bool { if style.Alignment == nil { return xf.ApplyAlignment == nil || !*xf.ApplyAlignment } return reflect.DeepEqual(xf.Alignment, newAlignment(style)) }, "protection": func(ID int, xf xlsxXf, style *Style) bool { if style.Protection == nil { return xf.ApplyProtection == nil || !*xf.ApplyProtection } return reflect.DeepEqual(xf.Protection, newProtection(style)) && xf.ApplyProtection != nil && *xf.ApplyProtection }, } // getStyleID provides a function to get styleID by given style. If given // style does not exist, will return -1. func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) { var ( err error fontID int styleID = -1 ) if ss.CellXfs == nil { return styleID, err } numFmtID, borderID, fillID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style) if fontID, err = f.getFontID(ss, style); err != nil { return styleID, err } if style.CustomNumFmt != nil { numFmtID = getCustomNumFmtID(ss, style) } for xfID, xf := range ss.CellXfs.Xf { if getXfIDFuncs["numFmt"](numFmtID, xf, style) && getXfIDFuncs["font"](fontID, xf, style) && getXfIDFuncs["fill"](fillID, xf, style) && getXfIDFuncs["border"](borderID, xf, style) && getXfIDFuncs["alignment"](0, xf, style) && getXfIDFuncs["protection"](0, xf, style) { styleID = xfID return styleID, err } } return styleID, err } // NewConditionalStyle provides a function to create style for conditional // format by given style format. The parameters are the same with the NewStyle // function. func (f *File) NewConditionalStyle(style *Style) (int, error) { s, err := f.stylesReader() if err != nil { return 0, err } fs, err := parseFormatStyleSet(style) if err != nil { return 0, err } dxf := dxf{ Fill: newFills(fs, false), } if fs.Alignment != nil { dxf.Alignment = newAlignment(fs) } if len(fs.Border) > 0 { dxf.Border = newBorders(fs) } if fs.Font != nil { dxf.Font, _ = f.newFont(fs) } dxfStr, _ := xml.Marshal(dxf) if s.Dxfs == nil { s.Dxfs = &xlsxDxfs{} } s.Dxfs.Count++ s.Dxfs.Dxfs = append(s.Dxfs.Dxfs, &xlsxDxf{ Dxf: string(dxfStr[5 : len(dxfStr)-6]), }) return s.Dxfs.Count - 1, nil } // GetDefaultFont provides the default font name currently set in the // workbook. The spreadsheet generated by excelize default font is Calibri. func (f *File) GetDefaultFont() (string, error) { font, err := f.readDefaultFont() if err != nil { return "", err } return *font.Name.Val, err } // SetDefaultFont changes the default font in the workbook. func (f *File) SetDefaultFont(fontName string) error { font, err := f.readDefaultFont() if err != nil { return err } font.Name.Val = stringPtr(fontName) s, _ := f.stylesReader() s.Fonts.Font[0] = font custom := true s.CellStyles.CellStyle[0].CustomBuiltIn = &custom return err } // readDefaultFont provides an un-marshalled font value. func (f *File) readDefaultFont() (*xlsxFont, error) { s, err := f.stylesReader() if err != nil { return nil, err } return s.Fonts.Font[0], err } // getFontID provides a function to get font ID. // If given font does not exist, will return -1. func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) (int, error) { var err error fontID := -1 if styleSheet.Fonts == nil || style.Font == nil { return fontID, err } for idx, fnt := range styleSheet.Fonts.Font { font, err := f.newFont(style) if err != nil { return fontID, err } if reflect.DeepEqual(*fnt, *font) { fontID = idx return fontID, err } } return fontID, err } // newFontColor set font color by given styles. func newFontColor(font *Font) *xlsxColor { var fontColor *xlsxColor prepareFontColor := func() { if fontColor != nil { return } fontColor = &xlsxColor{} } if font.Color != "" { prepareFontColor() fontColor.RGB = getPaletteColor(font.Color) } if font.ColorIndexed >= 0 && font.ColorIndexed <= len(IndexedColorMapping)+1 { prepareFontColor() fontColor.Indexed = font.ColorIndexed } if font.ColorTheme != nil { prepareFontColor() fontColor.Theme = font.ColorTheme } if font.ColorTint != 0 { prepareFontColor() fontColor.Tint = font.ColorTint } return fontColor } // newFont provides a function to add font style by given cell format // settings. func (f *File) newFont(style *Style) (*xlsxFont, error) { var err error if style.Font.Size < MinFontSize { style.Font.Size = 11 } fnt := xlsxFont{ Sz: &attrValFloat{Val: float64Ptr(style.Font.Size)}, Name: &attrValString{Val: stringPtr(style.Font.Family)}, Family: &attrValInt{Val: intPtr(2)}, } fnt.Color = newFontColor(style.Font) if style.Font.Bold { fnt.B = &attrValBool{Val: &style.Font.Bold} } if style.Font.Italic { fnt.I = &attrValBool{Val: &style.Font.Italic} } if *fnt.Name.Val == "" { if *fnt.Name.Val, err = f.GetDefaultFont(); err != nil { return &fnt, err } } if style.Font.Strike { fnt.Strike = &attrValBool{Val: &style.Font.Strike} } if idx := inStrSlice(supportedUnderlineTypes, style.Font.Underline, true); idx != -1 { fnt.U = &attrValString{Val: stringPtr(supportedUnderlineTypes[idx])} } return &fnt, err } // getNumFmtID provides a function to get number format code ID. // If given number format code does not exist, will return -1. func getNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (numFmtID int) { numFmtID = -1 if _, ok := builtInNumFmt[style.NumFmt]; ok { return style.NumFmt } for lang, numFmt := range langNumFmt { if _, ok := numFmt[style.NumFmt]; ok && lang == style.Lang { numFmtID = style.NumFmt return } } if fmtCode, ok := currencyNumFmt[style.NumFmt]; ok { numFmtID = style.NumFmt if styleSheet.NumFmts != nil { for _, numFmt := range styleSheet.NumFmts.NumFmt { if numFmt.FormatCode == fmtCode { numFmtID = numFmt.NumFmtID return } } } } return } // newNumFmt provides a function to check if number format code in the range // of built-in values. func newNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { dp := "0." numFmtID := 164 // Default custom number format code from 164. if style.DecimalPlaces < 0 || style.DecimalPlaces > 30 { style.DecimalPlaces = 2 } for i := 0; i < style.DecimalPlaces; i++ { dp += "0" } if style.CustomNumFmt != nil { if customNumFmtID := getCustomNumFmtID(styleSheet, style); customNumFmtID != -1 { return customNumFmtID } return setCustomNumFmt(styleSheet, style) } _, ok := builtInNumFmt[style.NumFmt] if !ok { fc, currency := currencyNumFmt[style.NumFmt] if !currency { return setLangNumFmt(styleSheet, style) } fc = strings.ReplaceAll(fc, "0.00", dp) if style.NegRed { fc = fc + ";[Red]" + fc } if styleSheet.NumFmts != nil { numFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 nf := xlsxNumFmt{ FormatCode: fc, NumFmtID: numFmtID, } styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) styleSheet.NumFmts.Count++ } else { nf := xlsxNumFmt{ FormatCode: fc, NumFmtID: numFmtID, } numFmts := xlsxNumFmts{ NumFmt: []*xlsxNumFmt{&nf}, Count: 1, } styleSheet.NumFmts = &numFmts } return numFmtID } return style.NumFmt } // setCustomNumFmt provides a function to set custom number format code. func setCustomNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { nf := xlsxNumFmt{FormatCode: *style.CustomNumFmt} if styleSheet.NumFmts != nil { nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) styleSheet.NumFmts.Count++ } else { nf.NumFmtID = 164 numFmts := xlsxNumFmts{ NumFmt: []*xlsxNumFmt{&nf}, Count: 1, } styleSheet.NumFmts = &numFmts } return nf.NumFmtID } // getCustomNumFmtID provides a function to get custom number format code ID. // If given custom number format code does not exist, will return -1. func getCustomNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (customNumFmtID int) { customNumFmtID = -1 if styleSheet.NumFmts == nil { return } for _, numFmt := range styleSheet.NumFmts.NumFmt { if style.CustomNumFmt != nil && numFmt.FormatCode == *style.CustomNumFmt { customNumFmtID = numFmt.NumFmtID return } } return } // setLangNumFmt provides a function to set number format code with language. func setLangNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { numFmts, ok := langNumFmt[style.Lang] if !ok { return 0 } var fc string fc, ok = numFmts[style.NumFmt] if !ok { return 0 } nf := xlsxNumFmt{FormatCode: fc} if styleSheet.NumFmts != nil { nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) styleSheet.NumFmts.Count++ } else { nf.NumFmtID = style.NumFmt numFmts := xlsxNumFmts{ NumFmt: []*xlsxNumFmt{&nf}, Count: 1, } styleSheet.NumFmts = &numFmts } return nf.NumFmtID } // getFillID provides a function to get fill ID. If given fill is not // exist, will return -1. func getFillID(styleSheet *xlsxStyleSheet, style *Style) (fillID int) { fillID = -1 if styleSheet.Fills == nil || style.Fill.Type == "" { return } fills := newFills(style, true) if fills == nil { return } for idx, fill := range styleSheet.Fills.Fill { if reflect.DeepEqual(fill, fills) { fillID = idx return } } return } // newFills provides a function to add fill elements in the styles.xml by // given cell format settings. func newFills(style *Style, fg bool) *xlsxFill { patterns := []string{ "none", "solid", "mediumGray", "darkGray", "lightGray", "darkHorizontal", "darkVertical", "darkDown", "darkUp", "darkGrid", "darkTrellis", "lightHorizontal", "lightVertical", "lightDown", "lightUp", "lightGrid", "lightTrellis", "gray125", "gray0625", } variants := []xlsxGradientFill{ {Degree: 90, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 270, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 90, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 180, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}}, {Degree: 45, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 255, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 45, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}}, {Degree: 135, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 315, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}}, {Degree: 135, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path"}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Left: 1, Right: 1}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Top: 1}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Left: 1, Right: 1, Top: 1}, {Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 0.5, Left: 0.5, Right: 0.5, Top: 0.5}, } var fill xlsxFill switch style.Fill.Type { case "gradient": if len(style.Fill.Color) != 2 || style.Fill.Shading < 0 || style.Fill.Shading > 16 { break } gradient := variants[style.Fill.Shading] gradient.Stop[0].Color.RGB = getPaletteColor(style.Fill.Color[0]) gradient.Stop[1].Color.RGB = getPaletteColor(style.Fill.Color[1]) if len(gradient.Stop) == 3 { gradient.Stop[2].Color.RGB = getPaletteColor(style.Fill.Color[0]) } fill.GradientFill = &gradient case "pattern": if style.Fill.Pattern > 18 || style.Fill.Pattern < 0 { break } if len(style.Fill.Color) < 1 { break } var pattern xlsxPatternFill pattern.PatternType = patterns[style.Fill.Pattern] if fg { if pattern.FgColor == nil { pattern.FgColor = new(xlsxColor) } pattern.FgColor.RGB = getPaletteColor(style.Fill.Color[0]) } else { if pattern.BgColor == nil { pattern.BgColor = new(xlsxColor) } pattern.BgColor.RGB = getPaletteColor(style.Fill.Color[0]) } fill.PatternFill = &pattern default: return nil } return &fill } // newAlignment provides a function to formatting information pertaining to // text alignment in cells. There are a variety of choices for how text is // aligned both horizontally and vertically, as well as indentation settings, // and so on. func newAlignment(style *Style) *xlsxAlignment { var alignment xlsxAlignment if style.Alignment != nil { alignment.Horizontal = style.Alignment.Horizontal alignment.Indent = style.Alignment.Indent alignment.JustifyLastLine = style.Alignment.JustifyLastLine alignment.ReadingOrder = style.Alignment.ReadingOrder alignment.RelativeIndent = style.Alignment.RelativeIndent alignment.ShrinkToFit = style.Alignment.ShrinkToFit alignment.TextRotation = style.Alignment.TextRotation alignment.Vertical = style.Alignment.Vertical alignment.WrapText = style.Alignment.WrapText } return &alignment } // newProtection provides a function to set protection properties associated // with the cell. func newProtection(style *Style) *xlsxProtection { var protection xlsxProtection if style.Protection != nil { protection.Hidden = &style.Protection.Hidden protection.Locked = &style.Protection.Locked } return &protection } // getBorderID provides a function to get border ID. If given border is not // exist, will return -1. func getBorderID(styleSheet *xlsxStyleSheet, style *Style) (borderID int) { borderID = -1 if styleSheet.Borders == nil || len(style.Border) == 0 { return } for idx, border := range styleSheet.Borders.Border { if reflect.DeepEqual(*border, *newBorders(style)) { borderID = idx return } } return } // newBorders provides a function to add border elements in the styles.xml by // given borders format settings. func newBorders(style *Style) *xlsxBorder { styles := []string{ "none", "thin", "medium", "dashed", "dotted", "thick", "double", "hair", "mediumDashed", "dashDot", "mediumDashDot", "dashDotDot", "mediumDashDotDot", "slantDashDot", } var border xlsxBorder for _, v := range style.Border { if 0 <= v.Style && v.Style < 14 { var color xlsxColor color.RGB = getPaletteColor(v.Color) switch v.Type { case "left": border.Left.Style = styles[v.Style] border.Left.Color = &color case "right": border.Right.Style = styles[v.Style] border.Right.Color = &color case "top": border.Top.Style = styles[v.Style] border.Top.Color = &color case "bottom": border.Bottom.Style = styles[v.Style] border.Bottom.Color = &color case "diagonalUp": border.Diagonal.Style = styles[v.Style] border.Diagonal.Color = &color border.DiagonalUp = true case "diagonalDown": border.Diagonal.Style = styles[v.Style] border.Diagonal.Color = &color border.DiagonalDown = true } } } return &border } // setCellXfs provides a function to set describes all of the formatting for a // cell. func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) (int, error) { var xf xlsxXf xf.FontID = intPtr(fontID) if fontID != 0 { xf.ApplyFont = boolPtr(true) } xf.NumFmtID = intPtr(numFmtID) if numFmtID != 0 { xf.ApplyNumberFormat = boolPtr(true) } xf.FillID = intPtr(fillID) if fillID != 0 { xf.ApplyFill = boolPtr(true) } xf.BorderID = intPtr(borderID) if borderID != 0 { xf.ApplyBorder = boolPtr(true) } if len(style.CellXfs.Xf) == MaxCellStyles { return 0, ErrCellStyles } style.CellXfs.Count = len(style.CellXfs.Xf) + 1 xf.Alignment = alignment if alignment != nil { xf.ApplyAlignment = boolPtr(applyAlignment) } if applyProtection { xf.ApplyProtection = boolPtr(applyProtection) xf.Protection = protection } xfID := 0 xf.XfID = &xfID style.CellXfs.Xf = append(style.CellXfs.Xf, xf) return style.CellXfs.Count - 1, nil } // GetCellStyle provides a function to get cell style index by given worksheet // name and cell reference. func (f *File) GetCellStyle(sheet, cell string) (int, error) { ws, err := f.workSheetReader(sheet) if err != nil { return 0, err } col, row, err := CellNameToCoordinates(cell) if err != nil { return 0, err } prepareSheetXML(ws, col, row) ws.Lock() defer ws.Unlock() return f.prepareCellStyle(ws, col, row, ws.SheetData.Row[row-1].C[col-1].S), err } // SetCellStyle provides a function to add style attribute for cells by given // worksheet name, range reference and style ID. This function is concurrency // safe. Note that diagonalDown and diagonalUp type border should be use same // color in the same range. SetCellStyle will overwrite the existing // styles for the cell, it won't append or merge style with existing styles. // // For example create a borders of cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Border: []excelize.Border{ // {Type: "left", Color: "0000FF", Style: 3}, // {Type: "top", Color: "00FF00", Style: 4}, // {Type: "bottom", Color: "FFFF00", Style: 5}, // {Type: "right", Color: "FF0000", Style: 6}, // {Type: "diagonalDown", Color: "A020F0", Style: 7}, // {Type: "diagonalUp", Color: "A020F0", Style: 8}, // }, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Set gradient fill with vertical variants shading styles for cell H9 on // Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Fill: excelize.Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 1}, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Set solid style pattern fill for cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Fill: excelize.Fill{Type: "pattern", Color: []string{"#E0EBF5"}, Pattern: 1}, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Set alignment style for cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Alignment: &excelize.Alignment{ // Horizontal: "center", // Indent: 1, // JustifyLastLine: true, // ReadingOrder: 0, // RelativeIndent: 1, // ShrinkToFit: true, // TextRotation: 45, // Vertical: "", // WrapText: true, // }, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Dates and times in Excel are represented by real numbers, for example "Apr 7 // 2017 12:00 PM" is represented by the number 42920.5. Set date and time format // for cell H9 on Sheet1: // // f.SetCellValue("Sheet1", "H9", 42920.5) // style, err := f.NewStyle(&excelize.Style{NumFmt: 22}) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Set font style for cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Font: &excelize.Font{ // Bold: true, // Italic: true, // Family: "Times New Roman", // Size: 36, // Color: "#777777", // }, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) // // Hide and lock for cell H9 on Sheet1: // // style, err := f.NewStyle(&excelize.Style{ // Protection: &excelize.Protection{ // Hidden: true, // Locked: true, // }, // }) // if err != nil { // fmt.Println(err) // } // err = f.SetCellStyle("Sheet1", "H9", "H9", style) func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { hCol, hRow, err := CellNameToCoordinates(hCell) if err != nil { return err } vCol, vRow, err := CellNameToCoordinates(vCell) if err != nil { return err } // Normalize the range, such correct C1:B3 to B1:C3. if vCol < hCol { vCol, hCol = hCol, vCol } if vRow < hRow { vRow, hRow = hRow, vRow } hColIdx := hCol - 1 hRowIdx := hRow - 1 vColIdx := vCol - 1 vRowIdx := vRow - 1 ws, err := f.workSheetReader(sheet) if err != nil { return err } prepareSheetXML(ws, vCol, vRow) makeContiguousColumns(ws, hRow, vRow, vCol) ws.Lock() defer ws.Unlock() s, err := f.stylesReader() if err != nil { return err } s.Lock() defer s.Unlock() if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { return newInvalidStyleID(styleID) } for r := hRowIdx; r <= vRowIdx; r++ { for k := hColIdx; k <= vColIdx; k++ { ws.SheetData.Row[r].C[k].S = styleID } } return err } // SetConditionalFormat provides a function to create conditional formatting // rule for cell value. Conditional formatting is a feature of Excel which // allows you to apply a format to a cell or a range of cells based on certain // criteria. // // The type option is a required parameter and it has no default value. // Allowable type values and their associated parameters are: // // Type | Parameters // ---------------+------------------------------------ // cell | Criteria // | Value // | Minimum // | Maximum // date | Criteria // | Value // | Minimum // | Maximum // time_period | Criteria // text | Criteria // | Value // average | Criteria // duplicate | (none) // unique | (none) // top | Criteria // | Value // bottom | Criteria // | Value // blanks | (none) // no_blanks | (none) // errors | (none) // no_errors | (none) // 2_color_scale | MinType // | MaxType // | MinValue // | MaxValue // | MinColor // | MaxColor // 3_color_scale | MinType // | MidType // | MaxType // | MinValue // | MidValue // | MaxValue // | MinColor // | MidColor // | MaxColor // data_bar | MinType // | MaxType // | MinValue // | MaxValue // | BarBorderColor // | BarColor // | BarDirection // | BarOnly // | BarSolid // iconSet | IconStyle // | ReverseIcons // | IconsOnly // formula | Criteria // // The 'Criteria' parameter is used to set the criteria by which the cell data // will be evaluated. It has no default value. The most common criteria as // applied to {"type":"cell"} are: // // between | // not between | // equal to | == // not equal to | != // greater than | > // less than | < // greater than or equal to | >= // less than or equal to | <= // // You can either use Excel's textual description strings, in the first column // above, or the more common symbolic alternatives. // // Additional criteria which are specific to other conditional format types are // shown in the relevant sections below. // // value: The value is generally used along with the criteria parameter to set // the rule by which the cell data will be evaluated: // // err := f.SetConditionalFormat("Sheet1", "D1:D10", // []excelize.ConditionalFormatOptions{ // { // Type: "cell", // Criteria: ">", // Format: format, // Value: "6", // }, // }, // ) // // The value property can also be an cell reference: // // err := f.SetConditionalFormat("Sheet1", "D1:D10", // []excelize.ConditionalFormatOptions{ // { // Type: "cell", // Criteria: ">", // Format: format, // Value: "$C$1", // }, // }, // ) // // type: format - The format parameter is used to specify the format that will // be applied to the cell when the conditional formatting criterion is met. The // format is created using the NewConditionalStyle function in the same way as // cell formats: // // format, err := f.NewConditionalStyle( // &excelize.Style{ // Font: &excelize.Font{Color: "#9A0511"}, // Fill: excelize.Fill{ // Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1, // }, // }, // ) // if err != nil { // fmt.Println(err) // } // err = f.SetConditionalFormat("Sheet1", "D1:D10", // []excelize.ConditionalFormatOptions{ // {Type: "cell", Criteria: ">", Format: format, Value: "6"}, // }, // ) // // Note: In Excel, a conditional format is superimposed over the existing cell // format and not all cell format properties can be modified. Properties that // cannot be modified in a conditional format are font name, font size, // superscript and subscript, diagonal borders, all alignment properties and all // protection properties. // // Excel specifies some default formats to be used with conditional formatting. // These can be replicated using the following excelize formats: // // // Rose format for bad conditional. // format1, err := f.NewConditionalStyle( // &excelize.Style{ // Font: &excelize.Font{Color: "#9A0511"}, // Fill: excelize.Fill{ // Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1, // }, // }, // ) // // // Light yellow format for neutral conditional. // format2, err := f.NewConditionalStyle( // &excelize.Style{ // Font: &excelize.Font{Color: "#9B5713"}, // Fill: excelize.Fill{ // Type: "pattern", Color: []string{"#FEEAA0"}, Pattern: 1, // }, // }, // ) // // // Light green format for good conditional. // format3, err := f.NewConditionalStyle( // &excelize.Style{ // Font: &excelize.Font{Color: "#09600B"}, // Fill: excelize.Fill{ // Type: "pattern", Color: []string{"#C7EECF"}, Pattern: 1, // }, // }, // ) // // type: Minimum - The 'Minimum' parameter is used to set the lower limiting // value when the criteria is either "between" or "not between". // // // Highlight cells rules: between... // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "cell", // Criteria: "between", // Format: format, // Minimum: "6", // Maximum: "8", // }, // }, // ) // // type: Maximum - The 'Maximum' parameter is used to set the upper limiting // value when the criteria is either "between" or "not between". See the // previous example. // // type: average - The average type is used to specify Excel's "Average" style // conditional format: // // // Top/Bottom rules: Above Average... // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "average", // Criteria: "=", // Format: format1, // AboveAverage: true, // }, // }, // ) // // // Top/Bottom rules: Below Average... // err := f.SetConditionalFormat("Sheet1", "B1:B10", // []excelize.ConditionalFormatOptions{ // { // Type: "average", // Criteria: "=", // Format: format2, // AboveAverage: false, // }, // }, // ) // // type: duplicate - The duplicate type is used to highlight duplicate cells in // a range: // // // Highlight cells rules: Duplicate Values... // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // {Type: "duplicate", Criteria: "=", Format: format}, // }, // ) // // type: unique - The unique type is used to highlight unique cells in a range: // // // Highlight cells rules: Not Equal To... // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // {Type: "unique", Criteria: "=", Format: format}, // }, // ) // // type: top - The top type is used to specify the top n values by number or // percentage in a range: // // // Top/Bottom rules: Top 10. // err := f.SetConditionalFormat("Sheet1", "H1:H10", // []excelize.ConditionalFormatOptions{ // { // Type: "top", // Criteria: "=", // Format: format, // Value: "6", // }, // }, // ) // // The criteria can be used to indicate that a percentage condition is required: // // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "top", // Criteria: "=", // Format: format, // Value: "6", // Percent: true, // }, // }, // ) // // type: 2_color_scale - The 2_color_scale type is used to specify Excel's "2 // Color Scale" style conditional format: // // // Color scales: 2 color. // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "2_color_scale", // Criteria: "=", // MinType: "min", // MaxType: "max", // MinColor: "#F8696B", // MaxColor: "#63BE7B", // }, // }, // ) // // This conditional type can be modified with MinType, MaxType, MinValue, // MaxValue, MinColor and MaxColor, see below. // // type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3 // Color Scale" style conditional format: // // // Color scales: 3 color. // err := f.SetConditionalFormat("Sheet1", "A1:A10", // []excelize.ConditionalFormatOptions{ // { // Type: "3_color_scale", // Criteria: "=", // MinType: "min", // MidType: "percentile", // MaxType: "max", // MinColor: "#F8696B", // MidColor: "#FFEB84", // MaxColor: "#63BE7B", // }, // }, // ) // // This conditional type can be modified with MinType, MidType, MaxType, // MinValue, MidValue, MaxValue, MinColor, MidColor and MaxColor, see // below. // // type: data_bar - The data_bar type is used to specify Excel's "Data Bar" // style conditional format. // // MinType - The MinType and MaxType properties are available when the // conditional formatting type is 2_color_scale, 3_color_scale or data_bar. // The MidType is available for 3_color_scale. The properties are used as // follows: // // // Data Bars: Gradient Fill. // err := f.SetConditionalFormat("Sheet1", "K1:K10", // []excelize.ConditionalFormatOptions{ // { // Type: "data_bar", // Criteria: "=", // MinType: "min", // MaxType: "max", // BarColor: "#638EC6", // }, // }, // ) // // The available min/mid/max types are: // // min (for MinType only) // num // percent // percentile // formula // max (for MaxType only) // // MidType - Used for 3_color_scale. Same as MinType, see above. // // MaxType - Same as MinType, see above. // // MinValue - The MinValue and MaxValue properties are available when the // conditional formatting type is 2_color_scale, 3_color_scale or data_bar. // // MidValue - The MidValue is available for 3_color_scale. Same as MinValue, // see above. // // MaxValue - Same as MinValue, see above. // // MinColor - The MinColor and MaxColor properties are available when the // conditional formatting type is 2_color_scale, 3_color_scale or data_bar. // // MidColor - The MidColor is available for 3_color_scale. The properties // are used as follows: // // // Color scales: 3 color. // err := f.SetConditionalFormat("Sheet1", "B1:B10", // []excelize.ConditionalFormatOptions{ // { // Type: "3_color_scale", // Criteria: "=", // MinType: "min", // MidType: "percentile", // MaxType: "max", // MinColor: "#F8696B", // MidColor: "#FFEB84", // MaxColor: "#63BE7B", // }, // }, // ) // // MaxColor - Same as MinColor, see above. // // BarColor - Used for data_bar. Same as MinColor, see above. // // BarBorderColor - Used for sets the color for the border line of a data bar, // this is only visible in Excel 2010 and later. // // BarDirection - sets the direction for data bars. The available options are: // // context - Data bar direction is set by spreadsheet application based on the context of the data displayed. // leftToRight - Data bar direction is from right to left. // rightToLeft - Data bar direction is from left to right. // // BarOnly - Used for set displays a bar data but not the data in the cells. // // BarSolid - Used for turns on a solid (non-gradient) fill for data bars, this // is only visible in Excel 2010 and later. // // IconStyle - The available options are: // // 3Arrows // 3ArrowsGray // 3Flags // 3Signs // 3Symbols // 3Symbols2 // 3TrafficLights1 // 3TrafficLights2 // 4Arrows // 4ArrowsGray // 4Rating // 4RedToBlack // 4TrafficLights // 5Arrows // 5ArrowsGray // 5Quarters // 5Rating // // ReverseIcons - Used for set reversed icons sets. // // IconsOnly - Used for set displayed without the cell value. // // StopIfTrue - used to set the "stop if true" feature of a conditional // formatting rule when more than one rule is applied to a cell or a range of // cells. When this parameter is set then subsequent rules are not evaluated // if the current rule is true. func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFormatOptions) error { drawContFmtFunc := map[string]func(p int, ct, GUID string, fmtCond *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule){ "cellIs": drawCondFmtCellIs, "top10": drawCondFmtTop10, "aboveAverage": drawCondFmtAboveAverage, "duplicateValues": drawCondFmtDuplicateUniqueValues, "uniqueValues": drawCondFmtDuplicateUniqueValues, "2_color_scale": drawCondFmtColorScale, "3_color_scale": drawCondFmtColorScale, "dataBar": drawCondFmtDataBar, "expression": drawCondFmtExp, "iconSet": drawCondFmtIconSet, } ws, err := f.workSheetReader(sheet) if err != nil { return err } // Create a pseudo GUID for each unique rule. var rules int for _, cf := range ws.ConditionalFormatting { rules += len(cf.CfRule) } GUID := fmt.Sprintf("{00000000-0000-0000-%04X-%012X}", f.getSheetID(sheet), rules) var cfRule []*xlsxCfRule for p, v := range opts { var vt, ct string var ok bool // "type" is a required parameter, check for valid validation types. vt, ok = validType[v.Type] if ok { // Check for valid criteria types. ct, ok = criteriaType[v.Criteria] if ok || vt == "expression" || vt == "iconSet" { drawFunc, ok := drawContFmtFunc[vt] if ok { rule, x14rule := drawFunc(p, ct, GUID, &v) if rule == nil { return ErrParameterInvalid } if x14rule != nil { if err = f.appendCfRule(ws, x14rule); err != nil { return err } f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14) } cfRule = append(cfRule, rule) } } } } ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{ SQRef: rangeRef, CfRule: cfRule, }) return err } // appendCfRule provides a function to append rules to conditional formatting. func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error { var ( err error idx int appendMode bool decodeExtLst = new(decodeWorksheetExt) condFmts *xlsxX14ConditionalFormattings decodeCondFmts *decodeX14ConditionalFormattings ext *xlsxWorksheetExt condFmtBytes, condFmtsBytes, extLstBytes []byte ) condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{ {XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: []*xlsxX14CfRule{rule}}, }) if ws.ExtLst != nil { // append mode ext if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err } for idx, ext = range decodeExtLst.Ext { if ext.URI == ExtURIConditionalFormattings { decodeCondFmts = new(decodeX14ConditionalFormattings) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts) if condFmts == nil { condFmts = &xlsxX14ConditionalFormattings{} } condFmts.Content = decodeCondFmts.Content + string(condFmtBytes) condFmtsBytes, _ = xml.Marshal(condFmts) decodeExtLst.Ext[idx].Content = string(condFmtsBytes) appendMode = true } } } if !appendMode { condFmtsBytes, _ = xml.Marshal(&xlsxX14ConditionalFormattings{Content: string(condFmtBytes)}) decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxWorksheetExt{ URI: ExtURIConditionalFormattings, Content: string(condFmtsBytes), }) } sort.Slice(decodeExtLst.Ext, func(i, j int) bool { return inStrSlice(extensionURIPriority, decodeExtLst.Ext[i].URI, false) < inStrSlice(extensionURIPriority, decodeExtLst.Ext[j].URI, false) }) extLstBytes, err = xml.Marshal(decodeExtLst) ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} return err } // extractCondFmtCellIs provides a function to extract conditional format // settings for cell value (include between, not between, equal, not equal, // greater than and less than) by given conditional formatting rule. func extractCondFmtCellIs(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue, Type: "cell", Criteria: operatorType[c.Operator], Format: *c.DxfID} if len(c.Formula) == 2 { format.Minimum, format.Maximum = c.Formula[0], c.Formula[1] return format } format.Value = c.Formula[0] return format } // extractCondFmtTop10 provides a function to extract conditional format // settings for top N (default is top 10) by given conditional formatting // rule. func extractCondFmtTop10(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{ StopIfTrue: c.StopIfTrue, Type: "top", Criteria: "=", Format: *c.DxfID, Percent: c.Percent, Value: strconv.Itoa(c.Rank), } if c.Bottom { format.Type = "bottom" } return format } // extractCondFmtAboveAverage provides a function to extract conditional format // settings for above average and below average by given conditional formatting // rule. func extractCondFmtAboveAverage(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return ConditionalFormatOptions{ StopIfTrue: c.StopIfTrue, Type: "average", Criteria: "=", Format: *c.DxfID, AboveAverage: *c.AboveAverage, } } // extractCondFmtDuplicateUniqueValues provides a function to extract // conditional format settings for duplicate and unique values by given // conditional formatting rule. func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { return ConditionalFormatOptions{ StopIfTrue: c.StopIfTrue, Type: map[string]string{ "duplicateValues": "duplicate", "uniqueValues": "unique", }[c.Type], Criteria: "=", Format: *c.DxfID, } } // extractCondFmtColorScale provides a function to extract conditional format // settings for color scale (include 2 color scale and 3 color scale) by given // conditional formatting rule. func extractCondFmtColorScale(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue} format.Type, format.Criteria = "2_color_scale", "=" values := len(c.ColorScale.Cfvo) colors := len(c.ColorScale.Color) if colors > 1 && values > 1 { format.MinType = c.ColorScale.Cfvo[0].Type if c.ColorScale.Cfvo[0].Val != "0" { format.MinValue = c.ColorScale.Cfvo[0].Val } format.MinColor = "#" + strings.TrimPrefix(strings.ToUpper(c.ColorScale.Color[0].RGB), "FF") format.MaxType = c.ColorScale.Cfvo[1].Type if c.ColorScale.Cfvo[1].Val != "0" { format.MaxValue = c.ColorScale.Cfvo[1].Val } format.MaxColor = "#" + strings.TrimPrefix(strings.ToUpper(c.ColorScale.Color[1].RGB), "FF") } if colors == 3 { format.Type = "3_color_scale" format.MidType = c.ColorScale.Cfvo[1].Type if c.ColorScale.Cfvo[1].Val != "0" { format.MidValue = c.ColorScale.Cfvo[1].Val } format.MidColor = "#" + strings.TrimPrefix(strings.ToUpper(c.ColorScale.Color[1].RGB), "FF") format.MaxType = c.ColorScale.Cfvo[2].Type if c.ColorScale.Cfvo[2].Val != "0" { format.MaxValue = c.ColorScale.Cfvo[2].Val } format.MaxColor = "#" + strings.TrimPrefix(strings.ToUpper(c.ColorScale.Color[2].RGB), "FF") } return format } // extractCondFmtDataBar provides a function to extract conditional format // settings for data bar by given conditional formatting rule. func extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{Type: "data_bar", Criteria: "="} if c.DataBar != nil { format.StopIfTrue = c.StopIfTrue format.MinType = c.DataBar.Cfvo[0].Type format.MaxType = c.DataBar.Cfvo[1].Type format.BarColor = "#" + strings.TrimPrefix(strings.ToUpper(c.DataBar.Color[0].RGB), "FF") if c.DataBar.ShowValue != nil { format.BarOnly = !*c.DataBar.ShowValue } } extractDataBarRule := func(condFmts []decodeX14ConditionalFormatting) { for _, condFmt := range condFmts { for _, rule := range condFmt.CfRule { if rule.DataBar != nil { format.BarSolid = !rule.DataBar.Gradient format.BarDirection = rule.DataBar.Direction if rule.DataBar.BorderColor != nil { format.BarBorderColor = "#" + strings.TrimPrefix(strings.ToUpper(rule.DataBar.BorderColor.RGB), "FF") } } } } } extractExtLst := func(extLst *decodeWorksheetExt) { for _, ext := range extLst.Ext { if ext.URI == ExtURIConditionalFormattings { decodeCondFmts := new(decodeX14ConditionalFormattings) if err := xml.Unmarshal([]byte(ext.Content), &decodeCondFmts); err == nil { condFmts := []decodeX14ConditionalFormatting{} if err = xml.Unmarshal([]byte(decodeCondFmts.Content), &condFmts); err == nil { extractDataBarRule(condFmts) } } } } } if c.ExtLst != nil { ext := decodeX14ConditionalFormattingExt{} if err := xml.Unmarshal([]byte(c.ExtLst.Ext), &ext); err == nil && extLst != nil { decodeExtLst := new(decodeWorksheetExt) if err = xml.Unmarshal([]byte(""+extLst.Ext+""), decodeExtLst); err == nil { extractExtLst(decodeExtLst) } } } return format } // extractCondFmtExp provides a function to extract conditional format settings // for expression by given conditional formatting rule. func extractCondFmtExp(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue, Type: "formula", Format: *c.DxfID} if len(c.Formula) > 0 { format.Criteria = c.Formula[0] } return format } // extractCondFmtIconSet provides a function to extract conditional format // settings for icon sets by given conditional formatting rule. func extractCondFmtIconSet(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { format := ConditionalFormatOptions{Type: "iconSet"} if c.IconSet != nil { if c.IconSet.ShowValue != nil { format.IconsOnly = !*c.IconSet.ShowValue } format.IconStyle = c.IconSet.IconSet format.ReverseIcons = c.IconSet.Reverse } return format } // GetConditionalFormats returns conditional format settings by given worksheet // name. func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) { extractContFmtFunc := map[string]func(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions{ "cellIs": extractCondFmtCellIs, "top10": extractCondFmtTop10, "aboveAverage": extractCondFmtAboveAverage, "duplicateValues": extractCondFmtDuplicateUniqueValues, "uniqueValues": extractCondFmtDuplicateUniqueValues, "colorScale": extractCondFmtColorScale, "dataBar": extractCondFmtDataBar, "expression": extractCondFmtExp, "iconSet": extractCondFmtIconSet, } conditionalFormats := make(map[string][]ConditionalFormatOptions) ws, err := f.workSheetReader(sheet) if err != nil { return conditionalFormats, err } for _, cf := range ws.ConditionalFormatting { var opts []ConditionalFormatOptions for _, cr := range cf.CfRule { if extractFunc, ok := extractContFmtFunc[cr.Type]; ok { opts = append(opts, extractFunc(cr, ws.ExtLst)) } } conditionalFormats[cf.SQRef] = opts } return conditionalFormats, err } // UnsetConditionalFormat provides a function to unset the conditional format // by given worksheet name and range reference. func (f *File) UnsetConditionalFormat(sheet, rangeRef string) error { ws, err := f.workSheetReader(sheet) if err != nil { return err } for i, cf := range ws.ConditionalFormatting { if cf.SQRef == rangeRef { ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i], ws.ConditionalFormatting[i+1:]...) return nil } } return nil } // drawCondFmtCellIs provides a function to create conditional formatting rule // for cell value (include between, not between, equal, not equal, greater // than and less than) by given priority, criteria type and format settings. func drawCondFmtCellIs(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { c := &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], Operator: ct, DxfID: &format.Format, } // "between" and "not between" criteria require 2 values. if ct == "between" || ct == "notBetween" { c.Formula = append(c.Formula, []string{format.Minimum, format.Maximum}...) } if idx := inStrSlice([]string{"equal", "notEqual", "greaterThan", "lessThan", "greaterThanOrEqual", "lessThanOrEqual", "containsText", "notContains", "beginsWith", "endsWith"}, ct, true); idx != -1 { c.Formula = append(c.Formula, format.Value) } return c, nil } // drawCondFmtTop10 provides a function to create conditional formatting rule // for top N (default is top 10) by given priority, criteria type and format // settings. func drawCondFmtTop10(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { c := &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Bottom: format.Type == "bottom", Type: validType[format.Type], Rank: 10, DxfID: &format.Format, Percent: format.Percent, } if rank, err := strconv.Atoi(format.Value); err == nil { c.Rank = rank } return c, nil } // drawCondFmtAboveAverage provides a function to create conditional // formatting rule for above average and below average by given priority, // criteria type and format settings. func drawCondFmtAboveAverage(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], AboveAverage: &format.AboveAverage, DxfID: &format.Format, }, nil } // drawCondFmtDuplicateUniqueValues provides a function to create conditional // formatting rule for duplicate and unique values by given priority, criteria // type and format settings. func drawCondFmtDuplicateUniqueValues(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], DxfID: &format.Format, }, nil } // drawCondFmtColorScale provides a function to create conditional formatting // rule for color scale (include 2 color scale and 3 color scale) by given // priority, criteria type and format settings. func drawCondFmtColorScale(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { minValue := format.MinValue if minValue == "" { minValue = "0" } maxValue := format.MaxValue if maxValue == "" { maxValue = "0" } midValue := format.MidValue if midValue == "" { midValue = "50" } c := &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: "colorScale", ColorScale: &xlsxColorScale{ Cfvo: []*xlsxCfvo{ {Type: format.MinType, Val: minValue}, }, Color: []*xlsxColor{ {RGB: getPaletteColor(format.MinColor)}, }, }, } if validType[format.Type] == "3_color_scale" { c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MidType, Val: midValue}) c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MidColor)}) } c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MaxType, Val: maxValue}) c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MaxColor)}) return c, nil } // drawCondFmtDataBar provides a function to create conditional formatting // rule for data bar by given priority, criteria type and format settings. func drawCondFmtDataBar(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { var x14CfRule *xlsxX14CfRule var extLst *xlsxExtLst if format.BarSolid || format.BarDirection == "leftToRight" || format.BarDirection == "rightToLeft" || format.BarBorderColor != "" { extLst = &xlsxExtLst{Ext: fmt.Sprintf(`%s`, ExtURIConditionalFormattingRuleID, NameSpaceSpreadSheetX14.Value, GUID)} x14CfRule = &xlsxX14CfRule{ Type: validType[format.Type], ID: GUID, DataBar: &xlsx14DataBar{ MaxLength: 100, Border: format.BarBorderColor != "", Gradient: !format.BarSolid, Direction: format.BarDirection, Cfvo: []*xlsxCfvo{{Type: "autoMin"}, {Type: "autoMax"}}, NegativeFillColor: &xlsxColor{RGB: "FFFF0000"}, AxisColor: &xlsxColor{RGB: "FFFF0000"}, }, } if x14CfRule.DataBar.Border { x14CfRule.DataBar.BorderColor = &xlsxColor{RGB: getPaletteColor(format.BarBorderColor)} } } return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], DataBar: &xlsxDataBar{ ShowValue: boolPtr(!format.BarOnly), Cfvo: []*xlsxCfvo{{Type: format.MinType}, {Type: format.MaxType}}, Color: []*xlsxColor{{RGB: getPaletteColor(format.BarColor)}}, }, ExtLst: extLst, }, x14CfRule } // drawCondFmtExp provides a function to create conditional formatting rule // for expression by given priority, criteria type and format settings. func drawCondFmtExp(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { return &xlsxCfRule{ Priority: p + 1, StopIfTrue: format.StopIfTrue, Type: validType[format.Type], Formula: []string{format.Criteria}, DxfID: &format.Format, }, nil } // drawCondFmtIconSet provides a function to create conditional formatting rule // for icon set by given priority, criteria type and format settings. func drawCondFmtIconSet(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { cfvo3 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ {Type: "percent", Val: "0"}, {Type: "percent", Val: "33"}, {Type: "percent", Val: "67"}, }}} cfvo4 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ {Type: "percent", Val: "0"}, {Type: "percent", Val: "25"}, {Type: "percent", Val: "50"}, {Type: "percent", Val: "75"}, }}} cfvo5 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ {Type: "percent", Val: "0"}, {Type: "percent", Val: "20"}, {Type: "percent", Val: "40"}, {Type: "percent", Val: "60"}, {Type: "percent", Val: "80"}, }}} presets := map[string]*xlsxCfRule{ "3Arrows": cfvo3, "3ArrowsGray": cfvo3, "3Flags": cfvo3, "3Signs": cfvo3, "3Symbols": cfvo3, "3Symbols2": cfvo3, "3TrafficLights1": cfvo3, "3TrafficLights2": cfvo3, "4Arrows": cfvo4, "4ArrowsGray": cfvo4, "4Rating": cfvo4, "4RedToBlack": cfvo4, "4TrafficLights": cfvo4, "5Arrows": cfvo5, "5ArrowsGray": cfvo5, "5Quarters": cfvo5, "5Rating": cfvo5, } cfRule, ok := presets[format.IconStyle] if !ok { return nil, nil } cfRule.Priority = p + 1 cfRule.IconSet.IconSet = format.IconStyle cfRule.IconSet.Reverse = format.ReverseIcons cfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly) cfRule.Type = format.Type return cfRule, nil } // getPaletteColor provides a function to convert the RBG color by given // string. func getPaletteColor(color string) string { return "FF" + strings.ReplaceAll(strings.ToUpper(color), "#", "") } // themeReader provides a function to get the pointer to the xl/theme/theme1.xml // structure after deserialization. func (f *File) themeReader() (*xlsxTheme, error) { if _, ok := f.Pkg.Load(defaultXMLPathTheme); !ok { return nil, nil } theme := xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value} if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathTheme)))). Decode(&theme); err != nil && err != io.EOF { return &theme, err } return &theme, nil } // ThemeColor applied the color with tint value. func ThemeColor(baseColor string, tint float64) string { if tint == 0 { return "FF" + baseColor } r, _ := strconv.ParseUint(baseColor[:2], 16, 64) g, _ := strconv.ParseUint(baseColor[2:4], 16, 64) b, _ := strconv.ParseUint(baseColor[4:6], 16, 64) var h, s, l float64 if r <= math.MaxUint8 && g <= math.MaxUint8 && b <= math.MaxUint8 { h, s, l = RGBToHSL(uint8(r), uint8(g), uint8(b)) } if tint < 0 { l *= 1 + tint } else { l = l*(1-tint) + (1 - (1 - tint)) } br, bg, bb := HSLToRGB(h, s, l) return fmt.Sprintf("FF%02X%02X%02X", br, bg, bb) }