Skip to content
Open

iss29 #104

Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 56 additions & 22 deletions NavfertyExcelAddIn.UnitTests/ParseNumerics/DecimalParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,71 @@ 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());
}

[TestMethod]
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());
}



}
}
16 changes: 12 additions & 4 deletions NavfertyExcelAddIn.UnitTests/ParseNumerics/NumericParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void BeforeEachTest()
}

[TestMethod]
public void ParsedSuccessfully() // TODO naming
public void ParsedSuccessfully() // TODO naming
{
var selection = new Mock<Range>(MockBehavior.Strict);
var values = new object[,] { { 1, "1", "abc" }, { "123.123", "321 , 321", null } };
Expand All @@ -46,9 +46,17 @@ public void ParsedSuccessfully() // TODO naming
ws.Setup(x => x.Parent).Returns(wb.Object);

var areas = new Mock<Areas>(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<Range>(MockBehavior.Default);
cells.Setup(x => x.GetEnumerator()).Returns(new[] { selection.Object }.GetEnumerator());
//selection.SetupSet(x => x.get_Cells = It.Is<object[,]>(z => VerifyParsed(z)));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

просьба удалить неиспользуемый закомментированный код

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тесты недоделаны, немогу Range.Cells нормально в тесте сделать... тест не проходит....

//selection.Setup(x => x.Cells).Returns(cells.Object.GetEnumerator);

//areas.Setup(x => x.ce .Cells).Returns(cells.Object);

NumericParser.Parse(selection.Object);
}

Expand Down
126 changes: 93 additions & 33 deletions NavfertyExcelAddIn/Commons/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;

using System.Runtime.CompilerServices;

using Autofac;

using Microsoft.Office.Interop.Excel;
Expand All @@ -17,41 +18,55 @@ public static class EnumerableExtensions
private static readonly UndoManager undoManager =
NavfertyRibbon.Container.Resolve<UndoManager>();

private static readonly ILogger logger = LogManager.GetCurrentClassLogger();

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
private static readonly ILogger logger = LogManager.GetCurrentClassLogger();

/// <summary>null-safe ForEach </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<T>(this IEnumerable<T>? source, Action<T>? 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<Range> action)
{
// TODO rewrite to use less read-write calls to interop (like Range.Value)
range.Cast<Range>().ForEach(action);
}

public static void ApplyForEachCellOfType<TIn, TOut>(this Range range, Func<TIn, TOut> transform)
}

/// <summary>Null-safe plug for loops</summary>
/// <returns>If source != null, return source. If source == null, returns empty Enumerable, without null reference exception</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
=> source ?? Enumerable.Empty<T>();

/// <summary>null-safe ForEachCell</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEachCell(this Range? range, Action<Range>? 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<Range>().ForEach(action);
}

/// <summary>null-safe ApplyForEachCellOfType</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ApplyForEachCellOfType<TIn, TOut>(this Range? range, Func<TIn, TOut>? 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<TIn, TOut>(Range range, Func<TIn, TOut> transform)
}

/// <summary>null-safe ApplyToArea</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ApplyToArea<TIn, TOut>(Range? range, Func<TIn, TOut> 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;
Expand All @@ -64,15 +79,17 @@ private static void ApplyToArea<TIn, TOut>(Range range, Func<TIn, TOut> 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();

Expand All @@ -87,7 +104,7 @@ private static void ApplyToArea<TIn, TOut>(Range range, Func<TIn, TOut> 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;
Expand All @@ -110,6 +127,49 @@ private static void ApplyToArea<TIn, TOut>(Range range, Func<TIn, TOut> transfor
Width = upperJ
});
}
}
}
}
}

/// <summary>null-safe ApplyForEachCellOfType, allow acces to Range object from transform func</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ApplyForEachCellOfType2<TIn, TOut>(this Range? range, Func<TIn, Range, TOut>? 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
/// <summary>null-safe ApplyToArea, allow acces to Range object from transform func may be slower than Old</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ApplyToArea2<TIn, TOut>(Range? area, Func<TIn, Range, TOut> 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);
});
}
}
}

18 changes: 13 additions & 5 deletions NavfertyExcelAddIn/Commons/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Text.RegularExpressions;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;

using NLog;

Expand All @@ -9,19 +11,25 @@ public static class StringExtensions
private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
private static readonly Regex spacesRegex = new Regex("\\s+", RegexOptions.None);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string TrimSpaces(this string value)
{
if (string.IsNullOrWhiteSpace(value))
return null;

// replace any single or multiple space chars with single space
return null;
// replace any single or multiple space chars with single space
var newValue = spacesRegex.Replace(value, " ");

newValue = string.IsNullOrEmpty(newValue)
? null
: newValue.Trim();

return newValue;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CountChars(this string value, char c)
=> value.Count(x => x == c);

}
}
Loading