/******************************************************************************* * You may amend and distribute as you like, but don't remove this header! * * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. * See http://www.codeplex.com/EPPlus for details. * * Copyright (C) 2011 Jan Källman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html * * All code and executables are provided "as is" with no warranty either express or implied. * The author accepts no liability for any damage or loss of business that this product may cause. * * Code change notes: * * Author Change Date * ****************************************************************************** * Jan Källman Initial Release 2009-10-01 * Jan Källman License changed GPL-->LGPL 2011-12-16 *******************************************************************************/ using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Globalization; using System.Text.RegularExpressions; namespace OfficeOpenXml.Style.XmlAccess { /// /// Xml access class for number formats /// public sealed class ExcelNumberFormatXml : StyleXmlHelper { internal ExcelNumberFormatXml(XmlNamespaceManager nameSpaceManager) : base(nameSpaceManager) { } internal ExcelNumberFormatXml(XmlNamespaceManager nameSpaceManager, bool buildIn): base(nameSpaceManager) { BuildIn = buildIn; } internal ExcelNumberFormatXml(XmlNamespaceManager nsm, XmlNode topNode) : base(nsm, topNode) { _numFmtId = GetXmlNodeInt("@numFmtId"); _format = GetXmlNodeString("@formatCode"); } public bool BuildIn { get; private set; } int _numFmtId; // const string idPath = "@numFmtId"; /// /// Id for number format /// /// Build in ID's /// /// 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 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) /// 45 mm:ss /// 46 [h]:mm:ss /// 47 mmss.0 /// 48 ##0.0E+0 /// 49 @ /// public int NumFmtId { get { return _numFmtId; } set { _numFmtId = value; } } internal override string Id { get { return _format; } } const string fmtPath = "@formatCode"; string _format = string.Empty; public string Format { get { return _format; } set { _numFmtId = ExcelNumberFormat.GetFromBuildIdFromFormat(value); _format = value; } } internal string GetNewID(int NumFmtId, string Format) { if (NumFmtId < 0) { NumFmtId = ExcelNumberFormat.GetFromBuildIdFromFormat(Format); } return NumFmtId.ToString(); } internal static void AddBuildIn(XmlNamespaceManager NameSpaceManager, ExcelStyleCollection NumberFormats) { NumberFormats.Add("General",new ExcelNumberFormatXml(NameSpaceManager,true){NumFmtId=0,Format="General"}); NumberFormats.Add("0", new ExcelNumberFormatXml(NameSpaceManager,true) { NumFmtId = 1, Format = "0" }); NumberFormats.Add("0.00", new ExcelNumberFormatXml(NameSpaceManager,true) { NumFmtId = 2, Format = "0.00" }); NumberFormats.Add("#,##0", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 3, Format = "#,##0" }); NumberFormats.Add("#,##0.00", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 4, Format = "#,##0.00" }); NumberFormats.Add("0%", new ExcelNumberFormatXml(NameSpaceManager,true) { NumFmtId = 9, Format = "0%" }); NumberFormats.Add("0.00%", new ExcelNumberFormatXml(NameSpaceManager,true) { NumFmtId = 10, Format = "0.00%" }); NumberFormats.Add("0.00E+00", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 11, Format = "0.00E+00" }); NumberFormats.Add("# ?/?", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 12, Format = "# ?/?" }); NumberFormats.Add("# ??/??", new ExcelNumberFormatXml(NameSpaceManager,true) { NumFmtId = 13, Format = "# ??/??" }); NumberFormats.Add("mm-dd-yy", new ExcelNumberFormatXml(NameSpaceManager,true) { NumFmtId = 14, Format = "mm-dd-yy" }); NumberFormats.Add("d-mmm-yy", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 15, Format = "d-mmm-yy" }); NumberFormats.Add("d-mmm", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 16, Format = "d-mmm" }); NumberFormats.Add("mmm-yy", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 17, Format = "mmm-yy" }); NumberFormats.Add("h:mm AM/PM", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 18, Format = "h:mm AM/PM" }); NumberFormats.Add("h:mm:ss AM/PM", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 19, Format = "h:mm:ss AM/PM" }); NumberFormats.Add("h:mm", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 20, Format = "h:mm" }); NumberFormats.Add("h:mm:ss", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 21, Format = "h:mm:ss" }); NumberFormats.Add("m/d/yy h:mm", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 22, Format = "m/d/yy h:mm" }); NumberFormats.Add("#,##0 ;(#,##0)", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 37, Format = "#,##0 ;(#,##0)" }); NumberFormats.Add("#,##0 ;[Red](#,##0)", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 38, Format = "#,##0 ;[Red](#,##0)" }); NumberFormats.Add("#,##0.00;(#,##0.00)", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 39, Format = "#,##0.00;(#,##0.00)" }); NumberFormats.Add("#,##0.00;[Red](#,##0.00)", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 40, Format = "#,##0.00;[Red](#,#)" }); NumberFormats.Add("mm:ss", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 45, Format = "mm:ss" }); NumberFormats.Add("[h]:mm:ss", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 46, Format = "[h]:mm:ss" }); NumberFormats.Add("mmss.0", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 47, Format = "mmss.0" }); NumberFormats.Add("##0.0", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 48, Format = "##0.0" }); NumberFormats.Add("@", new ExcelNumberFormatXml(NameSpaceManager, true) { NumFmtId = 49, Format = "@" }); NumberFormats.NextId = 164; //Start for custom formats. } internal override XmlNode CreateXmlNode(XmlNode topNode) { TopNode = topNode; SetXmlNodeString("@numFmtId", NumFmtId.ToString()); SetXmlNodeString("@formatCode", Format); return TopNode; } internal enum eFormatType { Unknown = 0, Number = 1, DateTime = 2, } ExcelFormatTranslator _translator = null; internal ExcelFormatTranslator FormatTranslator { get { if (_translator == null) { _translator = new ExcelFormatTranslator(Format, NumFmtId); } return _translator; } } #region Excel --> .Net Format internal class ExcelFormatTranslator { internal ExcelFormatTranslator(string format, int numFmtID) { if (numFmtID == 14) { NetFormat = NetFormatForWidth = "d"; NetTextFormat = NetTextFormatForWidth = ""; DataType = eFormatType.DateTime; } else if (format.Equals("general",StringComparison.InvariantCultureIgnoreCase)) { NetFormat = NetFormatForWidth = "0.#####"; NetTextFormat = NetTextFormatForWidth = ""; DataType = eFormatType.Number; } else { ToNetFormat(format, false); ToNetFormat(format, true); } } internal string NetTextFormat { get; private set; } internal string NetFormat { get; private set; } CultureInfo _ci = null; internal CultureInfo Culture { get { if (_ci == null) { return CultureInfo.CurrentCulture; } return _ci; } private set { _ci = value; } } internal eFormatType DataType { get; private set; } internal string NetTextFormatForWidth { get; private set; } internal string NetFormatForWidth { get; private set; } //internal string FractionFormatInteger { get; private set; } internal string FractionFormat { get; private set; } //internal string FractionFormat2 { get; private set; } private void ToNetFormat(string ExcelFormat, bool forColWidth) { DataType = eFormatType.Unknown; int secCount = 0; bool isText = false; bool isBracket = false; string bracketText = ""; bool prevBslsh = false; bool useMinute = false; bool prevUnderScore = false; bool ignoreNext = false; int fractionPos = -1; string specialDateFormat = ""; bool containsAmPm = ExcelFormat.Contains("AM/PM"); List lstDec=new List(); StringBuilder sb = new StringBuilder(); Culture = null; var format = ""; var text = ""; char clc; if (containsAmPm) { ExcelFormat = Regex.Replace(ExcelFormat, "AM/PM", ""); DataType = eFormatType.DateTime; } for (int pos = 0; pos < ExcelFormat.Length; pos++) { char c = ExcelFormat[pos]; if (c == '"') { isText = !isText; } else { if (ignoreNext) { ignoreNext = false; continue; } else if (isText && !isBracket) { sb.Append(c); } else if (isBracket) { if (c == ']') { isBracket = false; if (bracketText[0] == '$') //Local Info { string[] li = Regex.Split(bracketText, "-"); if (li[0].Length > 1) { sb.Append("\"" + li[0].Substring(1, li[0].Length - 1) + "\""); //Currency symbol } if (li.Length > 1) { if (li[1].Equals("f800", StringComparison.InvariantCultureIgnoreCase)) { specialDateFormat = "D"; } else if (li[1].Equals("f400", StringComparison.InvariantCultureIgnoreCase)) { specialDateFormat = "T"; } else { var num = int.Parse(li[1], NumberStyles.HexNumber); try { Culture = CultureInfo.GetCultureInfo(num & 0xFFFF); } catch { Culture = null; } } } } else if(bracketText[0]=='t') { sb.Append("hh"); //TODO:This will not be correct for dates over 24H. } else if (bracketText[0] == 'h') { specialDateFormat = "hh"; //TODO:This will not be correct for dates over 24H. } } else { bracketText += c; } } else if (prevUnderScore) { if (forColWidth) { sb.AppendFormat("\"{0}\"", c); } prevUnderScore = false; } else { if (c == ';') //We use first part (for positive only at this stage) { secCount++; if (DataType == eFormatType.DateTime || secCount == 3) { //Add qoutes SetDecimal(lstDec, sb); lstDec = new List(); format = sb.ToString(); sb = new StringBuilder(); } else { sb.Append(c); } } else { clc = c.ToString().ToLower(CultureInfo.InvariantCulture)[0]; //Lowercase character //Set the datetype if (DataType == eFormatType.Unknown) { if (c == '0' || c == '#' || c == '.') { DataType = eFormatType.Number; } else if (clc == 'y' || clc == 'm' || clc == 'd' || clc == 'h' || clc == 'm' || clc == 's') { DataType = eFormatType.DateTime; } } if (prevBslsh) { sb.Append(c); prevBslsh = false; } else if (c == '[') { bracketText = ""; isBracket = true; } else if (c == '\\') { prevBslsh = true; } else if (c == '0' || c == '#' || c == '.' || c == ',' || c == '%' || clc == 'd' || clc == 's') { sb.Append(c); if(c=='.') { lstDec.Add(sb.Length - 1); } } else if (clc == 'h') { if (containsAmPm) { sb.Append('h'); ; } else { sb.Append('H'); } useMinute = true; } else if (clc == 'm') { if (useMinute) { sb.Append('m'); } else { sb.Append('M'); } } else if (c == '_') //Skip next but use for alignment { prevUnderScore = true; } else if (c == '?') { sb.Append(' '); } else if (c == '/') { if (DataType == eFormatType.Number) { fractionPos = sb.Length; int startPos = pos - 1; while (startPos >= 0 && (ExcelFormat[startPos] == '?' || ExcelFormat[startPos] == '#' || ExcelFormat[startPos] == '0')) { startPos--; } if (startPos > 0) //RemovePart sb.Remove(sb.Length-(pos-startPos-1),(pos-startPos-1)) ; int endPos = pos + 1; while (endPos < ExcelFormat.Length && (ExcelFormat[endPos] == '?' || ExcelFormat[endPos] == '#' || (ExcelFormat[endPos] >= '0' && ExcelFormat[endPos]<= '9'))) { endPos++; } pos = endPos; if (FractionFormat != "") { FractionFormat = ExcelFormat.Substring(startPos+1, endPos - startPos-1); } sb.Append('?'); //Will be replaced later on by the fraction } else { sb.Append('/'); } } else if (c == '*') { //repeat char--> ignore ignoreNext = true; } else if (c == '@') { sb.Append("{0}"); } else { sb.Append(c); } } } } } //Add qoutes SetDecimal(lstDec, sb); // AM/PM format if (containsAmPm) { format += "tt"; } if (format == "") format = sb.ToString(); else text = sb.ToString(); if (specialDateFormat != "") { format = specialDateFormat; } if (forColWidth) { NetFormatForWidth = format; NetTextFormatForWidth = text; } else { NetFormat = format; NetTextFormat = text; } if (Culture == null) { Culture = CultureInfo.CurrentCulture; } } private static void SetDecimal(List lstDec, StringBuilder sb) { if (lstDec.Count > 1) { for (int i = lstDec.Count - 1; i >= 0; i--) { sb.Insert(lstDec[i] + 1, '\''); sb.Insert(lstDec[i], '\''); } } } internal string FormatFraction(double d) { int numerator, denomerator; int intPart = (int)d; string[] fmt = FractionFormat.Split('/'); int fixedDenominator; if (!int.TryParse(fmt[1], out fixedDenominator)) { fixedDenominator = 0; } if (d == 0 || double.IsNaN(d)) { if (fmt[0].Trim() == "" && fmt[1].Trim() == "") { return new string(' ', FractionFormat.Length); } else { return 0.ToString(fmt[0]) + "/" + 1.ToString(fmt[0]); } } int maxDigits = fmt[1].Length; string sign = d < 0 ? "-" : ""; if (fixedDenominator == 0) { List numerators = new List() { 1, 0 }; List denominators = new List() { 0, 1 }; if (maxDigits < 1 && maxDigits > 12) { throw (new ArgumentException("Number of digits out of range (1-12)")); } int maxNum = 0; for (int i = 0; i < maxDigits; i++) { maxNum += 9 * (int)(Math.Pow((double)10, (double)i)); } double divRes = 1 / ((double)Math.Abs(d) - intPart); double result, prevResult = double.NaN; int listPos = 2, index = 1; while (true) { index++; double intDivRes = Math.Floor(divRes); numerators.Add((intDivRes * numerators[index - 1] + numerators[index - 2])); if (numerators[index] > maxNum) { break; } denominators.Add((intDivRes * denominators[index - 1] + denominators[index - 2])); result = numerators[index] / denominators[index]; if (denominators[index] > maxNum) { break; } listPos = index; if (result == prevResult) break; if (result == d) break; prevResult = result; divRes = 1 / (divRes - intDivRes); //Rest } numerator = (int)numerators[listPos]; denomerator = (int)denominators[listPos]; } else { numerator = (int)Math.Round((d - intPart) / (1D / fixedDenominator), 0); denomerator = fixedDenominator; } if (numerator == denomerator || numerator==0) { if(numerator == denomerator) intPart++; return sign + intPart.ToString(NetFormat).Replace("?", new string(' ', FractionFormat.Length)); } else if (intPart == 0) { return sign + FmtInt(numerator, fmt[0]) + "/" + FmtInt(denomerator, fmt[1]); } else { return sign + intPart.ToString(NetFormat).Replace("?", FmtInt(numerator, fmt[0]) + "/" + FmtInt(denomerator, fmt[1])); } } private string FmtInt(double value, string format) { string v = value.ToString("#"); string pad = ""; if (v.Length < format.Length) { for (int i = format.Length - v.Length-1; i >= 0; i--) { if (format[i] == '?') { pad += " "; } else if (format[i] == ' ') { pad += "0"; } } } return pad + v; } } #endregion } }