Skip to content

Commit 37a9c48

Browse files
committed
Merge pull request #140 from neutmute/feature/ambiguous-abbreviations
Throw exception when parsing ambiguous abbreviation
2 parents 631c024 + 296446d commit 37a9c48

40 files changed

+295
-56
lines changed

UnitsNet.Tests/UnitSystemTests.cs

Lines changed: 29 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,32 @@ 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+
Assert.Throws<AmbiguousUnitParseException>(() => Volume.Parse("1 tsp"));
190+
}
191+
164192
[TestCase("m^2", Result = AreaUnit.SquareMeter)]
165193
[TestCase("cm^2", Result = AreaUnit.Undefined)]
166194
public AreaUnit Parse_ReturnsUnitMappedByCustomAbbreviationOrUndefined(string unitAbbreviationToParse)
167195
{
168-
CultureInfo cultureInfo = CultureInfo.GetCultureInfo("en-US");
169-
UnitSystem unitSystem = UnitSystem.GetCached(cultureInfo);
196+
var unitSystem = GetCachedUnitSystem();
170197
unitSystem.MapUnitToAbbreviation(AreaUnit.SquareMeter, "m^2");
171198

172199
return unitSystem.Parse<AreaUnit>(unitAbbreviationToParse);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright(c) 2007 Andreas Gullberg Larsen
2+
// https://github.com/anjdreas/UnitsNet
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy
5+
// of this software and associated documentation files (the "Software"), to deal
6+
// in the Software without restriction, including without limitation the rights
7+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the Software is
9+
// furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in
12+
// all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
// THE SOFTWARE.
21+
22+
using System;
23+
24+
namespace UnitsNet
25+
{
26+
public class AmbiguousUnitParseException : UnitsNetException
27+
{
28+
public AmbiguousUnitParseException(string message) : base(message)
29+
{
30+
}
31+
32+
public AmbiguousUnitParseException(string message, Exception innerException) : base(message, innerException)
33+
{
34+
}
35+
}
36+
}

UnitsNet/CustomCode/UnitSystem.cs

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

2929
// ReSharper disable once CheckNamespace
3030

31+
32+
3133
namespace UnitsNet
3234
{
3335
[PublicAPI]
@@ -41,7 +43,7 @@ public partial class UnitSystem
4143
/// Per-unit-type dictionary of enum values by abbreviation. This is the inverse of
4244
/// <see cref="_unitTypeToUnitValueToAbbrevs" />.
4345
/// </summary>
44-
private readonly Dictionary<Type, Dictionary<string, int>> _unitTypeToAbbrevToUnitValue;
46+
private readonly Dictionary<Type, AbbreviationMap> _unitTypeToAbbrevToUnitValue;
4547

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

7274
Culture = cultureInfo;
7375
_unitTypeToUnitValueToAbbrevs = new Dictionary<Type, Dictionary<int, List<string>>>();
74-
_unitTypeToAbbrevToUnitValue = new Dictionary<Type, Dictionary<string, int>>();
76+
_unitTypeToAbbrevToUnitValue = new Dictionary<Type, AbbreviationMap>();
7577

7678
LoadDefaultAbbreviatons(cultureInfo);
7779
}
@@ -122,17 +124,33 @@ public TUnit Parse<TUnit>(string unitAbbreviation)
122124
where TUnit : /*Enum constraint hack*/ struct, IComparable, IFormattable
123125
{
124126
Type unitType = typeof (TUnit);
125-
Dictionary<string, int> abbrevToUnitValue;
127+
AbbreviationMap abbrevToUnitValue;
126128
if (!_unitTypeToAbbrevToUnitValue.TryGetValue(unitType, out abbrevToUnitValue))
127129
throw new NotImplementedException(
128130
$"No abbreviations defined for unit type [{unitType}] for culture [{Culture}].");
129131

130-
int unitValue;
131-
TUnit result = abbrevToUnitValue.TryGetValue(unitAbbreviation, out unitValue)
132-
? (TUnit) (object) unitValue
133-
: default(TUnit);
132+
List<int> unitValues;
133+
List<TUnit> units;
134134

135-
return result;
135+
if (abbrevToUnitValue.TryGetValue(unitAbbreviation, out unitValues))
136+
{
137+
units = unitValues.Cast<TUnit>().Distinct().ToList();
138+
}
139+
else
140+
{
141+
units = new List<TUnit>();
142+
}
143+
144+
switch (units.Count)
145+
{
146+
case 1:
147+
return units[0];
148+
case 0:
149+
return default(TUnit);
150+
default:
151+
var unitsCsv = String.Join(", ", units.Select(x => x.ToString()).ToArray());
152+
throw new AmbiguousUnitParseException($"Cannot parse '{unitAbbreviation}' since it could be either of these: {unitsCsv}");
153+
}
136154
}
137155

138156
[PublicAPI]
@@ -187,15 +205,17 @@ public void MapUnitToAbbreviation(Type unitType, int unitValue, [NotNull] params
187205
unitValueToAbbrev[unitValue] = existingAbbreviations.Concat(abbreviations).Distinct().ToList();
188206
foreach (string abbreviation in abbreviations)
189207
{
190-
Dictionary<string, int> abbrevToUnitValue;
208+
AbbreviationMap abbrevToUnitValue;
191209
if (!_unitTypeToAbbrevToUnitValue.TryGetValue(unitType, out abbrevToUnitValue))
192210
{
193-
abbrevToUnitValue = _unitTypeToAbbrevToUnitValue[unitType] =
194-
new Dictionary<string, int>();
211+
abbrevToUnitValue = _unitTypeToAbbrevToUnitValue[unitType] = new AbbreviationMap();
195212
}
196213

197214
if (!abbrevToUnitValue.ContainsKey(abbreviation))
198-
abbrevToUnitValue[abbreviation] = unitValue;
215+
{
216+
abbrevToUnitValue[abbreviation] = new List<int>();
217+
}
218+
abbrevToUnitValue[abbreviation].Add(unitValue);
199219
}
200220
}
201221

@@ -205,11 +225,11 @@ public bool TryParse<TUnit>(string unitAbbreviation, out TUnit unit)
205225
{
206226
Type unitType = typeof (TUnit);
207227

208-
Dictionary<string, int> abbrevToUnitValue;
209-
int unitValue;
228+
AbbreviationMap abbrevToUnitValue;
229+
List<int> unitValues;
210230

211231
if (!_unitTypeToAbbrevToUnitValue.TryGetValue(unitType, out abbrevToUnitValue) ||
212-
!abbrevToUnitValue.TryGetValue(unitAbbreviation, out unitValue))
232+
!abbrevToUnitValue.TryGetValue(unitAbbreviation, out unitValues))
213233
{
214234
if (IsDefaultCulture)
215235
{
@@ -221,8 +241,16 @@ public bool TryParse<TUnit>(string unitAbbreviation, out TUnit unit)
221241
return GetCached(DefaultCulture).TryParse(unitAbbreviation, out unit);
222242
}
223243

224-
unit = (TUnit) (object) unitValue;
225-
return true;
244+
var maps = (List<TUnit>) (object) unitValues;
245+
246+
switch (maps.Count)
247+
{
248+
case 1: unit = maps[0];
249+
return true;
250+
default:
251+
unit = default(TUnit);
252+
return false;
253+
}
226254
}
227255

228256
/// <summary>
@@ -279,5 +307,13 @@ private void LoadDefaultAbbreviatons([NotNull] IFormatProvider culture)
279307
}
280308
}
281309
}
310+
311+
/// <summary>
312+
/// Avoids having too many nested generics for code clarity
313+
/// </summary>
314+
class AbbreviationMap : Dictionary<string, List<int>>
315+
{
316+
317+
}
282318
}
283319
}

UnitsNet/GeneratedCode/UnitClasses/Acceleration.g.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,11 @@ private static List<Acceleration> ParseWithRegex(string regexString, string str,
432432

433433
converted.Add(From(value, unit));
434434
}
435-
catch (Exception ex)
435+
catch(AmbiguousUnitParseException ambiguousException)
436+
{
437+
throw;
438+
}
439+
catch(Exception ex)
436440
{
437441
var newEx = new UnitsNetException("Error parsing string.", ex);
438442
newEx.Data["input"] = str;

UnitsNet/GeneratedCode/UnitClasses/AmplitudeRatio.g.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,11 @@ private static List<AmplitudeRatio> ParseWithRegex(string regexString, string st
360360

361361
converted.Add(From(value, unit));
362362
}
363-
catch (Exception ex)
363+
catch(AmbiguousUnitParseException ambiguousException)
364+
{
365+
throw;
366+
}
367+
catch(Exception ex)
364368
{
365369
var newEx = new UnitsNetException("Error parsing string.", ex);
366370
newEx.Data["input"] = str;

UnitsNet/GeneratedCode/UnitClasses/Angle.g.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,11 @@ private static List<Angle> ParseWithRegex(string regexString, string str, IForma
492492

493493
converted.Add(From(value, unit));
494494
}
495-
catch (Exception ex)
495+
catch(AmbiguousUnitParseException ambiguousException)
496+
{
497+
throw;
498+
}
499+
catch(Exception ex)
496500
{
497501
var newEx = new UnitsNetException("Error parsing string.", ex);
498502
newEx.Data["input"] = str;

UnitsNet/GeneratedCode/UnitClasses/Area.g.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,11 @@ private static List<Area> ParseWithRegex(string regexString, string str, IFormat
492492

493493
converted.Add(From(value, unit));
494494
}
495-
catch (Exception ex)
495+
catch(AmbiguousUnitParseException ambiguousException)
496+
{
497+
throw;
498+
}
499+
catch(Exception ex)
496500
{
497501
var newEx = new UnitsNetException("Error parsing string.", ex);
498502
newEx.Data["input"] = str;

UnitsNet/GeneratedCode/UnitClasses/Density.g.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,11 @@ private static List<Density> ParseWithRegex(string regexString, string str, IFor
492492

493493
converted.Add(From(value, unit));
494494
}
495-
catch (Exception ex)
495+
catch(AmbiguousUnitParseException ambiguousException)
496+
{
497+
throw;
498+
}
499+
catch(Exception ex)
496500
{
497501
var newEx = new UnitsNetException("Error parsing string.", ex);
498502
newEx.Data["input"] = str;

UnitsNet/GeneratedCode/UnitClasses/Duration.g.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,11 @@ private static List<Duration> ParseWithRegex(string regexString, string str, IFo
492492

493493
converted.Add(From(value, unit));
494494
}
495-
catch (Exception ex)
495+
catch(AmbiguousUnitParseException ambiguousException)
496+
{
497+
throw;
498+
}
499+
catch(Exception ex)
496500
{
497501
var newEx = new UnitsNetException("Error parsing string.", ex);
498502
newEx.Data["input"] = str;

UnitsNet/GeneratedCode/UnitClasses/ElectricCurrent.g.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,11 @@ private static List<ElectricCurrent> ParseWithRegex(string regexString, string s
412412

413413
converted.Add(From(value, unit));
414414
}
415-
catch (Exception ex)
415+
catch(AmbiguousUnitParseException ambiguousException)
416+
{
417+
throw;
418+
}
419+
catch(Exception ex)
416420
{
417421
var newEx = new UnitsNetException("Error parsing string.", ex);
418422
newEx.Data["input"] = str;

0 commit comments

Comments
 (0)