11using System ;
2+ using System . Collections . Generic ;
23using System . Linq ;
34using CodeGen . JsonTypes ;
45
@@ -50,6 +51,38 @@ internal class UnitTestBaseClassGenerator : GeneratorBase
5051 /// </summary>
5152 private readonly string _otherOrBaseUnitFullName ;
5253
54+ /// <summary>
55+ /// Stores a mapping of culture names to their corresponding unique unit abbreviations.
56+ /// Each culture maps to a dictionary where the key is the unit abbreviation and the value is the corresponding
57+ /// <see cref="Unit" />.
58+ /// This ensures that unit abbreviations are unique within the context of a specific culture.
59+ /// </summary>
60+ /// <remarks>
61+ /// Used for testing culture-specific parsing with non-ambiguous (unique) abbreviations.
62+ /// </remarks>
63+ private readonly Dictionary < string , Dictionary < string , Unit > > _uniqueAbbreviationsForCulture ;
64+
65+ /// <summary>
66+ /// Stores a mapping of culture names to their respective ambiguous unit abbreviations.
67+ /// Each culture maps to a dictionary where the key is the ambiguous abbreviation, and the value is a list of
68+ /// <see cref="Unit" /> objects
69+ /// that share the same abbreviation within that culture.
70+ /// </summary>
71+ /// <remarks>
72+ /// This field is used to identify and handle unit abbreviations that are not unique within a specific culture.
73+ /// Ambiguities arise when multiple units share the same abbreviation, requiring additional logic to resolve.
74+ /// </remarks>
75+ private readonly Dictionary < string , Dictionary < string , List < Unit > > > _ambiguousAbbreviationsForCulture ;
76+
77+ /// <summary>
78+ /// The default or fallback culture for unit localizations.
79+ /// </summary>
80+ /// <remarks>
81+ /// This culture, "en-US", is used as a fallback when a specific <see cref="System.Globalization.CultureInfo" />
82+ /// is not available for the defined unit localizations.
83+ /// </remarks>
84+ private const string FallbackCultureName = "en-US" ;
85+
5386 public UnitTestBaseClassGenerator ( Quantity quantity )
5487 {
5588 _quantity = quantity ;
@@ -65,6 +98,52 @@ public UnitTestBaseClassGenerator(Quantity quantity)
6598 // Try to pick another unit, or fall back to base unit if only a single unit.
6699 _otherOrBaseUnit = quantity . Units . Where ( u => u != _baseUnit ) . DefaultIfEmpty ( _baseUnit ) . First ( ) ;
67100 _otherOrBaseUnitFullName = $ "{ _unitEnumName } .{ _otherOrBaseUnit . SingularName } ";
101+
102+ var abbreviationsForCulture = new Dictionary < string , Dictionary < string , List < Unit > > > ( ) ;
103+ foreach ( Unit unit in quantity . Units )
104+ {
105+ if ( unit . ObsoleteText != null )
106+ {
107+ continue ;
108+ }
109+
110+ foreach ( Localization localization in unit . Localization )
111+ {
112+ if ( ! abbreviationsForCulture . TryGetValue ( localization . Culture , out Dictionary < string , List < Unit > > ? localizationsForCulture ) )
113+ {
114+ abbreviationsForCulture [ localization . Culture ] = localizationsForCulture = new Dictionary < string , List < Unit > > ( ) ;
115+ }
116+
117+ foreach ( var abbreviation in localization . Abbreviations )
118+ {
119+ if ( localizationsForCulture . TryGetValue ( abbreviation , out List < Unit > ? matchingUnits ) )
120+ {
121+ matchingUnits . Add ( unit ) ;
122+ }
123+ else
124+ {
125+ localizationsForCulture [ abbreviation ] = [ unit ] ;
126+ }
127+ }
128+ }
129+ }
130+
131+ _uniqueAbbreviationsForCulture = new Dictionary < string , Dictionary < string , Unit > > ( ) ;
132+ _ambiguousAbbreviationsForCulture = new Dictionary < string , Dictionary < string , List < Unit > > > ( ) ;
133+ foreach ( ( var cultureName , Dictionary < string , List < Unit > > ? abbreviations ) in abbreviationsForCulture )
134+ {
135+ var uniqueAbbreviations = abbreviations . Where ( pair => pair . Value . Count == 1 ) . ToDictionary ( pair => pair . Key , pair => pair . Value [ 0 ] ) ;
136+ if ( uniqueAbbreviations . Count != 0 )
137+ {
138+ _uniqueAbbreviationsForCulture . Add ( cultureName , uniqueAbbreviations ) ;
139+ }
140+
141+ var ambiguousAbbreviations = abbreviations . Where ( pair => pair . Value . Count > 1 ) . ToDictionary ( ) ;
142+ if ( ambiguousAbbreviations . Count != 0 )
143+ {
144+ _ambiguousAbbreviationsForCulture . Add ( cultureName , ambiguousAbbreviations ) ;
145+ }
146+ }
68147 }
69148
70149 private string GetUnitFullName ( Unit unit ) => $ "{ _unitEnumName } .{ unit . SingularName } ";
@@ -90,6 +169,7 @@ public string Generate()
90169using System.Globalization;
91170using System.Linq;
92171using System.Threading;
172+ using UnitsNet.Tests.Helpers;
93173using UnitsNet.Tests.TestsBase;
94174using UnitsNet.Units;
95175using Xunit;
@@ -323,45 +403,193 @@ public void TryParse()
323403 }
324404 Writer . WL ( $@ "
325405 }}
406+ " ) ;
326407
327- [Fact]
328- public void ParseUnit()
329- {{" ) ;
330- foreach ( var unit in _quantity . Units . Where ( u => string . IsNullOrEmpty ( u . ObsoleteText ) ) )
331- foreach ( var localization in unit . Localization )
332- foreach ( var abbreviation in localization . Abbreviations )
408+ Writer . WL ( $@ "
409+ [Theory]" ) ;
410+ foreach ( ( var abbreviation , Unit unit ) in _uniqueAbbreviationsForCulture [ FallbackCultureName ] )
333411 {
334412 Writer . WL ( $@ "
335- try
336- {{
337- var parsedUnit = { _quantity . Name } .ParseUnit(""{ abbreviation } "", CultureInfo.GetCultureInfo(""{ localization . Culture } ""));
338- Assert.Equal({ GetUnitFullName ( unit ) } , parsedUnit);
339- }} catch (AmbiguousUnitParseException) {{ /* Some units have the same abbreviations */ }}
413+ [InlineData(""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
414+ }
415+ Writer . WL ( $@ "
416+ public void ParseUnit_WithUsEnglishCurrentCulture(string abbreviation, { _unitEnumName } expectedUnit)
417+ {{
418+ // Fallback culture ""{ FallbackCultureName } "" is always localized
419+ using var _ = new CultureScope(""{ FallbackCultureName } "");
420+ { _unitEnumName } parsedUnit = { _quantity . Name } .ParseUnit(abbreviation);
421+ Assert.Equal(expectedUnit, parsedUnit);
422+ }}
340423" ) ;
424+
425+ Writer . WL ( $@ "
426+ [Theory]" ) ;
427+ foreach ( ( var abbreviation , Unit unit ) in _uniqueAbbreviationsForCulture [ FallbackCultureName ] )
428+ {
429+ Writer . WL ( $@ "
430+ [InlineData(""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
341431 }
342432 Writer . WL ( $@ "
433+ public void ParseUnit_WithUnsupportedCurrentCulture_FallsBackToUsEnglish(string abbreviation, { _unitEnumName } expectedUnit)
434+ {{
435+ // Currently, no abbreviations are localized for Icelandic, so it should fall back to ""{ FallbackCultureName } "" when parsing.
436+ using var _ = new CultureScope(""is-IS"");
437+ { _unitEnumName } parsedUnit = { _quantity . Name } .ParseUnit(abbreviation);
438+ Assert.Equal(expectedUnit, parsedUnit);
343439 }}
440+ " ) ;
344441
345- [Fact]
346- public void TryParseUnit()
347- {{" ) ;
348- foreach ( var unit in _quantity . Units . Where ( u => string . IsNullOrEmpty ( u . ObsoleteText ) ) )
349- foreach ( var localization in unit . Localization )
350- foreach ( var abbreviation in localization . Abbreviations )
442+ Writer . WL ( $@ "
443+ [Theory]" ) ;
444+ foreach ( ( var cultureName , Dictionary < string , Unit > abbreviations ) in _uniqueAbbreviationsForCulture )
351445 {
352- // Skip units with ambiguous abbreviations, since there is no exception to describe this is why TryParse failed.
353- if ( IsAmbiguousAbbreviation ( localization , abbreviation ) ) continue ;
446+ foreach ( ( var abbreviation , Unit unit ) in abbreviations )
447+ {
448+ Writer . WL ( $@ "
449+ [InlineData(""{ cultureName } "", ""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
450+ }
451+ }
452+ Writer . WL ( $@ "
453+ public void ParseUnit_WithCurrentCulture(string culture, string abbreviation, { _unitEnumName } expectedUnit)
454+ {{
455+ using var _ = new CultureScope(culture);
456+ { _unitEnumName } parsedUnit = { _quantity . Name } .ParseUnit(abbreviation);
457+ Assert.Equal(expectedUnit, parsedUnit);
458+ }}
459+ " ) ;
460+
461+ Writer . WL ( $@ "
462+ [Theory]" ) ;
463+ foreach ( ( var cultureName , Dictionary < string , Unit > abbreviations ) in _uniqueAbbreviationsForCulture )
464+ {
465+ foreach ( ( var abbreviation , Unit unit ) in abbreviations )
466+ {
467+ Writer . WL ( $@ "
468+ [InlineData(""{ cultureName } "", ""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
469+ }
470+ }
471+ Writer . WL ( $@ "
472+ public void ParseUnit_WithCulture(string culture, string abbreviation, { _unitEnumName } expectedUnit)
473+ {{
474+ { _unitEnumName } parsedUnit = { _quantity . Name } .ParseUnit(abbreviation, CultureInfo.GetCultureInfo(culture));
475+ Assert.Equal(expectedUnit, parsedUnit);
476+ }}
477+ " ) ;
354478
479+ // we only generate these for a few of the quantities
480+ if ( _ambiguousAbbreviationsForCulture . Count != 0 )
481+ {
355482 Writer . WL ( $@ "
356- {{
357- Assert.True({ _quantity . Name } .TryParseUnit(""{ abbreviation } "", CultureInfo.GetCultureInfo(""{ localization . Culture } ""), out var parsedUnit));
358- Assert.Equal({ GetUnitFullName ( unit ) } , parsedUnit);
359- }}
483+ [Theory]" ) ;
484+ foreach ( ( var cultureName , Dictionary < string , List < Unit > > ? abbreviations ) in _ambiguousAbbreviationsForCulture )
485+ {
486+ foreach ( KeyValuePair < string , List < Unit > > ambiguousPair in abbreviations )
487+ {
488+ Writer . WL ( $@ "
489+ [InlineData(""{ cultureName } "", ""{ ambiguousPair . Key } "")] // [{ string . Join ( ", " , ambiguousPair . Value . Select ( x => x . SingularName ) ) } ] " ) ;
490+ }
491+ }
492+ Writer . WL ( $@ "
493+ public void ParseUnitWithAmbiguousAbbreviation(string culture, string abbreviation)
494+ {{
495+ Assert.Throws<AmbiguousUnitParseException>(() => { _quantity . Name } .ParseUnit(abbreviation, CultureInfo.GetCultureInfo(culture)));
496+ }}
360497" ) ;
498+ } // ambiguousAbbreviations
499+
500+ Writer . WL ( $@ "
501+ [Theory]" ) ;
502+ foreach ( ( var abbreviation , Unit unit ) in _uniqueAbbreviationsForCulture [ FallbackCultureName ] )
503+ {
504+ Writer . WL ( $@ "
505+ [InlineData(""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
361506 }
362507 Writer . WL ( $@ "
508+ public void TryParseUnit_WithUsEnglishCurrentCulture(string abbreviation, { _unitEnumName } expectedUnit)
509+ {{
510+ // Fallback culture ""{ FallbackCultureName } "" is always localized
511+ using var _ = new CultureScope(""{ FallbackCultureName } "");
512+ Assert.True({ _quantity . Name } .TryParseUnit(abbreviation, out { _unitEnumName } parsedUnit));
513+ Assert.Equal(expectedUnit, parsedUnit);
363514 }}
515+ " ) ;
364516
517+ Writer . WL ( $@ "
518+ [Theory]" ) ;
519+ foreach ( ( var abbreviation , Unit unit ) in _uniqueAbbreviationsForCulture [ FallbackCultureName ] )
520+ {
521+ Writer . WL ( $@ "
522+ [InlineData(""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
523+ }
524+ Writer . WL ( $@ "
525+ public void TryParseUnit_WithUnsupportedCurrentCulture_FallsBackToUsEnglish(string abbreviation, { _unitEnumName } expectedUnit)
526+ {{
527+ // Currently, no abbreviations are localized for Icelandic, so it should fall back to ""{ FallbackCultureName } "" when parsing.
528+ using var _ = new CultureScope(""is-IS"");
529+ Assert.True({ _quantity . Name } .TryParseUnit(abbreviation, out { _unitEnumName } parsedUnit));
530+ Assert.Equal(expectedUnit, parsedUnit);
531+ }}
532+ " ) ;
533+
534+ Writer . WL ( $@ "
535+ [Theory]" ) ;
536+ foreach ( ( var cultureName , Dictionary < string , Unit > abbreviations ) in _uniqueAbbreviationsForCulture )
537+ {
538+ foreach ( ( var abbreviation , Unit unit ) in abbreviations )
539+ {
540+ Writer . WL ( $@ "
541+ [InlineData(""{ cultureName } "", ""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
542+ }
543+ }
544+ Writer . WL ( $@ "
545+ public void TryParseUnit_WithCurrentCulture(string culture, string abbreviation, { _unitEnumName } expectedUnit)
546+ {{
547+ using var _ = new CultureScope(culture);
548+ Assert.True({ _quantity . Name } .TryParseUnit(abbreviation, out { _unitEnumName } parsedUnit));
549+ Assert.Equal(expectedUnit, parsedUnit);
550+ }}
551+ " ) ;
552+
553+ Writer . WL ( $@ "
554+ [Theory]" ) ;
555+ foreach ( ( var cultureName , Dictionary < string , Unit > abbreviations ) in _uniqueAbbreviationsForCulture )
556+ {
557+ foreach ( ( var abbreviation , Unit unit ) in abbreviations )
558+ {
559+ Writer . WL ( $@ "
560+ [InlineData(""{ cultureName } "", ""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
561+ }
562+ }
563+ Writer . WL ( $@ "
564+ public void TryParseUnit_WithCulture(string culture, string abbreviation, { _unitEnumName } expectedUnit)
565+ {{
566+ Assert.True({ _quantity . Name } .TryParseUnit(abbreviation, CultureInfo.GetCultureInfo(culture), out { _unitEnumName } parsedUnit));
567+ Assert.Equal(expectedUnit, parsedUnit);
568+ }}
569+ " ) ;
570+
571+ // we only generate these for a few of the quantities
572+ if ( _ambiguousAbbreviationsForCulture . Count != 0 )
573+ {
574+ Writer . WL ( $@ "
575+ [Theory]" ) ;
576+ foreach ( ( var cultureName , Dictionary < string , List < Unit > > ? abbreviations ) in _ambiguousAbbreviationsForCulture )
577+ {
578+ foreach ( KeyValuePair < string , List < Unit > > ambiguousPair in abbreviations )
579+ {
580+ Writer . WL ( $@ "
581+ [InlineData(""{ cultureName } "", ""{ ambiguousPair . Key } "")] // [{ string . Join ( ", " , ambiguousPair . Value . Select ( x => x . SingularName ) ) } ] " ) ;
582+ }
583+ }
584+ Writer . WL ( $@ "
585+ public void TryParseUnitWithAmbiguousAbbreviation(string culture, string abbreviation)
586+ {{
587+ Assert.False({ _quantity . Name } .TryParseUnit(abbreviation, CultureInfo.GetCultureInfo(culture), out _));
588+ }}
589+ " ) ;
590+ } // ambiguousAbbreviations
591+
592+ Writer . WL ( $@ "
365593 [Theory]
366594 [MemberData(nameof(UnitTypes))]
367595 public void ToUnit({ _unitEnumName } unit)
0 commit comments