mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-07-08 18:16:08 +00:00
The spreadsheet engine parsed typed input with the invariant culture while displaying values with the current culture, breaking the edit round-trip on non-en-US hosts and making comma-decimal entry (10,50) impossible. Workbook gains a Culture property (defaults to CurrentCulture) which the component stamps from its inherited Culture parameter. It drives: - Cell input parsing (CellData type inference), including day-month date handling for cultures where the group and date separators collide (de-DE) - Edit text and display rendering (GetValue, GetValueAsString, GetDisplayText) - Number format rendering - format codes stay canonical invariant tokens while separators, month names, and AM/PM designators follow the culture - Formula entry and display via the new FormulaLocalizer (Excel FormulaLocal semantics: ';' argument separators and ',' decimals in comma-decimal cultures, lenient comma acceptance where unambiguous, canonical invariant storage) - Dialog input (data validation, conditional format, filter) via shared conversion helpers on SpreadsheetDialogBase XLSX and CSV files read and write canonical invariant values regardless of the workbook culture, including autofit column widths. Malformed formulas now surface as error trees instead of an unhandled lexer exception. Number format parsing is cached (hard-bounded) since CellView reparsed per render. Includes localization demos for the Spreadsheet and Document Processing sections and culture test suites. Note: headless code on non-en-US hosts now parses string values with the host culture; set Workbook.Culture explicitly (or to InvariantCulture) for host-independent processing.
125 lines
4.6 KiB
C#
125 lines
4.6 KiB
C#
using System.Globalization;
|
|
using Xunit;
|
|
|
|
using Radzen.Documents.Spreadsheet;
|
|
namespace Radzen.Blazor.Spreadsheet.Tests;
|
|
|
|
public class FormulaLocalizerTests
|
|
{
|
|
private static readonly CultureInfo German = CultureInfo.GetCultureInfo("de-DE");
|
|
private static readonly CultureInfo Brazilian = CultureInfo.GetCultureInfo("pt-BR");
|
|
private static readonly CultureInfo English = CultureInfo.GetCultureInfo("en-US");
|
|
|
|
[Theory]
|
|
[InlineData("=SUM(A1,B1)", "=SUM(A1;B1)")]
|
|
[InlineData("=A1*1.5", "=A1*1,5")]
|
|
[InlineData("=.5", "=,5")]
|
|
[InlineData("=A1-.5", "=A1-,5")]
|
|
[InlineData("=5.5%", "=5,5%")]
|
|
[InlineData("=1.5E+3", "=1,5E+3")]
|
|
[InlineData("=IF(D6>1000,-100.5,0)", "=IF(D6>1000;-100,5;0)")]
|
|
[InlineData("=INDEX(I42:I46, MATCH(104, H42:H46, 0))", "=INDEX(I42:I46; MATCH(104; H42:H46; 0))")]
|
|
[InlineData("=SUM(A1,.5)", "=SUM(A1;,5)")]
|
|
public void ToLocalized_ConvertsSeparatorsForGerman(string invariant, string localized)
|
|
{
|
|
Assert.Equal(localized, FormulaLocalizer.ToLocalized(invariant, German));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("=SUM(A1;B1)", "=SUM(A1,B1)")]
|
|
[InlineData("=A1*1,5", "=A1*1.5")]
|
|
[InlineData("=,5", "=.5")]
|
|
[InlineData("=A1-,5", "=A1-.5")]
|
|
[InlineData("=5,5%", "=5.5%")]
|
|
[InlineData("=1,5E+3", "=1.5E+3")]
|
|
[InlineData("=SUM(A1;,5)", "=SUM(A1,.5)")]
|
|
public void ToInvariant_ConvertsSeparatorsForGerman(string localized, string invariant)
|
|
{
|
|
Assert.Equal(invariant, FormulaLocalizer.ToInvariant(localized, German));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("=SUM(A1,A2)", "=SUM(A1,A2)")]
|
|
[InlineData("=SUM(A1, 5)", "=SUM(A1, 5)")]
|
|
[InlineData("=SUM(A1:B2,5)", "=SUM(A1:B2,5)")]
|
|
public void ToInvariant_LenientCommaIsSeparatorWhereItCannotBeDecimal(string localized, string invariant)
|
|
{
|
|
Assert.Equal(invariant, FormulaLocalizer.ToInvariant(localized, German));
|
|
}
|
|
|
|
[Fact]
|
|
public void ToInvariant_DigitAdjacentCommaBindsAsDecimal()
|
|
{
|
|
Assert.Equal("=SUM(1.5)", FormulaLocalizer.ToInvariant("=SUM(1,5)", German));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("=IF(A1>1,\"a,b\",\"c;d\")", "=IF(A1>1;\"a,b\";\"c;d\")")]
|
|
[InlineData("='My,Sheet'!B2+1.5", "='My,Sheet'!B2+1,5")]
|
|
public void Transform_LeavesStringAndSheetLiteralsUntouched(string invariant, string localized)
|
|
{
|
|
Assert.Equal(localized, FormulaLocalizer.ToLocalized(invariant, German));
|
|
Assert.Equal(invariant, FormulaLocalizer.ToInvariant(localized, German));
|
|
}
|
|
|
|
[Fact]
|
|
public void Transform_LeavesBracketedSegmentsUntouched()
|
|
{
|
|
var formula = "=SUM(Table[[Col1],[Col2]])";
|
|
Assert.Equal(formula, FormulaLocalizer.ToLocalized(formula, German));
|
|
Assert.Equal(formula, FormulaLocalizer.ToInvariant(formula, German));
|
|
}
|
|
|
|
[Fact]
|
|
public void Transform_LeavesArrayLiteralsUntouched()
|
|
{
|
|
var formula = "={1,2,3}";
|
|
Assert.Same(formula, FormulaLocalizer.ToLocalized(formula, German));
|
|
Assert.Same(formula, FormulaLocalizer.ToInvariant(formula, German));
|
|
}
|
|
|
|
[Fact]
|
|
public void ToLocalized_ReturnsSameReferenceForDotDecimalCultures()
|
|
{
|
|
var formula = "=SUM(A1,B1)";
|
|
Assert.Same(formula, FormulaLocalizer.ToLocalized(formula, English));
|
|
Assert.Same(formula, FormulaLocalizer.ToLocalized(formula, CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("=SUM(A1,B1)")]
|
|
[InlineData("=A1*1.5")]
|
|
[InlineData("=IF(D6>1000,-100,0)")]
|
|
[InlineData("=VLOOKUP(103, H42:J46, 2, FALSE)")]
|
|
[InlineData("=ROUND(AVERAGE(E2:E9), 1)")]
|
|
[InlineData("=AGGREGATE(14,6,A1:A11,3)")]
|
|
[InlineData("=CONCAT(\"a\",\"b\",1.5)")]
|
|
[InlineData("=AVERAGEIF(A1:A2,\">1\")")]
|
|
[InlineData("=TEXT(A1,\"#,##0.00\")")]
|
|
[InlineData("=SUM(1, 2, 3) + IF(2>1, 10, 20)")]
|
|
public void RoundTrip_IsIdentityOverFormulaCorpus(string invariant)
|
|
{
|
|
Assert.Equal(invariant, FormulaLocalizer.ToInvariant(FormulaLocalizer.ToLocalized(invariant, German), German));
|
|
Assert.Equal(invariant, FormulaLocalizer.ToInvariant(FormulaLocalizer.ToLocalized(invariant, Brazilian), Brazilian));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("=SUM(1,5,2,5)")]
|
|
[InlineData("=SUM(1,5E3,10)")]
|
|
public void MalformedLocalizedInput_ProducesErrorTreeNotThrow(string localized)
|
|
{
|
|
var invariant = FormulaLocalizer.ToInvariant(localized, German);
|
|
var tree = FormulaParser.Parse(invariant);
|
|
Assert.NotEmpty(tree.Errors);
|
|
Assert.NotNull(tree.Root);
|
|
}
|
|
|
|
[Fact]
|
|
public void MalformedInvariantFormula_ProducesErrorTreeNotThrow()
|
|
{
|
|
var tree = FormulaParser.Parse("=1.5.2");
|
|
Assert.NotEmpty(tree.Errors);
|
|
Assert.NotNull(tree.Root);
|
|
}
|
|
}
|