diff --git a/Navferty.Common/Extensions/StringExtensions.cs b/Navferty.Common/Extensions/StringExtensions.cs index d63c277..5b14629 100644 --- a/Navferty.Common/Extensions/StringExtensions.cs +++ b/Navferty.Common/Extensions/StringExtensions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; @@ -6,6 +7,7 @@ namespace Navferty.Common { + [DebuggerStepThrough] public static class StringExtensions { //private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); @@ -25,12 +27,20 @@ public static class StringExtensions : newValue.Trim(); return newValue; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CountChars(this string value, char c) - { - return value.Count(x => x == c); - } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CountChars(this string value, char c) + => value.Count(x => x == c); + + /// Removes only ASCII Space char (0x32) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string RemoveSpacesFast(this string source) + => source.Replace(" ", string.Empty); + + /// Removes all Unicode character which is categorized as white space. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string RemoveSpacesEx(this string source) + => string.Concat(source.Where(c => !char.IsWhiteSpace(c))); } } diff --git a/NavfertyExcelAddIn.UnitTests/ParseNumerics/DecimalParserTests.cs b/NavfertyExcelAddIn.UnitTests/ParseNumerics/DecimalParserTests.cs index 939df4e..67ad989 100644 --- a/NavfertyExcelAddIn.UnitTests/ParseNumerics/DecimalParserTests.cs +++ b/NavfertyExcelAddIn.UnitTests/ParseNumerics/DecimalParserTests.cs @@ -9,27 +9,45 @@ namespace NavfertyExcelAddIn.UnitTests.ParseNumerics [TestClass] public class DecimalParserTests { - [DataRow("0", 0)] - [DataRow("123", 123)] - [DataRow("1.1", 1.1)] - [DataRow("1,1", 1.1)] - [DataRow("1 000", 1000)] - [DataRow("12 00 00 . 12", 120000.12)] - [DataRow("0.1", 0.1)] - [DataRow(".123", 0.123)] - [DataRow("10.000.000", 10000000)] - [DataRow("10.000,12", 10000.12)] - [DataRow("12,345.12", 12345.12)] - [DataRow("12,345,000", 12345000)] - [DataRow("1E6", 1000000)] - [DataRow("1.2E3", 1200)] - [DataRow("-1.3E-5", -0.000013)] + [DataRow("0", 0, null)] + [DataRow("123", 123, null)] + [DataRow("1.1", 1.1, null)] + [DataRow("1,1", 1.1, null)] + [DataRow("1 000", 1000, null)] + [DataRow("12 00 00 . 12", 120000.12, null)] + [DataRow("0.1", 0.1, null)] + [DataRow(".123", 0.123, null)] + [DataRow("10.000.000", 10000000, null)] + [DataRow("10.000,12", 10000.12, null)] + [DataRow("12,345.12", 12345.12, null)] + [DataRow("12,345,000", 12345000, null)] + [DataRow("1E6", 1000000, null)] + [DataRow("1.2E3", 1200, null)] + [DataRow("-1.3E-5", -0.000013, null)] + [DataRow("$5666", 5666, "$")] + [DataRow("5666$", 5666, "$")] + [DataRow("$56.66", 56.66, "$")] + [DataRow("56.66$", 56.66, "$")] + [DataRow("$56,66", 56.66, "$")] + [DataRow("56,66$", 56.66, "$")] + [DataRow("₽5666", 5666, "₽")] + [DataRow("5666₽", 5666, "₽")] + [DataRow("₽56.66", 56.66, "₽")] + [DataRow("56.66₽", 56.66, "₽")] + [DataRow("₽56,66", 56.66, "₽")] + [DataRow("56,66₽", 56.66, "₽")] + [DataRow("£5666", 5666, "£")] + [DataRow("5666£", 5666, "£")] + [DataRow("£56.66", 56.66, "£")] + [DataRow("56.66£", 56.66, "£")] + [DataRow("£56.66", 56.66, "£")] + [DataRow("56.66£", 56.66, "£")] [TestMethod] - public void ParseDecimal_Success(string sourceValue, double targetDoubleValue) - { - // DataRow can't into decimals - they are not primitive type - var targetValue = Convert.ToDecimal(targetDoubleValue); - + public void ParseDecimal_Success(string sourceValue, double targetDoubleValue, string currencySymbol) + { + var targetValue = new NumericParseResult(targetDoubleValue, currencySymbol); + // DataRow can't into decimals - they are not primitive type + //var targetValue = Convert.ToDecimal(targetDoubleValue); Assert.AreEqual(targetValue, sourceValue.ParseDecimal()); } @@ -37,9 +55,25 @@ public void ParseDecimal_Success(string sourceValue, double targetDoubleValue) public void ParseDecimal_MissingValue() { var input = "no value"; - - Assert.AreEqual(null, input.ParseDecimal()); + Assert.AreEqual(null, input.ParseDecimal()); } + [TestMethod] + public void ParseDecimal_UnknownCurrency() + { + var input = "56.66%"; + Assert.AreEqual(null, input.ParseDecimal()); + } + + [TestMethod] + public void ParseDecimal_MultipleCurrencyInOneString() + { + Assert.AreEqual(null, "56.66£₽".ParseDecimal()); + Assert.AreEqual(null, "56.66₽£".ParseDecimal()); + Assert.AreEqual(null, "₽£56.66".ParseDecimal()); + } + + + } } diff --git a/NavfertyExcelAddIn.UnitTests/ParseNumerics/NumericParserTests.cs b/NavfertyExcelAddIn.UnitTests/ParseNumerics/NumericParserTests.cs index dfdfa30..7665f6e 100644 --- a/NavfertyExcelAddIn.UnitTests/ParseNumerics/NumericParserTests.cs +++ b/NavfertyExcelAddIn.UnitTests/ParseNumerics/NumericParserTests.cs @@ -25,7 +25,7 @@ public void BeforeEachTest() } [TestMethod] - public void ParsedSuccessfully() // TODO naming + public void ParsedSuccessfully() // TODO naming { var selection = new Mock(MockBehavior.Strict); var values = new object[,] { { 1, "1", "abc" }, { "123.123", "321 , 321", null } }; @@ -46,9 +46,17 @@ public void ParsedSuccessfully() // TODO naming ws.Setup(x => x.Parent).Returns(wb.Object); var areas = new Mock(MockBehavior.Strict); - areas.Setup(x => x.GetEnumerator()).Returns(new[] { selection.Object }.GetEnumerator()); - selection.Setup(x => x.Areas).Returns(areas.Object); - + areas.Setup(x => x.GetEnumerator()).Returns(new[] { selection.Object }.GetEnumerator()); + selection.Setup(x => x.Areas).Returns(areas.Object); + + + var cells = new Mock(MockBehavior.Default); + cells.Setup(x => x.GetEnumerator()).Returns(new[] { selection.Object }.GetEnumerator()); + //selection.SetupSet(x => x.get_Cells = It.Is(z => VerifyParsed(z))); + //selection.Setup(x => x.Cells).Returns(cells.Object.GetEnumerator); + + //areas.Setup(x => x.ce .Cells).Returns(cells.Object); + NumericParser.Parse(selection.Object); } diff --git a/NavfertyExcelAddIn/Commons/EnumerableExtensions.cs b/NavfertyExcelAddIn/Commons/EnumerableExtensions.cs index 0cf5f08..e70a145 100644 --- a/NavfertyExcelAddIn/Commons/EnumerableExtensions.cs +++ b/NavfertyExcelAddIn/Commons/EnumerableExtensions.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; - +using System.Runtime.CompilerServices; + using Autofac; using Microsoft.Office.Interop.Excel; @@ -17,41 +18,55 @@ public static class EnumerableExtensions private static readonly UndoManager undoManager = NavfertyRibbon.Container.Resolve(); - private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); - - public static void ForEach(this IEnumerable source, Action action) + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + + /// null-safe ForEach + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForEach(this IEnumerable? source, Action? action) { - foreach (T element in source) + foreach (T element in source.OrEmptyIfNull()) { - action(element); + action?.Invoke(element); } - } - - public static void ForEachCell(this Range range, Action action) - { - // TODO rewrite to use less read-write calls to interop (like Range.Value) - range.Cast().ForEach(action); - } - - public static void ApplyForEachCellOfType(this Range range, Func transform) + } + + /// Null-safe plug for loops + /// If source != null, return source. If source == null, returns empty Enumerable, without null reference exception + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable OrEmptyIfNull(this IEnumerable source) + => source ?? Enumerable.Empty(); + + /// null-safe ForEachCell + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForEachCell(this Range? range, Action? action) + { + // TODO rewrite to use less read-write calls to interop (like Range.Value) (may be use try/finally with selection.Worksheet.EnableCalculation = false/true?; + range?.Cast().ForEach(action); + } + + /// null-safe ApplyForEachCellOfType + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ApplyForEachCellOfType(this Range? range, Func? transform) { + if (range == null || transform == null) return; logger.Debug($"Apply transformation to range '{range.GetRelativeAddress()}' on worksheet '{range.Worksheet.Name}'"); - undoManager.StartNewAction(range); foreach (Range area in range.Areas) { ApplyToArea(area, transform); } - } - - private static void ApplyToArea(Range range, Func transform) + } + + /// null-safe ApplyToArea + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ApplyToArea(Range? range, Func transform) { - var rangeValue = range.Value; + var rangeValue = range?.Value; if (rangeValue is null) return; - if (rangeValue is TIn currentValue) + if (rangeValue is TIn currentValue)//single cell { var newValue = transform(currentValue); range.Value = newValue; @@ -64,15 +79,17 @@ private static void ApplyToArea(Range range, Func transfor }; undoManager.PushUndoItem(undoItem); return; - } - - // minimize number of COM calls to excel + } + + // minimize number of COM calls to excel if (!(rangeValue is object[,] values)) - return; - - int upperI = values.GetUpperBound(0); // Rows - int upperJ = values.GetUpperBound(1); // Columns - + return; + + //area of cells + + int upperI = values.GetUpperBound(0); // Rows + int upperJ = values.GetUpperBound(1); // Columns + var isChanged = false; var oldValues = (object[,])values.Clone(); @@ -87,7 +104,7 @@ private static void ApplyToArea(Range range, Func transfor if (value is TIn s) { var newValue = transform(s); - if ((object)newValue != value) // TODO check boxing time on million values + if ((object)newValue != value) // TODO check boxing time on million values { isChanged = true; values[i, j] = newValue; @@ -110,6 +127,49 @@ private static void ApplyToArea(Range range, Func transfor Width = upperJ }); } - } - } -} + } + + /// null-safe ApplyForEachCellOfType, allow acces to Range object from transform func + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ApplyForEachCellOfType2(this Range? range, Func? transform) + { + if (range == null || transform == null) return; + + logger.Debug($"Apply transformation to range '{range.GetRelativeAddress()}' on worksheet '{range.Worksheet.Name}'"); + + undoManager.StartNewAction(range); + + foreach (Range area in range.Areas) + { + ApplyToArea2(area, transform); + } + } + + // TODO check boxing time on million values + /// null-safe ApplyToArea, allow acces to Range object from transform func may be slower than Old + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ApplyToArea2(Range? area, Func transform) + { + //try { if (null == range || null == range.Cells) return; } catch { return; }//TODO: Just for Test cases, remove catch and modify tests (range.Cells) + area?.Cells?.ForEachCell(cell => + { + var cellValue = cell.Value; + if ((cellValue is null) || (cellValue is not TIn currentValue)) return; + + // TODO transform func may change format of cell, and we need to allow undo this, but set/restore cell formating has so weird api... + var newValue = transform(currentValue, cell); + if (null == newValue || newValue.Equals(currentValue)) return;//value did not changed or not parsed + cell.Value = newValue; + var undoItem = new UndoItem + { + OldValue = currentValue, + NewValue = newValue, + ColumnIndex = cell.Column, + RowIndex = cell.Row + }; + undoManager.PushUndoItem(undoItem); + }); + } + } +} + diff --git a/NavfertyExcelAddIn/NavfertyExcelAddIn.csproj b/NavfertyExcelAddIn/NavfertyExcelAddIn.csproj index be280f2..3269ff1 100644 --- a/NavfertyExcelAddIn/NavfertyExcelAddIn.csproj +++ b/NavfertyExcelAddIn/NavfertyExcelAddIn.csproj @@ -240,6 +240,7 @@ + diff --git a/NavfertyExcelAddIn/ParseNumerics/DecimalParser.cs b/NavfertyExcelAddIn/ParseNumerics/DecimalParser.cs index bda0ef4..d0de650 100644 --- a/NavfertyExcelAddIn/ParseNumerics/DecimalParser.cs +++ b/NavfertyExcelAddIn/ParseNumerics/DecimalParser.cs @@ -1,99 +1,125 @@ -using System.Globalization; +using System; +using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; using Navferty.Common; -namespace NavfertyExcelAddIn.ParseNumerics +namespace NavfertyExcelAddIn.ParseNumerics { - /// - /// Parser implemented in https://github.com/navferty/NumericParser - /// - public static class DecimalParser - { - private static readonly Regex SpacesPattern = new(@"\s"); - private static readonly Regex DecimalPattern = new(@"[\d\.\,\s]*"); - private static readonly Regex ExponentPattern = new(@"[-+]?\d*\.?\d+[eE][-+]?\d+"); - - public static decimal? ParseDecimal(this string value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return null; - } - - var v = SpacesPattern.Replace(value, match => string.Empty); - - if (ExponentPattern.IsMatch(v)) - { - return v.TryParseExponent(); - } - - if (!DecimalPattern.IsMatch(value)) - { - return null; - } - - if (v.Contains(",") && v.Contains(".")) - { - var last = v.LastIndexOfAny(new[] { ',', '.' }); - var c = v[last]; - return v.CountChars(c) == 1 - ? v.TryParse(c == '.' ? Format.Dot : Format.Comma) - : null; - } - - if (v.Contains(",")) - { - return v.CountChars(',') == 1 - ? v.TryParse(Format.Comma) - : v.TryParse(Format.Dot); - } - - if (v.Contains(".")) - { - return v.CountChars('.') == 1 - ? v.TryParse(Format.Dot) - : v.TryParse(Format.Comma); - } - - return v.TryParse(Format.Dot); - } - - private static decimal? TryParseExponent(this string value) - { - return decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out decimal result) - ? result - : (decimal?)null; - } - - private static decimal? TryParse(this string value, Format info) - { - var formatInfo = (NumberFormatInfo)NumberFormatInfo.InvariantInfo.Clone(); - - if (info == Format.Comma) - { - formatInfo.CurrencyDecimalSeparator = ","; - formatInfo.CurrencyGroupSeparator = "."; - formatInfo.NumberDecimalSeparator = ","; - formatInfo.NumberGroupSeparator = "."; - } - else - { - formatInfo.CurrencyDecimalSeparator = "."; - formatInfo.CurrencyGroupSeparator = ","; - } - // добавить тест-кейсов на формат валют - //formatInfo.CurrencyNegativePattern = 8; - //formatInfo.CurrencyPositivePattern = 3; - return decimal.TryParse(value, NumberStyles.Currency, formatInfo, out decimal result) - ? result - : (decimal?)null; - } - - private enum Format - { - Dot, - Comma - } - } -} + /// + /// Parser implemented in https://github.com/navferty/NumericParser + /// + public static class DecimalParser + { + //private static readonly Regex SpacesPattern = new(@"\s"); + private static readonly Regex DecimalPattern = new(@"[\d\.\,\s]+"); + private static readonly Regex ExponentPattern = new(@"[-+]?\d*\.?\d+[eE][-+]?\d+"); + + public static NumericParseResult? ParseDecimal(this string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } + + var v = value.RemoveSpacesEx(); + + if (ExponentPattern.IsMatch(v)) + return new NumericParseResult(v.TryParseExponent()); + + if (!DecimalPattern.IsMatch(value)) + return null;//Doesn't look like a number at all. + + //Determine the decimal separator . or , + if (v.Contains(",") && v.Contains(".")) + { + var last = v.LastIndexOfAny(new[] { ',', '.' }); + var c = v[last]; + return v.CountChars(c) == 1 + ? v.TryParse(c == '.' ? Format.Dot : Format.Comma) + : null; + } + + if (v.Contains(",")) + { + return v.CountChars(',') == 1 + ? v.TryParse(Format.Comma) + : v.TryParse(Format.Dot); + } + + if (v.Contains(".")) + { + return v.CountChars('.') == 1 + ? v.TryParse(Format.Dot) + : v.TryParse(Format.Comma); + } + + return v.TryParse(Format.Dot); + } + + private static double? TryParseExponent(this string value) + { + return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out double result) + ? result + : null; + } + + private static Lazy _allCurrencySymbolsCacheLazy = new(() + => CultureInfo.GetCultures(CultureTypes.AllCultures) + .Select(ci => ci.NumberFormat.CurrencySymbol) + .Distinct() + .Where(cur => !string.IsNullOrWhiteSpace(cur)) + .ToArray()); + + private static NumericParseResult? TryParse(this string value, Format info) + { + var formatInfo = (NumberFormatInfo)NumberFormatInfo.InvariantInfo.Clone(); + + if (info == Format.Comma) + { + formatInfo.CurrencyDecimalSeparator = ","; + formatInfo.CurrencyGroupSeparator = "."; + formatInfo.NumberDecimalSeparator = ","; + formatInfo.NumberGroupSeparator = "."; + } + else + { + formatInfo.CurrencyDecimalSeparator = "."; + formatInfo.CurrencyGroupSeparator = ","; + } + // добавить тест-кейсов на формат валют + //formatInfo.CurrencyNegativePattern = 8; + //formatInfo.CurrencyPositivePattern = 3; + + var valueParsed = double.TryParse(value, NumberStyles.Currency, formatInfo, out double result); + if (valueParsed) return new NumericParseResult(result);//Parsed without our help + + //.TryParse cannot parse a string with a CurrencySymbol and number format not from the current user culture + //Therefore, we help to parse the string with an arbitrary currency manualy... + + //detect how many different currency symbols contains source string. + var currenciesInValue = _allCurrencySymbolsCacheLazy.Value.Where(cur => value.Contains(cur));//.ToArray(); + if (currenciesInValue.Count() == 1)// TODO: If the string contains several different currency symbols, do not convert, because currency priority is not clear + { + var curSymb = currenciesInValue.First(); + //Remove found currencySymbol from source string + var valueWithoutCurrencySymbol = value.Replace(curSymb, string.Empty); + valueParsed = double.TryParse(valueWithoutCurrencySymbol, NumberStyles.Currency, formatInfo, out result); + if (valueParsed) + { + //Debug.WriteLine($"Parsed value: '{value}, valueWithoutCurrencySymbol: {valueWithoutCurrencySymbol}', result: {result}, currency: {curSymb}"); + return new NumericParseResult(result, curSymb); + } + //It was not possible to parse the line, even after removing the currencySymbol, most likely this is not about money at all... + } + return null;//Not found any currency symbols, or found more than one, or even not number... + } + + private enum Format + { + Dot, + Comma + } + } +} diff --git a/NavfertyExcelAddIn/ParseNumerics/NumericParseResult.cs b/NavfertyExcelAddIn/ParseNumerics/NumericParseResult.cs new file mode 100644 index 0000000..7ea89dd --- /dev/null +++ b/NavfertyExcelAddIn/ParseNumerics/NumericParseResult.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NavfertyExcelAddIn.ParseNumerics +{ + public struct NumericParseResult + { + private static readonly string currencySymbolFromOSUserLocale = CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol; + private static readonly string currencySymbolRu = CultureInfo.GetCultureInfo("ru").NumberFormat.CurrencySymbol; + + public readonly double? ConvertedValue = null; + public readonly string? Currency = null; + + public NumericParseResult(double? value, string? curr = null) + { + ConvertedValue = value; + Currency = curr; + } + + public bool IsMoney + => !string.IsNullOrEmpty(Currency); + + public bool IsCurrencyFromCurrentCulture() + => (currencySymbolFromOSUserLocale == Currency); + + public bool IsCurrencyFromRU() + => (currencySymbolRu == Currency); + + /* + + public static bool operator ==(NumericParseResult obj1, NumericParseResult obj2) + { + if ((obj1 is null) && (obj2 is null)) return true; + if (obj1 is null) return false; + + return obj1.ConvertedValue == obj2.ConvertedValue + && obj1.Currency == obj2.Currency + && obj1.IsMoney == obj2.IsMoney; + } + + public static bool operator !=(NumericParseResult obj1, NumericParseResult obj2) + { + if ((obj1 is null) && (obj2 is null)) return false; + if (obj1 is null) return true; + + return !(obj1.ConvertedValue == obj2.ConvertedValue + && obj1.Currency == obj2.Currency + && obj1.IsMoney == obj2.IsMoney); + } + + public override bool Equals(object obj) + => !(obj is null) && (obj.GetType() == typeof(NumericParseResult) && (this == obj as NumericParseResult)); + + */ + + } +} diff --git a/NavfertyExcelAddIn/ParseNumerics/NumericParser.cs b/NavfertyExcelAddIn/ParseNumerics/NumericParser.cs index 5a45f29..145cc63 100644 --- a/NavfertyExcelAddIn/ParseNumerics/NumericParser.cs +++ b/NavfertyExcelAddIn/ParseNumerics/NumericParser.cs @@ -1,24 +1,59 @@ -using System; - -using Microsoft.Office.Interop.Excel; - -using NavfertyExcelAddIn.Commons; - -namespace NavfertyExcelAddIn.ParseNumerics -{ - public class NumericParser : INumericParser - { - public void Parse(Range selection) +using System; +using System.Diagnostics; + +using Microsoft.Office.Interop.Excel; + +using NavfertyExcelAddIn.Commons; + +namespace NavfertyExcelAddIn.ParseNumerics +{ + public class NumericParser : INumericParser + { + public void Parse(Range selection) { - selection.ApplyForEachCellOfType( - value => - { - var newValue = value.ParseDecimal(); - if (newValue.HasValue) - // Excel stores numerics as Double - return (object)Convert.ToDouble(newValue); - return (object)value; - }); - } - } -} + //Формат положительных;Формат отрицательных;Формат нулей;Формат текста + const string EXCEL_CURRENCY_FORMAT_TEMPLATE_RUS = @"_-* #,##0.00 {CUR}_-;-* #,##0.00 {CUR}_-;_-* ""-""?? {CUR}_-;_-@_-"; + const string EXCEL_CURRENCY_FORMAT_TEMPLATE_LAT = @"_-{CUR}* # ##0.00_-;-{CUR}* # ##0.00_-;_-{CUR}* ""-""??_-;_-@_-"; + const string CURRENCY_TEMPLATE = @"{CUR}"; + + bool autoCalcEnabled = false; + try { autoCalcEnabled = selection.Worksheet.EnableCalculation; } catch { } + if (autoCalcEnabled) selection.Worksheet.EnableCalculation = false; + +#if DEBUG + var sw = new Stopwatch(); + sw.Start(); +#endif + try + { + + selection.ApplyForEachCellOfType2( + (value, cell) => + { + var pdResult = value.ParseDecimal(); + if (!pdResult.HasValue || !pdResult.Value.ConvertedValue.HasValue) + return (object)value; + + var npr = pdResult.Value; + //Parsed Ok... + if (pdResult.Value.IsMoney) + { + string currencyFormat = npr.IsCurrencyFromRU() ? EXCEL_CURRENCY_FORMAT_TEMPLATE_RUS : EXCEL_CURRENCY_FORMAT_TEMPLATE_LAT; + string curSymFmt = @"[$" + npr.Currency + @"]"; + cell.NumberFormat = currencyFormat.Replace(CURRENCY_TEMPLATE, curSymFmt); + } + return (object)Convert.ToDouble(npr.ConvertedValue.Value);// Excel stores numerics as Double + }); + } + finally + { +#if DEBUG + sw.Stop(); + Debug.WriteLine($"NumericParser.Parse() {sw.Elapsed.TotalMilliseconds}ms"); +#endif + + if (autoCalcEnabled) selection.Worksheet.EnableCalculation = autoCalcEnabled;//Restart sheet autorecalc + } + } + } +}