diff --git a/src/Cuemon.Core/Globalization/UnM49DataContainer.cs b/src/Cuemon.Core/Globalization/UnM49DataContainer.cs index 9daef7c1..dc84abf4 100644 --- a/src/Cuemon.Core/Globalization/UnM49DataContainer.cs +++ b/src/Cuemon.Core/Globalization/UnM49DataContainer.cs @@ -142,6 +142,15 @@ private void BuildHierarchy(List regions, List(StringComparer.OrdinalIgnoreCase); + foreach (var r in World.Regions) + { + if (!regionsByIsoAlpha2.ContainsKey(r.TwoLetterISORegionName)) + { + regionsByIsoAlpha2[r.TwoLetterISORegionName] = r; + } + } + foreach (var countryData in countries) { if (RegionsByCode.TryGetValue(countryData.ParentCode, out var parent)) @@ -154,15 +163,10 @@ private void BuildHierarchy(List regions, List string.Equals(r.TwoLetterISORegionName, countryData.IsoAlpha2, StringComparison.OrdinalIgnoreCase)); - } - catch + if (!string.IsNullOrEmpty(countryData.IsoAlpha2)) { - // Some territories may not be supported by the OS + regionsByIsoAlpha2.TryGetValue(countryData.IsoAlpha2, out regionInfo); } var country = new StatisticalRegionInfo( diff --git a/src/Cuemon.Core/Globalization/World.cs b/src/Cuemon.Core/Globalization/World.cs index 4901e2e1..eb06ba00 100644 --- a/src/Cuemon.Core/Globalization/World.cs +++ b/src/Cuemon.Core/Globalization/World.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Cuemon.Collections.Generic; namespace Cuemon.Globalization { @@ -14,9 +15,9 @@ public static class World { var cultures = new SortedList(); var specificCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures); - foreach (var c in specificCultures.Where(ci => ci.LCID != 127)) + foreach (var c in specificCultures.Where(ci => ci.LCID != 127)) // *should* not happen for specific cultures, but on some linux based systems there are some cultures with LCID 127 (invariant culture) that are incorrectly categorized as specific cultures, so we need to filter those out. { - if (!cultures.ContainsKey(c.DisplayName)) { cultures.Add(c.DisplayName, c); } + Decorator.Enclose(cultures).TryAdd(c.Name, c); } return cultures.Values; }); @@ -27,9 +28,10 @@ public static class World foreach (var c in SpecificCultures.Value) { var region = new RegionInfo(c.Name); - if (!regions.ContainsKey(region.EnglishName)) { regions.Add(region.EnglishName, region); } + if (int.TryParse(region.Name, out _)) { continue; } // Skip statistical regions (why would Microsoft even consider having these as part of RegionInfo? No valid ISO 3166-1 alpha-2/3 code can be all digits, so these are not actual regions or countries.) + Decorator.Enclose(regions).TryAdd($"{region.Name}:{region.NativeName}", region); } - return regions.Values; + return regions.Values.OrderBy(info => info.Name).ThenBy(info => info.NativeName); }); private static readonly Lazy UnM49Data = new(() => new UnM49DataContainer()); diff --git a/test/Cuemon.Core.Tests/Globalization/WorldTest.cs b/test/Cuemon.Core.Tests/Globalization/WorldTest.cs index d40c6414..12e7ce38 100644 --- a/test/Cuemon.Core.Tests/Globalization/WorldTest.cs +++ b/test/Cuemon.Core.Tests/Globalization/WorldTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Codebelt.Extensions.Xunit; @@ -13,17 +14,95 @@ public WorldTest(ITestOutputHelper output) : base(output) } [Fact] - public void Regions_ShouldReturnAllRegions() + public void Regions_ShouldContainAllExpectedIsoRegionCodes_ForBackwardCompatibility() + { + var expectedTwoLetterIsoCodes = new HashSet(StringComparer.Ordinal) + { + "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", + "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BW", "BY", "BZ", + "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ", + "DE", "DJ", "DK", "DM", "DO", "DZ", + "EC", "EE", "EG", "ER", "ES", "ET", + "FI", "FJ", "FK", "FM", "FO", "FR", + "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GT", "GU", "GW", "GY", + "HK", "HN", "HR", "HT", "HU", + "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", + "JE", "JM", "JO", "JP", + "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", + "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", + "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", + "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", + "OM", + "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", + "QA", + "RE", "RO", "RS", "RU", "RW", + "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", + "TC", "TD", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", + "UA", "UG", "UM", "US", "UY", "UZ", + "VA", "VC", "VE", "VG", "VI", "VN", "VU", + "WF", "WS", + "XK", + "YE", "YT", + "ZA", "ZM", "ZW" + }; + + var sut1 = World.Regions.ToList(); + var actualCodes = new HashSet(sut1.Select(r => r.Name), StringComparer.Ordinal); + +#if NET48_OR_GREATER + Assert.NotEmpty(actualCodes); + Assert.True(actualCodes.Count > 100, "actualCodes.Count > 100"); +#else + var missing = expectedTwoLetterIsoCodes.Except(actualCodes).OrderBy(c => c).ToList(); + var added = actualCodes.Except(expectedTwoLetterIsoCodes).OrderBy(c => c).ToList(); + foreach (var code in missing) + { + TestOutput.WriteLine($"Missing: {code} - {World.Regions.SingleOrDefault(info => info.Name == code).EnglishName}"); + } + foreach (var code in added) + { + TestOutput.WriteLine($"Added: {code} - {World.Regions.Last(info => info.Name == code).EnglishName}"); + } + TestOutput.WriteLine($"Expected: {expectedTwoLetterIsoCodes.Count}, Actual: {actualCodes.Count}, Missing: {missing.Count}, Added: {added.Count}"); + Assert.Empty(missing); +#endif + } + + [Fact] + public void Regions_ShouldPrintAllRegionsAndHighlightIsoCodeFrequency() { var sut1 = World.Regions.ToList(); - TestOutput.WriteLine(sut1.Count.ToString()); + foreach (var r in sut1) + { + TestOutput.WriteLine($"{r.Name,-5} {r.EnglishName}"); + } + + var grouped = sut1.GroupBy(r => r.Name).OrderBy(g => g.Key).ToList(); + var multiEntry = grouped.Where(g => g.Count() > 1).OrderByDescending(g => g.Count()).ThenBy(g => g.Key).ToList(); + + TestOutput.WriteLine($"Total: {sut1.Count}, Unique ISO codes: {grouped.Count}, ISO codes with multiple entries: {multiEntry.Count}"); + + foreach (var g in multiEntry) + { + var first = g.First(); + var allEqual = g.All(r => r.Equals(first)); + var distinctNativeNames = g.Select(r => r.NativeName).Distinct().ToList(); + TestOutput.WriteLine($" {g.Key} ({first.EnglishName}): {g.Count()} entries | all Equals: {allEqual} | distinct NativeNames: {distinctNativeNames.Count}"); + if (distinctNativeNames.Count > 1) + { + foreach (var name in distinctNativeNames) + { + TestOutput.WriteLine($" NativeName: {name}"); + } + } + } - Assert.NotNull(sut1); + Assert.NotEmpty(sut1); #if NET48_OR_GREATER Assert.True(sut1.Count > 100, "sut1.Count > 100"); #else - Assert.True(sut1.Count > 220, "sut1.Count > 220"); + Assert.True(sut1.Count > 400, "sut1.Count > 400"); #endif }