1
1
using System ;
2
+ using System . Collections . Generic ;
2
3
using System . Linq ;
3
4
using CodeGen . JsonTypes ;
4
5
@@ -50,6 +51,38 @@ internal class UnitTestBaseClassGenerator : GeneratorBase
50
51
/// </summary>
51
52
private readonly string _otherOrBaseUnitFullName ;
52
53
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
+
53
86
public UnitTestBaseClassGenerator ( Quantity quantity )
54
87
{
55
88
_quantity = quantity ;
@@ -65,6 +98,52 @@ public UnitTestBaseClassGenerator(Quantity quantity)
65
98
// Try to pick another unit, or fall back to base unit if only a single unit.
66
99
_otherOrBaseUnit = quantity . Units . Where ( u => u != _baseUnit ) . DefaultIfEmpty ( _baseUnit ) . First ( ) ;
67
100
_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
+ }
68
147
}
69
148
70
149
private string GetUnitFullName ( Unit unit ) => $ "{ _unitEnumName } .{ unit . SingularName } ";
@@ -90,6 +169,7 @@ public string Generate()
90
169
using System.Globalization;
91
170
using System.Linq;
92
171
using System.Threading;
172
+ using UnitsNet.Tests.Helpers;
93
173
using UnitsNet.Tests.TestsBase;
94
174
using UnitsNet.Units;
95
175
using Xunit;
@@ -323,45 +403,193 @@ public void TryParse()
323
403
}
324
404
Writer . WL ( $@ "
325
405
}}
406
+ " ) ;
326
407
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 ] )
333
411
{
334
412
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
+ }}
340
423
" ) ;
424
+
425
+ Writer . WL ( $@ "
426
+ [Theory]" ) ;
427
+ foreach ( ( var abbreviation , Unit unit ) in _uniqueAbbreviationsForCulture [ FallbackCultureName ] )
428
+ {
429
+ Writer . WL ( $@ "
430
+ [InlineData(""{ abbreviation } "", { GetUnitFullName ( unit ) } )]" ) ;
341
431
}
342
432
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);
343
439
}}
440
+ " ) ;
344
441
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 )
351
445
{
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
+ " ) ;
354
478
479
+ // we only generate these for a few of the quantities
480
+ if ( _ambiguousAbbreviationsForCulture . Count != 0 )
481
+ {
355
482
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
+ }}
360
497
" ) ;
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 ) } )]" ) ;
361
506
}
362
507
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);
363
514
}}
515
+ " ) ;
364
516
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 ( $@ "
365
593
[Theory]
366
594
[MemberData(nameof(UnitTypes))]
367
595
public void ToUnit({ _unitEnumName } unit)
0 commit comments