Converted over to new name sanitization routine

This commit is contained in:
2026-03-28 20:04:48 -04:00
parent 88fa260c9d
commit 22fe60f431
3 changed files with 53 additions and 64 deletions

View File

@@ -221,10 +221,10 @@ void WingTokenizer::PrintEverything() const
} }
} }
FString WingTokenizer::ExternalizeID(const FString &S) FString WingTokenizer::ExternalizeID(const FString &InternalID)
{ {
TStringBuilder<512> Result; TStringBuilder<512> Result;
for (TCHAR Ch : S) for (TCHAR Ch : InternalID)
{ {
if (Ch == ' ') Result.AppendChar('.'); if (Ch == ' ') Result.AppendChar('.');
else if (WingCharacterClasses::GetCat(Ch) == Cat::Identifier) Result.AppendChar(Ch); else if (WingCharacterClasses::GetCat(Ch) == Cat::Identifier) Result.AppendChar(Ch);
@@ -247,15 +247,26 @@ FString WingTokenizer::ExternalizeID(const FString &S)
return Result.ToString(); 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(); Error.Empty();
FString Result = TokenizeIdentifier(Input, Error); FString InternalID = TokenizeIdentifier(Input, Error);
// If there's already an error, annotate with context // If there's already an error, annotate with context
if (!Error.IsEmpty()) 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(); return FString();
} }
// If the identifier tokenizer stops before consuming the whole // 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]); Cat Category = WingCharacterClasses::GetCat(Input[0]);
if (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) else if (Category == Cat::Punctuation)
{ {
Error = FString::Printf(TEXT("ERROR parsing id %s: in ids, these marks must be escaped: %s"), 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) 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(); return FString();
} }
// One last error case: empty input // One last error case: empty input
if (Result.IsEmpty()) if (InternalID.IsEmpty())
{ {
Error = TEXT("ERROR: Empty identifiers are not allowed"); Error = TEXT("ERROR: Empty identifiers are not allowed");
return FString(); return FString();
} }
return Result; return InternalID;
} }
FString WingTokenizer::CheckInternalizeID(const FString &S) FString WingTokenizer::CheckInternalizeID(const FString &ExternalID)
{ {
FString Error; FString Error;
FString Result = TryInternalizeID(S, Error); FString InternalID = TryInternalizeID(ExternalID, Error);
if (!Error.IsEmpty()) if (!Error.IsEmpty())
{ {
UWingServer::Printf(TEXT("%s\n"), *Error); UWingServer::Printf(TEXT("%s\n"), *Error);
UWingServer::SuggestManual(WingManual::Section::IdentifierSanitization); UWingServer::SuggestManual(WingManual::Section::IdentifierSanitization);
} }
return Result; return InternalID;
} }

View File

@@ -4,6 +4,7 @@
#include "WingTypes.h" #include "WingTypes.h"
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingTokenizer.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Engine/MemberReference.h" #include "Engine/MemberReference.h"
#include "Engine/World.h" #include "Engine/World.h"
@@ -56,50 +57,31 @@
FString WingUtils::SanitizeName(const FString &InName) FString WingUtils::SanitizeName(const FString &InName)
{ {
FString Name = InName; return WingTokenizer::ExternalizeID(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;
} }
FString WingUtils::UnsanitizeName(const FString &InName) FString WingUtils::UnsanitizeName(const FString &InName)
{ {
FString Name = InName; FString Error;
int32 Dst = 0; return WingTokenizer::TryInternalizeID(InName, Error);
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 WingUtils::SanitizeName(FName Name) 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) FString WingUtils::StandardizeMenuItem(const FString &Item)
@@ -128,16 +110,7 @@ FString WingUtils::StandardizeMenuItem(const FString &Item)
return Sanitized; 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 // Name Lookup

View File

@@ -129,7 +129,12 @@ struct WingTokenizer
// Convert an internal ID into an external ID. // Convert an internal ID into an external ID.
// Spaces are converted to periods. Any other // Spaces are converted to periods. Any other
// non-identifier character is HTML escaped. // 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. // Convert an external ID into an internal ID.
// Periods are converted back to spaces. HTML escapes // Periods are converted back to spaces. HTML escapes
@@ -137,13 +142,13 @@ struct WingTokenizer
// fail, for example, if the external name contains an // fail, for example, if the external name contains an
// invalid HTML escape. If it does, returns empty // invalid HTML escape. If it does, returns empty
// string and sets the error message. // 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 // Calls TryInternalizeName. If this generates an
// error, prints the error message, suggests the manual // error, prints the error message, suggests the manual
// entry on identifier sanitization, and returns empty // entry on identifier sanitization, and returns empty
// string. // string.
static FString CheckInternalizeID(const FString &S); static FString CheckInternalizeID(const FString &ExternalID);
// Print all tokens to the log for debugging. // Print all tokens to the log for debugging.
void PrintEverything() const; void PrintEverything() const;