diff --git a/Dalamud.Test/Dalamud.Test.csproj b/Dalamud.Test/Dalamud.Test.csproj index 7091ebe26..1ae18862f 100644 --- a/Dalamud.Test/Dalamud.Test.csproj +++ b/Dalamud.Test/Dalamud.Test.csproj @@ -43,6 +43,7 @@ + diff --git a/Dalamud.Test/Plugin/Sanitizer/SanitizerTests.cs b/Dalamud.Test/Plugin/Sanitizer/SanitizerTests.cs new file mode 100644 index 000000000..b0ead50a2 --- /dev/null +++ b/Dalamud.Test/Plugin/Sanitizer/SanitizerTests.cs @@ -0,0 +1,67 @@ +// ReSharper disable StringLiteralTypo + +using System.Linq; +using NUnit.Framework; + +namespace Dalamud.Test.Plugin.Sanitizer { + [TestFixture] + public class SanitizerTests { + global::Dalamud.Plugin.Sanitizer.Sanitizer sanitizer; + + [Test] + public void Sanitize_NormalString_NoChange() { + sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(ClientLanguage.English); + const string str = "Pixie Cotton Hood of Healing"; + var sanitizedString = sanitizer.Sanitize(str); + Assert.AreEqual(str, sanitizedString); + } + + [Test] + public void Sanitize_DESpecialCharacters_Sanitized() { + sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(ClientLanguage.German); + const string str = @"Anemos-Panzerhandschuhe des Drachenbluts"; + var sanitizedString = sanitizer.Sanitize(str); + Assert.AreEqual(@"Anemos-Panzerhandschuhe des Drachenbluts", sanitizedString); + } + + [Test] + public void Sanitize_FRSpecialCharacters_Sanitized() { + sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(ClientLanguage.French); + const string str = @"Le Diademe: terrains de chasse|Le Diademe: terrains de chasse"; + var sanitizedString = sanitizer.Sanitize(str); + Assert.AreEqual(@"Le Diademe: terrains de chasse|Le Diademe: terrains de chasse", sanitizedString); + } + + [Test] + public void Sanitize_SpecifyLanguage_Sanitized() { + sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(ClientLanguage.French); + const string str = @"Le Diademe: terrains de chasse|Le Diademe: terrains de chasse"; + var sanitizedString = sanitizer.Sanitize(str, ClientLanguage.French); + Assert.AreEqual(@"Le Diademe: terrains de chasse|Le Diademe: terrains de chasse", sanitizedString); + } + + [Test] + public void Sanitize_List_Sanitized() { + sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(ClientLanguage.German); + const string str = @"Anemos-Panzerhandschuhe des Drachenbluts"; + var sanitizedStrings = sanitizer.Sanitize(new[] {str}); + Assert.AreEqual(@"Anemos-Panzerhandschuhe des Drachenbluts", sanitizedStrings.First()); + } + + [Test] + public void Sanitize_SpecifyLanguageList_Sanitized() { + sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(ClientLanguage.German); + const string str = @"Anemos-Panzerhandschuhe des Drachenbluts"; + var sanitizedStrings = sanitizer.Sanitize(new[] {str}, ClientLanguage.German); + Assert.AreEqual(@"Anemos-Panzerhandschuhe des Drachenbluts", sanitizedStrings.First()); + } + + [Test] + public void Sanitize_UseAlternateLanguage_Sanitized() { + sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(ClientLanguage.English); + const string str = @"Anemos-Panzerhandschuhe des Drachenbluts"; + var sanitizedStrings = sanitizer.Sanitize(new[] {str}, ClientLanguage.German); + Assert.AreEqual(@"Anemos-Panzerhandschuhe des Drachenbluts", sanitizedStrings.First()); + } + } +} diff --git a/Dalamud.sln.DotSettings b/Dalamud.sln.DotSettings index 8b8890e01..af8f4d833 100644 --- a/Dalamud.sln.DotSettings +++ b/Dalamud.sln.DotSettings @@ -47,4 +47,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index c0d9f12e3..dab5995e7 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -13,6 +13,7 @@ using Dalamud.Game.ClientState; using Dalamud.Game.Command; using Dalamud.Game.Internal; using Dalamud.Interface; +using Dalamud.Plugin.Sanitizer; namespace Dalamud.Plugin { @@ -48,6 +49,7 @@ namespace Dalamud.Plugin this.pluginName = pluginName; this.configs = configs; + this.Sanitizer = new Sanitizer.Sanitizer(this.Data.Language); this.UiLanguage = this.dalamud.Configuration.LanguageOverride; dalamud.LocalizationManager.OnLocalizationChanged += this.OnLocalizationChanged; } @@ -132,6 +134,11 @@ namespace Dalamud.Plugin /// public string UiLanguage { get; private set; } + /// + /// Gets serializer class with functions to remove special characters from strings. + /// + public ISanitizer Sanitizer { get; } + /// /// Gets the action that should be executed when any plugin sends a message. /// diff --git a/Dalamud/Plugin/Sanitizer/ISanitizer.cs b/Dalamud/Plugin/Sanitizer/ISanitizer.cs new file mode 100644 index 000000000..21fe8ab77 --- /dev/null +++ b/Dalamud/Plugin/Sanitizer/ISanitizer.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Dalamud.Plugin.Sanitizer +{ + /// + /// Sanitize strings to remove soft hyphens and other special characters. + /// + public interface ISanitizer + { + /// + /// Creates a sanitized string using current clientLanguage. + /// + /// An unsanitized string to sanitize. + /// A sanitized string. + string Sanitize(string unsanitizedString); + + /// + /// Creates a sanitized string using request clientLanguage. + /// + /// An unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A sanitized string. + string Sanitize(string unsanitizedString, ClientLanguage clientLanguage); + + /// + /// Creates a list of sanitized strings using current clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// A list of sanitized strings. + IEnumerable Sanitize(IEnumerable unsanitizedStrings); + + /// + /// Creates a list of sanitized strings using requested clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A list of sanitized strings. + IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage); + } +} diff --git a/Dalamud/Plugin/Sanitizer/Sanitizer.cs b/Dalamud/Plugin/Sanitizer/Sanitizer.cs new file mode 100644 index 000000000..66b2c413f --- /dev/null +++ b/Dalamud/Plugin/Sanitizer/Sanitizer.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Dalamud.Plugin.Sanitizer +{ + /// + /// Sanitize strings to remove soft hyphens and other special characters. + /// + public class Sanitizer : ISanitizer + { + private readonly KeyValuePair softHyphen = new KeyValuePair("\u0002\u0016\u0001\u0003", string.Empty); + private readonly KeyValuePair emphasisOpen = new KeyValuePair("\u0002\u001A\u0003", string.Empty); + private readonly KeyValuePair emphasisClose = new KeyValuePair("\u0002\u001A\u0001\u0003", string.Empty); + private readonly KeyValuePair indent = new KeyValuePair("\u0002\u001D\u0001\u0003", string.Empty); + private readonly KeyValuePair dagger = new KeyValuePair("\u0020\u2020", string.Empty); + private readonly KeyValuePair ligatureOE = new KeyValuePair("\u0153", "\u006F\u0065"); + private readonly List> defaultSanitizationList; + + /// + /// Initializes a new instance of the class. + /// + /// Default clientLanguage for sanitizing strings. + public Sanitizer(ClientLanguage clientLanguage) + { + this.defaultSanitizationList = this.BuildSanitizationList(clientLanguage); + } + + /// + /// Creates a sanitized string using current clientLanguage. + /// + /// An unsanitized string to sanitize. + /// A sanitized string. + public string Sanitize(string unsanitizedString) + { + return this.defaultSanitizationList == null ? unsanitizedString : ApplySanitizationList(unsanitizedString, this.defaultSanitizationList); + } + + /// + /// Creates a sanitized string using request clientLanguage. + /// + /// An unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A sanitized string. + public string Sanitize(string unsanitizedString, ClientLanguage clientLanguage) + { + var newSanitizationList = this.BuildSanitizationList(clientLanguage); + return newSanitizationList == null ? unsanitizedString : ApplySanitizationList(unsanitizedString, newSanitizationList); + } + + /// + /// Creates a list of sanitized strings using current clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// A list of sanitized strings. + public IEnumerable Sanitize(IEnumerable unsanitizedStrings) + { + return this.defaultSanitizationList == null ? unsanitizedStrings.Select(unsanitizedString => unsanitizedString) : + unsanitizedStrings.Select(unsanitizedString => ApplySanitizationList(unsanitizedString, this.defaultSanitizationList)); + } + + /// + /// Creates a list of sanitized strings using requested clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A list of sanitized strings. + public IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) + { + var newSanitizationList = this.BuildSanitizationList(clientLanguage); + return newSanitizationList == null ? unsanitizedStrings.Select(unsanitizedString => unsanitizedString) : + unsanitizedStrings.Select(unsanitizedString => ApplySanitizationList(unsanitizedString, newSanitizationList)); + } + + private static string ApplySanitizationList(string unsanitizedString, IEnumerable> sanitizationList) + { + var sanitizedValue = new StringBuilder(unsanitizedString); + foreach (var item in sanitizationList) sanitizedValue.Replace(item.Key, item.Value); + return sanitizedValue.ToString(); + } + + private List> BuildSanitizationList(ClientLanguage clientLanguage) + { + switch (clientLanguage) + { + case ClientLanguage.Japanese: + break; + case ClientLanguage.English: + break; + case ClientLanguage.German: + return new List> + { + this.softHyphen, + this.emphasisOpen, + this.emphasisClose, + this.dagger, + }; + case ClientLanguage.French: + return new List> + { + this.softHyphen, + this.indent, + this.ligatureOE, + }; + default: + throw new ArgumentOutOfRangeException(); + } + + return null; + } + } +}