diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp index 3384e3c2..4405dc6e 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingTokenizer.cpp @@ -221,10 +221,10 @@ void WingTokenizer::PrintEverything() const } } -FString WingTokenizer::ExternalizeID(const FString &S) +FString WingTokenizer::ExternalizeID(const FString &InternalID) { TStringBuilder<512> Result; - for (TCHAR Ch : S) + for (TCHAR Ch : InternalID) { if (Ch == ' ') Result.AppendChar('.'); else if (WingCharacterClasses::GetCat(Ch) == Cat::Identifier) Result.AppendChar(Ch); @@ -247,15 +247,26 @@ FString WingTokenizer::ExternalizeID(const FString &S) return Result.ToString(); } -FString WingTokenizer::TryInternalizeID(const FString &S, FString &Error) +bool WingTokenizer::WouldExternalizeReadably(const FString &InternalID) { - FStringView Input(S); + if (InternalID.IsEmpty()) return false; + for (TCHAR Ch : InternalID) + { + if (Ch == ' ') continue; + if (WingCharacterClasses::GetCat(Ch) != Cat::Identifier) return false; + } + return true; +} + +FString WingTokenizer::TryInternalizeID(const FString &ExternalID, FString &Error) +{ + FStringView Input(ExternalID); Error.Empty(); - FString Result = TokenizeIdentifier(Input, Error); + FString InternalID = TokenizeIdentifier(Input, Error); // If there's already an error, annotate with context if (!Error.IsEmpty()) { - Error = FString::Printf(TEXT("ERROR parsing id %s: %s"), *S, *Error); + Error = FString::Printf(TEXT("ERROR parsing id %s: %s"), *ExternalID, *Error); return FString(); } // If the identifier tokenizer stops before consuming the whole @@ -266,37 +277,37 @@ FString WingTokenizer::TryInternalizeID(const FString &S, FString &Error) Cat Category = WingCharacterClasses::GetCat(Input[0]); if (Input[0] == ' ') { - Error = FString::Printf(TEXT("ERROR parsing id %s: in ids, spaces must be escaped"), *S); + Error = FString::Printf(TEXT("ERROR parsing id %s: in ids, spaces must be escaped"), *ExternalID); } else if (Category == Cat::Punctuation) { Error = FString::Printf(TEXT("ERROR parsing id %s: in ids, these marks must be escaped: %s"), - *S, WingCharacterClasses::PunctuationString); + *ExternalID, WingCharacterClasses::PunctuationString); } else if (Category == Cat::Control) { - Error = FString::Printf(TEXT("ERROR parsing id %s: in ids, control characters must be escaped"), *S); + Error = FString::Printf(TEXT("ERROR parsing id %s: in ids, control characters must be escaped"), *ExternalID); } - else Error = FString::Printf(TEXT("ERROR parsing id %s: unparseable character in id"), *S); + else Error = FString::Printf(TEXT("ERROR parsing id %s: unparseable character in id"), *ExternalID); return FString(); } // One last error case: empty input - if (Result.IsEmpty()) + if (InternalID.IsEmpty()) { Error = TEXT("ERROR: Empty identifiers are not allowed"); return FString(); } - return Result; + return InternalID; } -FString WingTokenizer::CheckInternalizeID(const FString &S) +FString WingTokenizer::CheckInternalizeID(const FString &ExternalID) { FString Error; - FString Result = TryInternalizeID(S, Error); + FString InternalID = TryInternalizeID(ExternalID, Error); if (!Error.IsEmpty()) { UWingServer::Printf(TEXT("%s\n"), *Error); UWingServer::SuggestManual(WingManual::Section::IdentifierSanitization); } - return Result; + return InternalID; } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 47d5b462..641c1fdb 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -4,6 +4,7 @@ #include "WingTypes.h" #include "WingServer.h" #include "WingHandler.h" +#include "WingTokenizer.h" #include "Engine/Blueprint.h" #include "Engine/MemberReference.h" #include "Engine/World.h" @@ -56,50 +57,31 @@ FString WingUtils::SanitizeName(const FString &InName) { - FString Name = InName; - int32 Dst = 0; - for (int32 Src = 0; Src < Name.Len(); Src++) - { - TCHAR c = Name[Src]; - if (c < 0x20 || c == 0x7F) continue; - if (c == ' ') c=L'·'; - if (c == '<') c=L'◁'; - if (c == '>') c=L'▷'; - if (c == '(') c=L'❨'; - if (c == ')') c=L'❩'; - if (c == '=') c=L'≡'; - if (c == ',') c=L'▾'; - Name[Dst++] = c; - } - if (Dst == 0) Name[Dst++] = L'·'; - Name.LeftInline(Dst); - return Name; + return WingTokenizer::ExternalizeID(InName); } FString WingUtils::UnsanitizeName(const FString &InName) { - FString Name = InName; - int32 Dst = 0; - for (int32 Src = 0; Src < Name.Len(); Src++) - { - TCHAR c = Name[Src]; - if (c < 0x20 || c == 0x7F) continue; - if (c == L'·') c=' '; - if (c == L'◁') c='<'; - if (c == L'▷') c='>'; - if (c == L'❨') c='('; - if (c == L'❩') c=')'; - if (c == L'≡') c='='; - if (c == L'▾') c=','; - Name[Dst++] = c; - } - Name.LeftInline(Dst); - return Name; + FString Error; + return WingTokenizer::TryInternalizeID(InName, Error); } FString WingUtils::SanitizeName(FName Name) { - return SanitizeName(Name.ToString()); + return WingTokenizer::ExternalizeID(Name.ToString()); +} + +FString WingUtils::CheckProposedName(const FString &ExternalID) +{ + FString InternalID = WingTokenizer::CheckInternalizeID(ExternalID); + if (!InternalID.IsEmpty() && WingTokenizer::WouldExternalizeReadably(InternalID)) + { + UWingServer::Printf(TEXT("ERROR: id %s would not be a readable id, may not create item with this name"), + *ExternalID); + UWingServer::SuggestManual(WingManual::Section::IdentifierSanitization); + return FString(); + } + return InternalID; } FString WingUtils::StandardizeMenuItem(const FString &Item) @@ -128,16 +110,7 @@ FString WingUtils::StandardizeMenuItem(const FString &Item) return Sanitized; } -FString WingUtils::CheckProposedName(const FString &Name) -{ - FString Unsanitized = UnsanitizeName(Name); - if ((Unsanitized.IsEmpty()) || (SanitizeName(Unsanitized) != Name)) - { - UWingServer::Printf(TEXT("Names must not contain control characters or be empty\n")); - return FString(); - } - return Unsanitized; -} + // ============================================================ // Name Lookup diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h b/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h index f6904368..9479dab1 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h @@ -129,7 +129,12 @@ struct WingTokenizer // Convert an internal ID into an external ID. // Spaces are converted to periods. Any other // non-identifier character is HTML escaped. - static FString ExternalizeID(const FString &S); + static FString ExternalizeID(const FString &InternalID); + + // Return true if the internal ID would convert + // to a readable, easy-to-understand external ID without + // HTML escape sequences. + static bool WouldExternalizeReadably(const FString &InternalID); // Convert an external ID into an internal ID. // Periods are converted back to spaces. HTML escapes @@ -137,13 +142,13 @@ struct WingTokenizer // fail, for example, if the external name contains an // invalid HTML escape. If it does, returns empty // string and sets the error message. - static FString TryInternalizeID(const FString &S, FString &Error); + static FString TryInternalizeID(const FString &ExternalID, FString &Error); // Calls TryInternalizeName. If this generates an // error, prints the error message, suggests the manual // entry on identifier sanitization, and returns empty // string. - static FString CheckInternalizeID(const FString &S); + static FString CheckInternalizeID(const FString &ExternalID); // Print all tokens to the log for debugging. void PrintEverything() const;