Skip to content

Commit 21f4759

Browse files
committed
#132 Handle ambiguous abbreviation parsing by throwing exception
1 parent 91884ea commit 21f4759

File tree

5 files changed

+99
-21
lines changed

5 files changed

+99
-21
lines changed

UnitsNet.Tests/UnitSystemTests.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ private enum CustomUnit
7171
// ReSharper restore UnusedMember.Local
7272
}
7373

74+
private UnitSystem GetCachedUnitSystem()
75+
{
76+
var cultureInfo = CultureInfo.GetCultureInfo("en-US");
77+
var unitSystem = UnitSystem.GetCached(cultureInfo);
78+
return unitSystem;
79+
}
80+
7481
private static IEnumerable<object> GetUnitTypesWithMissingAbbreviations<TUnit>(string cultureName,
7582
IEnumerable<TUnit> unitValues)
7683
where TUnit : /*Enum constraint hack*/ struct, IComparable, IFormattable
@@ -161,12 +168,39 @@ public void DecimalPointDigitGroupingCultureFormatting(string culture)
161168
Assert.AreEqual("1.111 m", Length.FromMeters(1111).ToString(LengthUnit.Meter, new CultureInfo(culture)));
162169
}
163170

171+
[Test]
172+
public void Parse_UnambiguousUnitsDoesNotThrow()
173+
{
174+
var unitSystem = GetCachedUnitSystem();
175+
var unit = Volume.Parse("1 l");
176+
177+
Assert.AreEqual(Volume.FromLiters(1), unit);
178+
}
179+
180+
[Test]
181+
public void Parse_AmbiguousUnitsThrowsException()
182+
{
183+
var unitSystem = GetCachedUnitSystem();
184+
185+
// Act 1
186+
Assert.Throws<AmbiguousUnitParseException>( ()=>unitSystem.Parse<VolumeUnit>("tsp"));
187+
188+
// Act 2
189+
try
190+
{
191+
Volume.Parse("1 tsp");
192+
}
193+
catch (UnitsNetException e)
194+
{
195+
Assert.True(e.InnerException is AmbiguousUnitParseException);
196+
}
197+
}
198+
164199
[TestCase("m^2", Result = AreaUnit.SquareMeter)]
165200
[TestCase("cm^2", Result = AreaUnit.Undefined)]
166201
public AreaUnit Parse_ReturnsUnitMappedByCustomAbbreviationOrUndefined(string unitAbbreviationToParse)
167202
{
168-
CultureInfo cultureInfo = CultureInfo.GetCultureInfo("en-US");
169-
UnitSystem unitSystem = UnitSystem.GetCached(cultureInfo);
203+
var unitSystem = GetCachedUnitSystem();
170204
unitSystem.MapUnitToAbbreviation(AreaUnit.SquareMeter, "m^2");
171205

172206
return unitSystem.Parse<AreaUnit>(unitAbbreviationToParse);

UnitsNet/CustomCode/UnitSystem.cs

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,15 @@
2828

2929
// ReSharper disable once CheckNamespace
3030

31+
32+
3133
namespace UnitsNet
3234
{
35+
public class AbbreviationMap : Dictionary<string, List<int>>
36+
{
37+
38+
}
39+
3340
[PublicAPI]
3441
public partial class UnitSystem
3542
{
@@ -41,7 +48,7 @@ public partial class UnitSystem
4148
/// Per-unit-type dictionary of enum values by abbreviation. This is the inverse of
4249
/// <see cref="_unitTypeToUnitValueToAbbrevs" />.
4350
/// </summary>
44-
private readonly Dictionary<Type, Dictionary<string, int>> _unitTypeToAbbrevToUnitValue;
51+
private readonly Dictionary<Type, AbbreviationMap> _unitTypeToAbbrevToUnitValue;
4552

4653
/// <summary>
4754
/// Per-unit-type dictionary of abbreviations by enum value. This is the inverse of
@@ -71,7 +78,7 @@ public UnitSystem([CanBeNull] IFormatProvider cultureInfo = null)
7178

7279
Culture = cultureInfo;
7380
_unitTypeToUnitValueToAbbrevs = new Dictionary<Type, Dictionary<int, List<string>>>();
74-
_unitTypeToAbbrevToUnitValue = new Dictionary<Type, Dictionary<string, int>>();
81+
_unitTypeToAbbrevToUnitValue = new Dictionary<Type, AbbreviationMap>();
7582

7683
LoadDefaultAbbreviatons(cultureInfo);
7784
}
@@ -122,17 +129,33 @@ public TUnit Parse<TUnit>(string unitAbbreviation)
122129
where TUnit : /*Enum constraint hack*/ struct, IComparable, IFormattable
123130
{
124131
Type unitType = typeof (TUnit);
125-
Dictionary<string, int> abbrevToUnitValue;
132+
AbbreviationMap abbrevToUnitValue;
126133
if (!_unitTypeToAbbrevToUnitValue.TryGetValue(unitType, out abbrevToUnitValue))
127134
throw new NotImplementedException(
128135
$"No abbreviations defined for unit type [{unitType}] for culture [{Culture}].");
129136

130-
int unitValue;
131-
TUnit result = abbrevToUnitValue.TryGetValue(unitAbbreviation, out unitValue)
132-
? (TUnit) (object) unitValue
133-
: default(TUnit);
137+
List<int> unitValues;
138+
List<TUnit> units;
139+
140+
if (abbrevToUnitValue.TryGetValue(unitAbbreviation, out unitValues))
141+
{
142+
units = unitValues.Cast<TUnit>().Distinct().ToList();
143+
}
144+
else
145+
{
146+
units = new List<TUnit>();
147+
}
134148

135-
return result;
149+
switch (units.Count)
150+
{
151+
case 1:
152+
return units[0];
153+
case 0:
154+
return default(TUnit);
155+
default:
156+
var unitsCsv = String.Join(", ", units.Select(x => x.ToString()).ToArray());
157+
throw new AmbiguousUnitParseException($"Cannot parse '{unitAbbreviation}' since it could be either of these: {unitsCsv}");
158+
}
136159
}
137160

138161
[PublicAPI]
@@ -187,15 +210,17 @@ public void MapUnitToAbbreviation(Type unitType, int unitValue, [NotNull] params
187210
unitValueToAbbrev[unitValue] = existingAbbreviations.Concat(abbreviations).Distinct().ToList();
188211
foreach (string abbreviation in abbreviations)
189212
{
190-
Dictionary<string, int> abbrevToUnitValue;
213+
AbbreviationMap abbrevToUnitValue;
191214
if (!_unitTypeToAbbrevToUnitValue.TryGetValue(unitType, out abbrevToUnitValue))
192215
{
193-
abbrevToUnitValue = _unitTypeToAbbrevToUnitValue[unitType] =
194-
new Dictionary<string, int>();
216+
abbrevToUnitValue = _unitTypeToAbbrevToUnitValue[unitType] = new AbbreviationMap();
195217
}
196218

197219
if (!abbrevToUnitValue.ContainsKey(abbreviation))
198-
abbrevToUnitValue[abbreviation] = unitValue;
220+
{
221+
abbrevToUnitValue[abbreviation] = new List<int>();
222+
}
223+
abbrevToUnitValue[abbreviation].Add(unitValue);
199224
}
200225
}
201226

@@ -205,11 +230,11 @@ public bool TryParse<TUnit>(string unitAbbreviation, out TUnit unit)
205230
{
206231
Type unitType = typeof (TUnit);
207232

208-
Dictionary<string, int> abbrevToUnitValue;
209-
int unitValue;
233+
AbbreviationMap abbrevToUnitValue;
234+
List<int> unitValues;
210235

211236
if (!_unitTypeToAbbrevToUnitValue.TryGetValue(unitType, out abbrevToUnitValue) ||
212-
!abbrevToUnitValue.TryGetValue(unitAbbreviation, out unitValue))
237+
!abbrevToUnitValue.TryGetValue(unitAbbreviation, out unitValues))
213238
{
214239
if (IsDefaultCulture)
215240
{
@@ -221,8 +246,16 @@ public bool TryParse<TUnit>(string unitAbbreviation, out TUnit unit)
221246
return GetCached(DefaultCulture).TryParse(unitAbbreviation, out unit);
222247
}
223248

224-
unit = (TUnit) (object) unitValue;
225-
return true;
249+
var maps = (List<TUnit>) (object) unitValues;
250+
251+
switch (maps.Count)
252+
{
253+
case 1: unit = maps[0];
254+
return true;
255+
default:
256+
unit = default(TUnit);
257+
return false;
258+
}
226259
}
227260

228261
/// <summary>

UnitsNet/GeneratedCode/UnitSystem.Default.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1980,7 +1980,7 @@ private static readonly ReadOnlyCollection<UnitLocalization> DefaultLocalization
19801980
new CulturesForEnumValue((int) VolumeUnit.MetricTeaspoon,
19811981
new[]
19821982
{
1983-
new AbbreviationsForCulture("en-US", ""),
1983+
new AbbreviationsForCulture("en-US", "tsp", "t", "ts", "tspn", "t.", "ts.", "tsp.", "tspn.", "teaspoon"),
19841984
new AbbreviationsForCulture("ru-RU", ""),
19851985
new AbbreviationsForCulture("nb-NO", ""),
19861986
}),

UnitsNet/Scripts/UnitDefinitions/Volume.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@
359359
"Localization": [
360360
{
361361
"Culture": "en-US",
362-
"Abbreviations": []
362+
"Abbreviations": ["tsp","t", "ts", "tspn", "t.", "ts.", "tsp.", "tspn.", "teaspoon"]
363363
},
364364
{
365365
"Culture": "ru-RU",

UnitsNet/UnitsNetException.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,15 @@ public UnitsNetException(string message, Exception innerException) : base(messag
3737
{
3838
}
3939
}
40+
41+
public class AmbiguousUnitParseException : UnitsNetException
42+
{
43+
public AmbiguousUnitParseException(string message) : base(message)
44+
{
45+
}
46+
47+
public AmbiguousUnitParseException(string message, Exception innerException) : base(message, innerException)
48+
{
49+
}
50+
}
4051
}

0 commit comments

Comments
 (0)