Skip to content
Open

iss29 #104

Show file tree
Hide file tree
Changes from all 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
26 changes: 18 additions & 8 deletions Navferty.Common/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System.Linq;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;

#nullable enable

namespace Navferty.Common
{
[DebuggerStepThrough]
public static class StringExtensions
{
//private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
Expand All @@ -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);

/// <summary>Removes only ASCII Space char (0x32)</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string RemoveSpacesFast(this string source)
=> source.Replace(" ", string.Empty);

/// <summary>Removes all Unicode character which is categorized as white space.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string RemoveSpacesEx(this string source)
=> string.Concat(source.Where(c => !char.IsWhiteSpace(c)));
}
}
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);
});
}
}
}

1 change: 1 addition & 0 deletions NavfertyExcelAddIn/NavfertyExcelAddIn.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@
<Compile Include="FindFormulaErrors\CVErrEnum.cs" />
<Compile Include="DataValidation\CellsValueValidator.cs" />
<Compile Include="DataValidation\IValidator.cs" />
<Compile Include="ParseNumerics\NumericParseResult.cs" />
<Compile Include="Transliterate\ICyrillicLettersReplacer.cs" />
<Compile Include="InteractiveRangeReport\InteractiveErrorItem.cs" />
<Compile Include="DataValidation\ICellsValueValidator.cs" />
Expand Down
Loading