winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

NameNormalization.cpp (25395B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Public/winget/NameNormalization.h"
      5 #include "Public/AppInstallerStrings.h"
      6 #include "Public/winget/Regex.h"
      7 
      8 
      9 namespace AppInstaller::Utility
     10 {
     11     namespace
     12     {
     13         struct InterimNameNormalizationResult
     14         {
     15             std::wstring Name;
     16             Architecture Architecture = Architecture::Unknown;
     17             std::wstring Locale;
     18         };
     19 
     20         struct InterimPublisherNormalizationResult
     21         {
     22             std::wstring Publisher;
     23         };
     24 
     25         // To maintain consistency, changes that result in different output must be done in a new version.
     26         // This can potentially be ignored (if thought through) when the changes will only increase the
     27         // number of matches being made, with no impact to existing matches. For instance, removing an
     28         // arbitrary new processor architecture from names would hopefully only affect existing packages
     29         // that were not matching properly. Fixing a bug that was causing bad strings to be produced would
     30         // be impactful, and thus should likely result in a new iteration.
     31         class NormalizationInitial : public details::INameNormalizer
     32         {
     33             static std::wstring PrepareForValidation(std::string_view value)
     34             {
     35                 std::wstring result = Utility::Normalize(ConvertToUTF16(value));
     36                 Trim(result);
     37                 size_t atPos = result.find(L"@@", 3);
     38                 if (atPos != std::wstring::npos)
     39                 {
     40                     result = result.substr(0, atPos);
     41                 }
     42                 return result;
     43             }
     44 
     45             // If the string is wrapped with some character groups, remove them.
     46             // Returns true if string was wrapped; false if not.
     47             static bool Unwrap(std::wstring& value)
     48             {
     49                 if (value.length() >= 2)
     50                 {
     51                     bool unwrap = false;
     52 
     53                     switch (value[0])
     54                     {
     55                     case L'"':
     56                         unwrap = value.back() == L'"';
     57                         break;
     58 
     59                     case L'(':
     60                         unwrap = value.back() == L')';
     61                         break;
     62                     }
     63 
     64                     if (unwrap)
     65                     {
     66                         value = value.substr(1, value.length() - 2);
     67                         return true;
     68                     }
     69                 }
     70 
     71                 return false;
     72             }
     73 
     74             // Removes all matches from the input string.
     75             static bool Remove(const Regex::Expression& re, std::wstring& input)
     76             {
     77                 std::wstring output = re.Replace(input, {});
     78                 bool result = (output != input);
     79                 input = std::move(output);
     80                 return result;
     81             }
     82 
     83             // Removes the architecture and returns the value, if any
     84             Architecture RemoveArchitecture(std::wstring& value) const
     85             {
     86                 Architecture result = Architecture::Unknown;
     87 
     88                 // Must detect this first because "32/64 bit" is a superstring of "64 bit"
     89                 if (Remove(Architecture32Or64Bit, value))
     90                 {
     91                     // If the program is 32 and 64 bit in the same installer, leave as unknown.
     92                 }
     93                 // Must detect 64 bit before 32 bit because of "x86-64" being a superstring of "x86"
     94                 else if (Remove(ArchitectureX64, value) || Remove(Architecture64Bit, value))
     95                 {
     96                     result = Architecture::X64;
     97                 }
     98                 else if (Remove(ArchitectureX32, value) || Remove(Architecture32Bit, value))
     99                 {
    100                     result = Architecture::X86;
    101                 }
    102 
    103                 return result;
    104             }
    105 
    106             // Removes all matches for the given regular expressions
    107             static bool RemoveAll(const std::vector<Regex::Expression*>& regexes, std::wstring& value)
    108             {
    109                 bool result = false;
    110 
    111                 for (const auto& re : regexes)
    112                 {
    113                     result = Remove(*re, value) || result;
    114                 }
    115 
    116                 return result;
    117             }
    118 
    119             // Removes all locales and returns the common value, if any
    120             std::wstring RemoveLocale(std::wstring& value) const
    121             {
    122                 bool localeFound = false;
    123                 std::wstring result;
    124 
    125                 std::wstring newValue;
    126                 auto newValueInserter = std::back_inserter(newValue);
    127 
    128                 Locale.ForEach(value,
    129                     [&](bool isMatch, std::wstring_view text)
    130                     {
    131                         bool copy = !isMatch;
    132 
    133                         if (isMatch)
    134                         {
    135                             std::wstring foldedText = ConvertToUTF16(FoldCase(text));
    136 
    137                             // Ensure that the value is in the locale list
    138                             auto bound = std::lower_bound(Locales.begin(), Locales.end(), foldedText);
    139 
    140                             if (bound == Locales.end() || *bound != foldedText)
    141                             {
    142                                 // Match was not a locale in our list, so copy it out
    143                                 copy = true;
    144                             }
    145                             else if (!localeFound)
    146                             {
    147                                 // First/only match, just extract the value
    148                                 result = foldedText;
    149                                 localeFound = true;
    150                             }
    151                             else if (!result.empty())
    152                             {
    153                                 // For some reason, there are multiple locales listed.
    154                                 // See if they have anything in common.
    155                                 if (result != foldedText)
    156                                 {
    157                                     // Not completely the same (expected), see if they are at least the same language
    158                                     result.erase(result.find(L'-'));
    159                                     foldedText.erase(foldedText.find(L'-'));
    160 
    161                                     if (result != foldedText)
    162                                     {
    163                                         // Not the same language, abandon having a locale and just clean them
    164                                         result.clear();
    165                                     }
    166                                 }
    167                             }
    168                         }
    169 
    170                         if (copy)
    171                         {
    172                             std::copy(text.begin(), text.end(), newValueInserter);
    173                         }
    174 
    175                         return true;
    176                     });
    177 
    178                 value = std::move(newValue);
    179 
    180                 return result;
    181             }
    182 
    183             // Splits the string based on the regex matches, excluding empty/whitespace strings
    184             // and any values found in the exclusions.
    185             static std::vector<std::wstring> Split(const Regex::Expression& re, const std::wstring& value, const std::vector<std::wstring>& exclusions, bool stopOnExclusion = false)
    186             {
    187                 std::vector<std::wstring> result;
    188 
    189                 re.ForEach(value,
    190                     [&](bool, std::wstring_view text)
    191                     {
    192                         if (IsEmptyOrWhitespace(text))
    193                         {
    194                             return true;
    195                         }
    196 
    197                         // Do not stop for an exclusion if it is the first word found
    198                         if (!result.empty())
    199                         {
    200                             std::wstring foldedText = ConvertToUTF16(FoldCase(text));
    201 
    202                             auto bound = std::lower_bound(exclusions.begin(), exclusions.end(), foldedText);
    203 
    204                             if (bound != exclusions.end() && *bound == foldedText)
    205                             {
    206                                 return !stopOnExclusion;
    207                             }
    208                         }
    209 
    210                         result.emplace_back(std::wstring{ text });
    211                         return true;
    212                     });
    213 
    214                 return result;
    215             }
    216 
    217             // Joins all of the given strings into a single value
    218             static std::wstring Join(const std::vector<std::wstring>& values, const std::wstring& separator = {})
    219             {
    220                 std::wstring result;
    221 
    222                 bool isFirst = true;
    223                 for (const auto& v : values)
    224                 {
    225                     if (isFirst)
    226                     {
    227                         isFirst = false;
    228                     }
    229                     else
    230                     {
    231                         result += separator;
    232                     }
    233 
    234                     result += v;
    235                 }
    236 
    237                 return result;
    238             }
    239 
    240             static constexpr Regex::Options reOptions = Regex::Options::CaseInsensitive;
    241 
    242             // Architecture
    243             Regex::Expression ArchitectureX32{ R"((?<=^|[^\p{L}\p{Nd}])(X32|X86)(?=\P{Nd}|$)(?:\sEDITION)?)", reOptions };
    244             Regex::Expression ArchitectureX64{ R"((?<=^|[^\p{L}\p{Nd}])(X64|AMD64|X86([\p{Pd}\p{Pc}]64))(?=\P{Nd}|$)(?:\sEDITION)?)", reOptions };
    245             Regex::Expression Architecture32Bit{ R"((?<=^|[^\p{L}\p{Nd}])(32[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions };
    246             Regex::Expression Architecture64Bit{ R"((?<=^|[^\p{L}\p{Nd}])(64[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions };
    247             Regex::Expression Architecture32Or64Bit{ R"((?<=^|[^\p{L}\p{Nd}])((64[\\\/]32|32[\\\/]64)[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions };
    248 
    249             // Locale
    250             Regex::Expression Locale{ R"((?<![A-Z])((?:\p{Lu}{2,3}(-(CANS|CYRL|LATN|MONG))?-\p{Lu}{2})(?![A-Z])(?:-VALENCIA)?))", reOptions };
    251 
    252             // Specifically for SAP Business Objects programs
    253             Regex::Expression SAPPackage{ R"(^(?:[\p{Lu}\p{Nd}]+[\._])+[\p{Lu}\p{Nd}]+(?:-(?:\p{Nd}+\.)+\p{Nd}+)(?:-(?:\p{Lu}{2}(?:_\p{Lu}{2})?|CORE))(?:-(?:\p{Lu}{2}|\p{Nd}{2}))$)", reOptions };
    254 
    255             // Extract KB numbers from their parens to preserve them
    256             Regex::Expression KBNumbers{ R"(\((KB\d+)\))", reOptions };
    257 
    258             Regex::Expression NonLettersAndDigits{ R"([^\p{L}\p{Nd}])", reOptions };
    259             Regex::Expression NonLetterDigitOrSpace{ R"([^\p{L}\p{Nd}\s])", reOptions };
    260             Regex::Expression URIProtocol{ R"((?<!\p{L})(?:http[s]?|ftp):\/\/)", reOptions }; // remove protocol from URIs
    261 
    262             Regex::Expression VersionDelimited{ R"(((?<!\p{L})(?:V|VER|VERSI(?:O|Ó)N|VERSÃO|VERSIE|WERSJA|BUILD|RELEASE|RC|SP)\P{L}?)?\p{Nd}+([\p{Po}\p{Pd}\p{Pc}]\p{Nd}?(RC|B|A|R|SP|K)?\p{Nd}+)+([\p{Po}\p{Pd}\p{Pc}]?[\p{L}\p{Nd}]+)*)", reOptions };
    263             Regex::Expression Version{ R"((FOR\s)?(?<!\p{L})(?:P|V|R|VER|VERSI(?:O|Ó)N|VERSÃO|VERSIE|WERSJA|BUILD|RELEASE|RC|SP)(?:\P{L}|\P{L}\p{L})?(\p{Nd}|\.\p{Nd})+(?:RC|B|A|R|V|SP)?\p{Nd}?)", reOptions };
    264             Regex::Expression VersionLetter{ R"((?<!\p{L})(?:(?:V|VER|VERSI(?:O|Ó)N|VERSÃO|VERSIE|WERSJA|BUILD|RELEASE|RC|SP)\P{L})?\p{Lu}\p{Nd}+(?:[\p{Po}\p{Pd}\p{Pc}]\p{Nd}+)+)", reOptions };
    265             Regex::Expression NonNestedBracket{ R"(\([^\(\)]*\)|\[[^\[\]]*\])", reOptions }; // remove things in parentheses, if there aren't parentheses nested inside
    266             Regex::Expression BracketEnclosed{ R"((?:\p{Ps}.*\p{Pe}|".*"))", reOptions }; // Impossible to properly handle nested parens with regex
    267             Regex::Expression LeadingSymbols{ R"(^[^\p{L}\p{Nd}]+)", reOptions }; // remove symbols at the beginning
    268             Regex::Expression TrailingNonLetters{ R"(\P{L}+$)", reOptions }; // remove non-letters at the end
    269             Regex::Expression PrefixParens{ R"(^\(.*?\))", reOptions }; // remove things in parentheses at the front of program names
    270             Regex::Expression EmptyParens{ R"((\(\s*\)|\[\s*\]|"\s*"))", reOptions }; // remove appearances of (), [], and "", with any number of spaces within
    271             Regex::Expression EN{ R"(\sEN\s*$)", reOptions }; // remove appearances of EN (represents English language) at the ends of program names
    272             Regex::Expression TrailingSymbols{ R"([^\p{L}\p{Nd}]+$)", reOptions }; // remove all non-letter/numbers at the end
    273             Regex::Expression FilePath{ R"(((INSTALLED\sAT|IN)\s)?[CDEF]:\\(.+?\\)*[^\s]*\\?)", reOptions }; // remove file paths
    274             Regex::Expression FilePathGHS{ R"(\(CHANGE #\d{1,2} TO [CDEF]:\\(.+?\\)*[^\s]*\\?\))", reOptions }; // remove file paths in certain Green Hills Software program names
    275             Regex::Expression FilePathParens{ R"(\([CDEF]:\\(.+?\\)*[^\s]*\\?\))", reOptions }; // remove file paths within parentheses
    276             Regex::Expression FilePathQuotes{ R"("[CDEF]:\\(.+?\\)*[^\s]*\\?")", reOptions }; // remove file paths within quotes
    277             Regex::Expression Roblox{ R"((?<=^ROBLOX\s(PLAYER|STUDIO))(\sFOR\s.*))", reOptions }; // for Roblox programs
    278             Regex::Expression Bomgar{ R"((?<=^BOMGAR\s(JUMP CLIENT|(ACCESS|REPRESENTATIVE) CONSOLE|BUTTON)|^EMBEDDED CALLBACK)(\s.*))", reOptions }; // for Bomgar programs
    279             Regex::Expression AcronymSeparators{ R"((?:(?<=^\p{L})|(?<=\P{L}\p{L}))(\.|\/)(?=\p{L}(?:\P{L}|$)))", reOptions };
    280             Regex::Expression NonLetters{ R"((?<=^|\s)[^\p{L}]+(?=\s|$))", reOptions }; // remove all non-letters not attached to 
    281             Regex::Expression ProgramNameSplit{ R"([^\p{L}\p{Nd}\+\&])", reOptions }; // used to separate 'words' in program names
    282             Regex::Expression PublisherNameSplit{ R"([^\p{L}\p{Nd}])", reOptions }; // used to separate 'words' in publisher names
    283 
    284             const std::vector<Regex::Expression*> ProgramNameRegexes
    285             {
    286                 &Roblox,
    287                 &Bomgar,
    288                 &PrefixParens,
    289                 &EmptyParens,
    290                 &FilePathGHS,
    291                 &FilePathParens,
    292                 &FilePathQuotes,
    293                 &FilePath,
    294                 &VersionLetter,
    295                 &VersionDelimited,
    296                 &Version,
    297                 &EN,
    298                 &NonNestedBracket,
    299                 &BracketEnclosed,
    300                 &URIProtocol,
    301                 &LeadingSymbols,
    302                 &TrailingSymbols
    303             };
    304 
    305             const std::vector<Regex::Expression*> PublisherNameRegexes
    306             {
    307                 &VersionDelimited,
    308                 &Version,
    309                 &NonNestedBracket,
    310                 &BracketEnclosed,
    311                 &URIProtocol,
    312                 &NonLetters,
    313                 &TrailingNonLetters,
    314                 &AcronymSeparators
    315             };
    316 
    317             // Add values here but use Locales in code.
    318             const std::vector<std::wstring_view> LocaleViews
    319             {
    320                 L"AF-ZA", L"AM-ET", L"AR-AE", L"AR-BH", L"AR-DZ", L"AR-EG", L"AR-IQ", L"AR-JO", L"AR-KW", L"AR-LB", L"AR-LY",
    321                 L"AR-MA", L"ARN-CL", L"AR-OM", L"AR-QA", L"AR-SA", L"AR-SY", L"AR-TN", L"AR-YE", L"AS-IN", L"BA-RU", L"BE-BY",
    322                 L"BG-BG", L"BN-BD", L"BN-IN", L"BO-CN", L"BR-FR", L"CA-ES", L"CA-ES-VALENCIA",
    323                 L"CO-FR", L"CS-CZ", L"CY-GB", L"DA-DK", L"DE-AT",
    324                 L"DE-CH", L"DE-DE", L"DE-LI", L"DE-LU", L"DSB-DE", L"DV-MV", L"EL-GR", L"EN-AU", L"EN-BZ", L"EN-CA", L"EN-GB",
    325                 L"EN-IE", L"EN-IN", L"EN-JM", L"EN-MY", L"EN-NZ", L"EN-PH", L"EN-SG", L"EN-TT", L"EN-US", L"EN-ZA", L"EN-ZW",
    326                 L"ES-AR", L"ES-BO", L"ES-CL", L"ES-CO", L"ES-CR", L"ES-DO", L"ES-EC", L"ES-ES", L"ES-GT", L"ES-HN", L"ES-MX",
    327                 L"ES-NI", L"ES-PA", L"ES-PE", L"ES-PR", L"ES-PY", L"ES-SV", L"ES-US", L"ES-UY", L"ES-VE", L"ET-EE", L"EU-ES",
    328                 L"FA-IR", L"FI-FI", L"FIL-PH", L"FO-FO", L"FR-BE", L"FR-CA", L"FR-CH", L"FR-FR", L"FR-LU", L"FR-MC", L"FY-NL",
    329                 L"GA-IE", L"GD-DB", L"GL-ES", L"GSW-FR", L"GU-IN", L"HE-IL", L"HI-IN", L"HR-BA", L"HR-HR", L"HSB-DE", L"HU-HU",
    330                 L"HY-AM", L"ID-ID", L"IG-NG", L"II-CN", L"IS-IS", L"IT-CH", L"IT-IT", L"JA-JP", L"KA-GE", L"KK-KZ", L"KL-GL",
    331                 L"KM-KH", L"KN-IN", L"KOK-IN", L"KO-KR", L"KY-KG", L"LB-LU", L"LO-LA", L"LT-LT", L"LV-LV", L"MI-NZ", L"MK-MK",
    332                 L"ML-IN", L"MN-MN", L"MOH-CA", L"MR-IN", L"MS-BN", L"MS-MY", L"MT-MT", L"NB-NO", L"NE-NP", L"NL-BE", L"NL-NL",
    333                 L"NN-NO", L"NSO-ZA", L"OC-FR", L"OR-IN", L"PA-IN", L"PL-PL", L"PRS-AF", L"PS-AF", L"PT-BR", L"PT-PT", L"QUT-GT",
    334                 L"QUZ-BO", L"QUZ-EC", L"QUZ-PE", L"RM-CH", L"RO-RO", L"RU-RU", L"RW-RW", L"SAH-RU", L"SA-IN", L"SE-FI", L"SE-NO",
    335                 L"SE-SE", L"SI-LK", L"SK-SK", L"SL-SI", L"SMA-NO", L"SMA-SE", L"SMJ-NO", L"SMJ-SE", L"SMN-FI", L"SMS-FI", L"SQ-AL",
    336                 L"SV-FI", L"SV-SE", L"SW-KE", L"SYR-SY", L"TA-IN", L"TE-IN", L"TH-TH", L"TK-TM", L"TN-ZA", L"TR-TR", L"TT-RU",
    337                 L"UG-CN", L"UK-UA", L"UR-PK", L"VI-VN", L"WO-SN", L"XH-ZA", L"YO-NG", L"ZH-CN", L"ZH-HK", L"ZH-MO", L"ZH-SG",
    338                 L"ZH-TW", L"ZU-ZA", L"AZ-CYRL-AZ", L"AZ-LATN-AZ", L"BS-CYRL-BA", L"BS-LATN-BA", L"HA-LATN-NG", L"IU-CANS-CA",
    339                 L"IU-LATN-CA", L"MN-MONG-CN", L"SR-CYRL-BA", L"SR-CYRL-CS", L"SR-CYRL-ME", L"SR-CYRL-RS", L"SR-LATN-BA",
    340                 L"SR-LATN-CS", L"SR-LATN-ME", L"SR-LATN-RS", L"TG-CYRL-TJ", L"TZM-LATN-DZ", L"UZ-CYRL-UZ", L"UZ-LATN-UZ",
    341             };
    342 
    343             // The folded and sorted version of LocaleViews.
    344             const std::vector<std::wstring> Locales;
    345 
    346             // Add values here but use LegalEntitySuffixes in code.
    347             const std::vector<std::wstring_view> LegalEntitySuffixViews
    348             {
    349                 // Acronyms
    350                 L"AB", L"AD", L"AG", L"APS", L"AS", L"ASA", L"BV", L"CO", L"CV", L"DOO", L"eV", L"GES", L"GESMBH", L"GMBH", L"INC", L"KG",
    351                 L"KS", L"PS", L"LLC", L"LP", L"LTD", L"LTDA", L"MBH", L"NV", L"PLC", L"SL", L"PTY", L"PVT", L"SA", L"SARL",
    352                 L"SC", L"SCA", L"SL", L"SP", L"SPA", L"SRL", L"SRO",
    353 
    354                 // Words
    355                 L"COMPANY", L"CORP", L"CORPORATION", L"HOLDING", L"HOLDINGS", L"INCORPORATED", L"LIMITED", L"SUBSIDIARY"
    356             };
    357 
    358             // The folded and sorted version of LocaleViews.
    359             const std::vector<std::wstring> LegalEntitySuffixes;
    360 
    361             const bool PreserveWhiteSpace;
    362 
    363             static std::vector<std::wstring> FoldAndSort(const std::vector<std::wstring_view>& input)
    364             {
    365                 std::vector<std::wstring> result;
    366                 std::transform(input.begin(), input.end(), std::back_inserter(result), [](const std::wstring_view wsv) { return Utility::ConvertToUTF16(Utility::FoldCase(wsv)); });
    367                 std::sort(result.begin(), result.end());
    368                 return result;
    369             }
    370 
    371             InterimNameNormalizationResult NormalizeNameInternal(std::string_view name) const
    372             {
    373                 InterimNameNormalizationResult result;
    374                 result.Name = PrepareForValidation(name);
    375                 while (Unwrap(result.Name)); // remove wrappers
    376 
    377                 // handle (large majority of) SAP Business Object programs
    378                 if (SAPPackage.IsMatch(result.Name))
    379                 {
    380                     return result;
    381                 }
    382 
    383                 result.Architecture = RemoveArchitecture(result.Name);
    384                 result.Locale = RemoveLocale(result.Name);
    385 
    386                 // Extract KB numbers from their parens and preserve them
    387                 result.Name = KBNumbers.Replace(result.Name, L"$1");
    388 
    389                 // Repeatedly remove matches for the regexes to create the minimum name
    390                 while (RemoveAll(ProgramNameRegexes, result.Name));
    391 
    392                 auto tokens = Split(ProgramNameSplit, result.Name, LegalEntitySuffixes);
    393 
    394                 // Re-join the tokens and drop all undesired characters
    395                 if (PreserveWhiteSpace)
    396                 {
    397                     result.Name = Join(tokens, L" ");
    398                     Remove(NonLetterDigitOrSpace, result.Name);
    399                 }
    400                 else
    401                 {
    402                     result.Name = Join(tokens);
    403                     Remove(NonLettersAndDigits, result.Name);
    404                 }
    405 
    406                 return result;
    407             }
    408 
    409             InterimPublisherNormalizationResult NormalizePublisherInternal(std::string_view publisher) const
    410             {
    411                 InterimPublisherNormalizationResult result;
    412 
    413                 result.Publisher = PrepareForValidation(publisher);
    414                 while (Unwrap(result.Publisher)); // remove wrappers
    415 
    416                 while (RemoveAll(PublisherNameRegexes, result.Publisher));
    417 
    418                 auto tokens = Split(PublisherNameSplit, result.Publisher, LegalEntitySuffixes, true);
    419 
    420                 // Re-join the tokens and drop all undesired characters
    421                 if (PreserveWhiteSpace)
    422                 {
    423                     result.Publisher = Join(tokens, L" ");
    424                     Remove(NonLetterDigitOrSpace, result.Publisher);
    425                 }
    426                 else
    427                 {
    428                     result.Publisher = Join(tokens);
    429                     Remove(NonLettersAndDigits, result.Publisher);
    430                 }
    431 
    432                 return result;
    433             }
    434 
    435         public:
    436             NormalizationInitial(bool preserveWhiteSpace) : Locales(FoldAndSort(LocaleViews)), LegalEntitySuffixes(FoldAndSort(LegalEntitySuffixViews)), PreserveWhiteSpace(preserveWhiteSpace)
    437             {
    438             }
    439 
    440             NormalizedName Normalize(std::string_view name, std::string_view publisher) const override
    441             {
    442                 InterimNameNormalizationResult nameResult = NormalizeNameInternal(name);
    443                 InterimPublisherNormalizationResult pubResult = NormalizePublisherInternal(publisher);
    444 
    445                 NormalizedName result;
    446                 result.Name(ConvertToUTF8(nameResult.Name));
    447                 result.Architecture(nameResult.Architecture);
    448                 result.Locale(ConvertToUTF8(nameResult.Locale));
    449                 result.Publisher(ConvertToUTF8(pubResult.Publisher));
    450 
    451                 return result;
    452             }
    453 
    454             NormalizedName NormalizeName(std::string_view name) const override
    455             {
    456                 InterimNameNormalizationResult nameResult = NormalizeNameInternal(name);
    457 
    458                 NormalizedName result;
    459                 result.Name(ConvertToUTF8(nameResult.Name));
    460                 result.Architecture(nameResult.Architecture);
    461                 result.Locale(ConvertToUTF8(nameResult.Locale));
    462 
    463                 return result;
    464             }
    465 
    466             std::string NormalizePublisher(std::string_view publisher) const override
    467             {
    468                 InterimPublisherNormalizationResult pubResult = NormalizePublisherInternal(publisher);
    469 
    470                 return ConvertToUTF8(pubResult.Publisher);
    471             }
    472         };
    473     }
    474 
    475     NameNormalizer::NameNormalizer(NormalizationVersion version)
    476     {
    477         switch (version)
    478         {
    479         case AppInstaller::Utility::NormalizationVersion::Initial:
    480             m_normalizer = std::make_unique<NormalizationInitial>(false);
    481             break;
    482         case AppInstaller::Utility::NormalizationVersion::InitialPreserveWhiteSpace:
    483             m_normalizer = std::make_unique<NormalizationInitial>(true);
    484             break;
    485         default:
    486             THROW_HR(E_INVALIDARG);
    487         }
    488     }
    489 
    490     NormalizedName NameNormalizer::Normalize(std::string_view name, std::string_view publisher) const
    491     {
    492         return m_normalizer->Normalize(name, publisher);
    493     }
    494 
    495     NormalizedName NameNormalizer::NormalizeName(std::string_view name) const
    496     {
    497         return m_normalizer->NormalizeName(name);
    498     }
    499 
    500     std::string NameNormalizer::NormalizePublisher(std::string_view publisher) const
    501     {
    502         return m_normalizer->NormalizePublisher(publisher);
    503     }
    504 
    505     std::string NormalizedName::GetNormalizedName(NormalizationField fieldsToInclude) const
    506     {
    507         std::string result = Name();
    508 
    509         if (WI_IsFlagSet(fieldsToInclude, NormalizationField::Architecture) && m_arch != Utility::Architecture::Unknown)
    510         {
    511             result += '(' + std::string(Utility::ToString(m_arch)) + ')';
    512         }
    513 
    514         return result;
    515     }
    516 
    517     NormalizationField NormalizedName::GetNormalizedFields() const
    518     {
    519         NormalizationField result = NormalizationField::None;
    520 
    521         if (m_arch != Utility::Architecture::Unknown)
    522         {
    523             result |= NormalizationField::Architecture;
    524         }
    525 
    526         return result;
    527     }
    528 }