Skip to content
Merged
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
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,15 @@ _ = smart.Format({0:cond:|No|\t|Yes|}", 1);
// Result: "|Yes|"
```

c) ChooseFormatter [#253](https://github.com/axuno/SmartFormat/pull/253)

Modified `ChooseFormatter` case-sensitivity for option strings. This modification is compatible with v2.

* `bool` and `null` as string: always case-insensitive
* using `SmartSettings.CaseSensitivity` unless overridden with `ChooseFormatter.CaseSensitivity`
* option strings comparison is culture-aware


v2.7.2
===
* **Fixed**: `ConditionalFormatter` processes unsigned numbers in arguments correctly.
Expand Down
35 changes: 28 additions & 7 deletions src/SmartFormat.Tests/Extensions/ChooseFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,22 @@ public void Choose_With_Changed_SplitChar()
Assert.That(result, Is.EqualTo("|two|"));
}

[TestCase("{0:choose(true|True):one|two|default}", true, "two")]
[TestCase("{0:choose(true|TRUE):one|two|default}", true, "default")]
[TestCase("{0:choose(string|String):one|two|default}", "String", "two")]
[TestCase("{0:choose(string|STRING):one|two|default}", "String", "default")]
[TestCase("{0:choose(ignore|Ignore):one|two|default}", SmartFormat.Core.Settings.FormatErrorAction.Ignore, "two")]
[TestCase("{0:choose(ignore|IGNORE):one|two|default}", SmartFormat.Core.Settings.FormatErrorAction.Ignore, "default")]
public void Choose_should_be_case_sensitive(string format, object arg0, string expectedResult)
// bool and null args: always case-insensitive
[TestCase("{0:choose(true|false):one|two|default}", false, true, "one")]
[TestCase("{0:choose(True|FALSE):one|two|default}", false, false, "two")]
[TestCase("{0:choose(null):is null|default}", false, default, "is null")]
[TestCase("{0:choose(NULL):is null|default}", false, default, "is null")]
// strings
[TestCase("{0:choose(string|String):one|two|default}", true, "String", "two")]
[TestCase("{0:choose(string|STRING):one|two|default}", true, "String", "default")]
// Enum
[TestCase("{0:choose(ignore|Ignore):one|two|default}", true, FormatErrorAction.Ignore, "two")]
[TestCase("{0:choose(ignore|IGNORE):one|two|default}", true, FormatErrorAction.Ignore, "default")]
public void Choose_should_be_case_sensitive(string format, bool caseSensitive, object arg0, string expectedResult)
{
var smart = Smart.CreateDefaultSmartFormat();
smart.GetFormatterExtension<ChooseFormatter>()!.CaseSensitivity =
caseSensitive ? CaseSensitivityType.CaseSensitive : CaseSensitivityType.CaseInsensitive;
Assert.AreEqual(expectedResult, smart.Format(format, arg0));
}

Expand Down Expand Up @@ -133,5 +140,19 @@ public void May_Contain_Nested_Choose_Formats(int? nullableInt, int valueIfNull,

Assert.That(result, Is.EqualTo(expected));
}

[Test, Description("Case-insensitive option string comparison")]
public void Choose_Should_Use_CultureInfo_For_Option_Strings()
{
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
var smart = Smart.CreateDefaultSmartFormat();
smart.GetFormatterExtension<ChooseFormatter>()!.CaseSensitivity = CaseSensitivityType.CaseInsensitive;

var result1 = smart.Format(CultureInfo.GetCultureInfo("de"), "{0:choose(ä|ü):umlautA|umlautU}", "Ä");
var result2 = smart.Format(CultureInfo.GetCultureInfo("de"), "{0:choose(ä|ü):umlautA|umlautU}", "ä");

Assert.That(result1, Is.EqualTo("umlautA"));
Assert.That(result2, Is.EqualTo("umlautA"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void Should_Use_Existing_Localized_Format()
_ = smart.Format("{:L(es):WeTranslateText}");
var result = smart.Format("{:L(es):WeTranslateText}");

Assert.That(locFormatter!.LocalizedFormatCache!.Keys.Contains(result), Is.True);
Assert.That(locFormatter!.LocalizedFormatCache!.ContainsKey(result), Is.True);
}

[TestCase("{:L():WeTranslateText}", "Traducimos el texto", "es")]
Expand Down Expand Up @@ -205,4 +205,4 @@ public void Combine_With_PluralLocalizationFormatter(string format, int count, s
Assert.That(actual, Is.EqualTo(expected));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void Add_And_Get_Items()
Assert.That(pvs[groupName1], Is.EqualTo(vg1));
Assert.That(pvs[groupName2], Is.EqualTo(vg2));
Assert.That(pvs.Keys.Count, Is.EqualTo(2));
Assert.That(pvs.Keys.Contains(groupName1));
Assert.That(pvs.ContainsKey(groupName1));
Assert.That(pvs.Values.Count, Is.EqualTo(2));
Assert.That(pvs.Values.Contains(vg1));
Assert.That(pvs.Values.Contains(vg2));
Expand Down
2 changes: 1 addition & 1 deletion src/SmartFormat.Tests/Extensions/VariablesGroupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void Add_And_Get_Items()
Assert.That((int) vg[var1Name].GetValue()!, Is.EqualTo(1234));
Assert.That((string) vg[var2Name].GetValue()!, Is.EqualTo("theValue"));
Assert.That(vg.Keys.Count, Is.EqualTo(3));
Assert.That(vg.Keys.Contains(var1Name));
Assert.That(vg.ContainsKey(var1Name));
Assert.That(vg.Values.Count, Is.EqualTo(3));
Assert.That(vg.Values.Contains(var1));
Assert.That(vg.Values.Contains(var2));
Expand Down
57 changes: 52 additions & 5 deletions src/SmartFormat/Extensions/ChooseFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using SmartFormat.Core.Extensions;
using SmartFormat.Core.Parsing;
using SmartFormat.Core.Settings;

namespace SmartFormat.Extensions
{
Expand All @@ -14,6 +16,8 @@ namespace SmartFormat.Extensions
/// </summary>
public class ChooseFormatter : IFormatter
{
private CultureInfo? _cultureInfo;

/// <summary>
/// Gets or sets the character used to split the option text literals.
/// </summary>
Expand Down Expand Up @@ -49,20 +53,19 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
$"Formatter named '{formattingInfo.Placeholder?.FormatterName}' requires at least 2 format options.");
}

_cultureInfo = formattingInfo.FormatDetails.Provider as CultureInfo ?? CultureInfo.CurrentUICulture;

var chosenFormat = DetermineChosenFormat(formattingInfo, formats, chooseOptions);

formattingInfo.FormatAsChild(chosenFormat, formattingInfo.CurrentValue);

return true;
}

private static Format DetermineChosenFormat(IFormattingInfo formattingInfo, IList<Format> choiceFormats,
private Format DetermineChosenFormat(IFormattingInfo formattingInfo, IList<Format> choiceFormats,
string[] chooseOptions)
{
var currentValue = formattingInfo.CurrentValue;
var currentValueString = currentValue == null ? "null" : currentValue.ToString();

var chosenIndex = Array.IndexOf(chooseOptions, currentValueString);
var chosenIndex = GetChosenIndex(formattingInfo, chooseOptions, out var currentValueString);

// Validate the number of formats:
if (choiceFormats.Count < chooseOptions.Length)
Expand All @@ -80,5 +83,49 @@ private static Format DetermineChosenFormat(IFormattingInfo formattingInfo, ILis
var chosenFormat = choiceFormats[chosenIndex];
return chosenFormat;
}

private int GetChosenIndex(IFormattingInfo formattingInfo, string[] chooseOptions, out string currentValueString)
{
string valAsString;

// null and bool types are always case-insensitive
switch (formattingInfo.CurrentValue)
{
case null:
valAsString = currentValueString = "null";
return Array.FindIndex(chooseOptions,
t => t.Equals(valAsString, StringComparison.OrdinalIgnoreCase));
case bool boolVal:
valAsString = currentValueString = boolVal.ToString();
return Array.FindIndex(chooseOptions,
t => t.Equals(valAsString, StringComparison.OrdinalIgnoreCase));
}

valAsString = currentValueString = formattingInfo.CurrentValue.ToString();

return Array.FindIndex(chooseOptions,
t => AreEqual(t, valAsString, formattingInfo.FormatDetails.Settings.CaseSensitivity));
}

private bool AreEqual(string s1, string s2, CaseSensitivityType caseSensitivityFromSettings)
{
System.Diagnostics.Debug.Assert(_cultureInfo is not null);
var culture = _cultureInfo!;

var toUse = caseSensitivityFromSettings == CaseSensitivity
? caseSensitivityFromSettings
: CaseSensitivity;

return toUse == CaseSensitivityType.CaseSensitive
? culture.CompareInfo.Compare(s1, s2, CompareOptions.None) == 0
: culture.CompareInfo.Compare(s1, s2, CompareOptions.IgnoreCase) == 0;
}

/// <summary>
/// Sets or gets the <see cref="CaseSensitivityType"/> for option strings.
/// Defaults to <see cref="CaseSensitivityType.CaseSensitive"/>.
/// Comparison of option strings is culture-aware.
/// </summary>
public CaseSensitivityType CaseSensitivity { get; set; } = CaseSensitivityType.CaseSensitive;
}
}