123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- // Copyright (c) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Drawing;
- using System.Globalization;
- using System.Linq;
- using System.Text;
- using System.Text.RegularExpressions;
- using System.Xml.Linq;
- using DocumentFormat.OpenXml.Packaging;
- namespace OpenXmlPowerTools
- {
- public class SmlCellFormatter
- {
- private enum CellType
- {
- General,
- Number,
- Date,
- };
- private class FormatConfig
- {
- public CellType CellType;
- public string FormatCode;
- }
- private static Dictionary<string, FormatConfig> ExcelFormatCodeToNetFormatCodeExceptionMap = new Dictionary<string, FormatConfig>()
- {
- {
- "# ?/?",
- new FormatConfig
- {
- CellType = SmlCellFormatter.CellType.Number,
- FormatCode = "0.00",
- }
- },
- {
- "# ??/??",
- new FormatConfig
- {
- CellType = SmlCellFormatter.CellType.Number,
- FormatCode = "0.00",
- }
- },
- };
- // Up to four sections of format codes can be specified. The format codes, separated by semicolons, define the
- // formats for positive numbers, negative numbers, zero values, and text, in that order. If only two sections are
- // specified, the first is used for positive numbers and zeros, and the second is used for negative numbers. If only
- // one section is specified, it is used for all numbers. To skip a section, the ending semicolon for that section shall
- // be written.
- public static string FormatCell(string formatCode, string value, out string color)
- {
- color = null;
- if (formatCode == null)
- formatCode = "General";
- var splitFormatCode = formatCode.Split(';');
- if (splitFormatCode.Length == 1)
- {
- double dv;
- if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out dv))
- {
- return FormatDouble(formatCode, dv, out color);
- }
- return value;
- }
- if (splitFormatCode.Length == 2)
- {
- double dv;
- if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out dv))
- {
- if (dv > 0)
- {
- return FormatDouble(splitFormatCode[0], dv, out color);
- }
- else
- {
- return FormatDouble(splitFormatCode[1], dv, out color);
- }
- }
- return value;
- }
- // positive, negative, zero, text
- // _("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)
- if (splitFormatCode.Length == 4)
- {
- double dv;
- if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out dv))
- {
- if (dv > 0)
- {
- var z1 = FormatDouble(splitFormatCode[0], dv, out color);
- return z1;
- }
- else if (dv < 0)
- {
- var z2 = FormatDouble(splitFormatCode[1], dv, out color);
- return z2;
- }
- else // == 0
- {
- var z3 = FormatDouble(splitFormatCode[2], dv, out color);
- return z3;
- }
- }
- string fmt = splitFormatCode[3].Replace("@", "{0}").Replace("\"", "");
- try
- {
- var s = string.Format(fmt, value);
- return s;
- }
- catch (Exception)
- {
- return value;
- }
- }
- return value;
- }
- static Regex UnderRegex = new Regex("_.");
- // The following Regex transforms currency specifies into a character / string
- // that string.Format can use to properly produce the correct text.
- // "[$£-809]" => "£"
- // "[$€-2]" => "€"
- // "[$¥-804]" => "¥
- // "[$CHF-100C]" => "CHF"
- static string s_CurrRegex = @"\[\$(?<curr>.*-).*\]";
- private static string ConvertFormatCode(string formatCode)
- {
- var newFormatCode = formatCode
- .Replace("mmm-", "MMM-")
- .Replace("-mmm", "-MMM")
- .Replace("mm-", "MM-")
- .Replace("mmmm", "MMMM")
- .Replace("AM/PM", "tt")
- .Replace("m/", "M/")
- .Replace("*", "")
- .Replace("?", "#")
- ;
- var withTrimmedUnderscores = UnderRegex.Replace(newFormatCode, "");
- var withTransformedCurrency = Regex.Replace(withTrimmedUnderscores, s_CurrRegex, m => m.Groups[1].Value.TrimEnd('-'));
- return withTransformedCurrency;
- }
- private static string[] ValidColors = new[] {
- "Black",
- "Blue",
- "Cyan",
- "Green",
- "Magenta",
- "Red",
- "White",
- "Yellow",
- };
- private static string FormatDouble(string formatCode, double dv, out string color)
- {
- color = null;
- var trimmed = formatCode.Trim();
- if (trimmed.StartsWith("[") &&
- trimmed.Contains("]"))
- {
- var colorLen = trimmed.IndexOf(']');
- color = trimmed.Substring(1, colorLen - 1);
- if (ValidColors.Contains(color) ||
- color.StartsWith("Color"))
- {
- if (color.StartsWith("Color"))
- {
- var idxStr = color.Substring(5);
- int colorIdx;
- if (int.TryParse(idxStr, out colorIdx))
- {
- if (colorIdx < SmlDataRetriever.IndexedColors.Length)
- color = SmlDataRetriever.IndexedColors[colorIdx];
- else
- color = null;
- }
- }
- formatCode = trimmed.Substring(colorLen + 1);
- }
- else
- color = null;
- }
- if (formatCode == "General")
- return dv.ToString(CultureInfo.InvariantCulture);
- bool isDate = IsFormatCodeForDate(formatCode);
- var cfc = ConvertFormatCode(formatCode);
- if (isDate)
- {
- DateTime thisDate;
- try
- {
- thisDate = DateTime.FromOADate(dv);
- }
- catch (ArgumentException)
- {
- return dv.ToString(CultureInfo.InvariantCulture);
- }
- if (cfc.StartsWith("[h]"))
- {
- DateTime zeroHour = new DateTime(1899, 12, 30, 0, 0, 0);
- int deltaInHours = (int)((thisDate - zeroHour).TotalHours);
- var newCfc = cfc.Substring(3);
- var s = (deltaInHours.ToString() + thisDate.ToString(newCfc)).Trim();
- return s;
- }
- if (cfc.EndsWith(".0"))
- {
- var cfc2 = cfc.Replace(".0", ":fff");
- var s4 = thisDate.ToString(cfc2).Trim();
- return s4;
- }
- var s2 = thisDate.ToString(cfc, CultureInfo.InvariantCulture).Trim();
- return s2;
- }
- if (ExcelFormatCodeToNetFormatCodeExceptionMap.ContainsKey(formatCode))
- {
- FormatConfig fc = ExcelFormatCodeToNetFormatCodeExceptionMap[formatCode];
- var s = dv.ToString(fc.FormatCode, CultureInfo.InvariantCulture).Trim();
- return s;
- }
- if ((cfc.Contains('(') && cfc.Contains(')')) || cfc.Contains('-'))
- {
- var s3 = (-dv).ToString(cfc, CultureInfo.InvariantCulture).Trim();
- return s3;
- }
- else
- {
- var s4 = dv.ToString(cfc, CultureInfo.InvariantCulture).Trim();
- return s4;
- }
- }
- private static bool IsFormatCodeForDate(string formatCode)
- {
- if (formatCode == "General")
- return false;
- return formatCode.Contains("m") ||
- formatCode.Contains("d") ||
- formatCode.Contains("y") ||
- formatCode.Contains("h") ||
- formatCode.Contains("s") ||
- formatCode.Contains("AM") ||
- formatCode.Contains("PM");
- }
- }
- }
|