diff --git a/Dalamud.Test/Dalamud.Test.csproj b/Dalamud.Test/Dalamud.Test.csproj
index ab73840ba..96ae59c05 100644
--- a/Dalamud.Test/Dalamud.Test.csproj
+++ b/Dalamud.Test/Dalamud.Test.csproj
@@ -59,6 +59,7 @@
+
diff --git a/Dalamud.Test/Plugin/Sanitizer/SanitizerTests.cs b/Dalamud.Test/Plugin/Sanitizer/SanitizerTests.cs
new file mode 100644
index 000000000..25aefd183
--- /dev/null
+++ b/Dalamud.Test/Plugin/Sanitizer/SanitizerTests.cs
@@ -0,0 +1,32 @@
+// ReSharper disable StringLiteralTypo
+
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Dalamud.Test.Plugin.Sanitizer {
+
+ public class SanitizerTests {
+ private global::Dalamud.Plugin.Sanitizer.Sanitizer sanitizer;
+
+ [Theory]
+ [InlineData( ClientLanguage.English, "Pixie Cotton Hood of Healing", "Pixie Cotton Hood of Healing" )]
+ [InlineData( ClientLanguage.Japanese, "アラガントームストーン:真理", "アラガントームストーン:真理" )]
+ [InlineData( ClientLanguage.German, "Anemos-Pan\x02\x16\x01\x03zer\x02\x16\x01\x03hand\x02\x16\x01\x03schu\x02\x16\x01\x03he des Drachenbluts", "Anemos-Panzerhandschuhe des Drachenbluts" )]
+ [InlineData( ClientLanguage.German, "Bienen-Spatha †", "Bienen-Spatha" )]
+ [InlineData( ClientLanguage.French, "Le Diademe\x02\x1D\x01\x03: terrains de chasse|Le Diademe\x02\x1D\x01\x03: terrains de chasse", "Le Diademe: terrains de chasse|Le Diademe: terrains de chasse" )]
+ [InlineData( ClientLanguage.French, "Cuir de bœuf", "Cuir de boeuf" )]
+ public void StringsAreSanitizedCorrectly(
+ ClientLanguage clientLanguage, string unsanitizedString, string sanitizedString)
+ {
+ var sanitizedStrings = new List {unsanitizedString};
+ sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(clientLanguage);
+ Assert.Equal(sanitizedString, sanitizer.Sanitize(unsanitizedString));
+ Assert.Equal(sanitizedString, sanitizer.Sanitize(sanitizedStrings).First());
+
+ sanitizer = new global::Dalamud.Plugin.Sanitizer.Sanitizer(ClientLanguage.English);
+ Assert.Equal(sanitizedString, sanitizer.Sanitize(unsanitizedString, clientLanguage));
+ Assert.Equal(sanitizedString, sanitizer.Sanitize(sanitizedStrings, clientLanguage).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/Data/DataManager.cs b/Dalamud/Data/DataManager.cs
index 6ea358135..c8dced858 100644
--- a/Dalamud/Data/DataManager.cs
+++ b/Dalamud/Data/DataManager.cs
@@ -19,6 +19,11 @@ namespace Dalamud.Data
///
public class DataManager : IDisposable
{
+ ///
+ /// The current game client language.
+ ///
+ internal ClientLanguage Language;
+
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
///
@@ -26,8 +31,6 @@ namespace Dalamud.Data
///
private Lumina.GameData gameData;
- private ClientLanguage language;
-
private Thread luminaResourceThread;
///
@@ -39,7 +42,7 @@ namespace Dalamud.Data
// Set up default values so plugins do not null-reference when data is being loaded.
this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary());
- this.language = language;
+ this.Language = language;
}
///
@@ -94,14 +97,14 @@ namespace Dalamud.Data
PanicOnSheetChecksumMismatch = false,
#endif
- DefaultExcelLanguage = this.language switch {
- ClientLanguage.Japanese => Language.Japanese,
- ClientLanguage.English => Language.English,
- ClientLanguage.German => Language.German,
- ClientLanguage.French => Language.French,
+ DefaultExcelLanguage = this.Language switch {
+ ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
+ ClientLanguage.English => Lumina.Data.Language.English,
+ ClientLanguage.German => Lumina.Data.Language.German,
+ ClientLanguage.French => Lumina.Data.Language.French,
_ => throw new ArgumentOutOfRangeException(
- nameof(this.language),
- "Unknown Language: " + this.language),
+ nameof(this.Language),
+ "Unknown Language: " + this.Language),
},
};
@@ -156,11 +159,11 @@ namespace Dalamud.Data
public ExcelSheet GetExcelSheet(ClientLanguage language) where T : ExcelRow
{
var lang = language switch {
- ClientLanguage.Japanese => Language.Japanese,
- ClientLanguage.English => Language.English,
- ClientLanguage.German => Language.German,
- ClientLanguage.French => Language.French,
- _ => throw new ArgumentOutOfRangeException(nameof(this.language), "Unknown Language: " + this.language)
+ ClientLanguage.Japanese => Lumina.Data.Language.Japanese,
+ ClientLanguage.English => Lumina.Data.Language.English,
+ ClientLanguage.German => Lumina.Data.Language.German,
+ ClientLanguage.French => Lumina.Data.Language.French,
+ _ => throw new ArgumentOutOfRangeException(nameof(this.Language), "Unknown Language: " + this.Language)
};
return this.Excel.GetSheet(lang);
}
@@ -207,7 +210,7 @@ namespace Dalamud.Data
/// The containing the icon.
public TexFile GetIcon(int iconId)
{
- return this.GetIcon(this.language, iconId);
+ return this.GetIcon(this.Language, iconId);
}
///
@@ -223,7 +226,7 @@ namespace Dalamud.Data
ClientLanguage.English => "en/",
ClientLanguage.German => "de/",
ClientLanguage.French => "fr/",
- _ => throw new ArgumentOutOfRangeException(nameof(this.language), "Unknown Language: " + this.language)
+ _ => throw new ArgumentOutOfRangeException(nameof(this.Language), "Unknown Language: " + this.Language)
};
return this.GetIcon(type, iconId);
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..58f6fde47
--- /dev/null
+++ b/Dalamud/Plugin/Sanitizer/Sanitizer.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Dalamud.Plugin.Sanitizer
+{
+ ///
+ /// Sanitize strings to remove soft hyphens and other special characters.
+ ///
+ public class Sanitizer : ISanitizer
+ {
+ private static readonly Dictionary DESanitizationDict = new Dictionary
+ {
+ { "\u0020\u2020", string.Empty }, // dagger
+ };
+
+ private static readonly Dictionary FRSanitizationDict = new Dictionary
+ {
+ { "\u0153", "\u006F\u0065" }, // ligature oe
+ };
+
+ private readonly ClientLanguage defaultClientLanguage;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Default clientLanguage for sanitizing strings.
+ public Sanitizer(ClientLanguage defaultClientLanguage)
+ {
+ this.defaultClientLanguage = defaultClientLanguage;
+ }
+
+ ///
+ /// Creates a sanitized string using current clientLanguage.
+ ///
+ /// An unsanitized string to sanitize.
+ /// A sanitized string.
+ public string Sanitize(string unsanitizedString)
+ {
+ return SanitizeByLanguage(unsanitizedString, this.defaultClientLanguage);
+ }
+
+ ///
+ /// 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)
+ {
+ return SanitizeByLanguage(unsanitizedString, clientLanguage);
+ }
+
+ ///
+ /// 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 SanitizeByLanguage(unsanitizedStrings, this.defaultClientLanguage);
+ }
+
+ ///
+ /// 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)
+ {
+ return SanitizeByLanguage(unsanitizedStrings, clientLanguage);
+ }
+
+ private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage)
+ {
+ var sanitizedString = FilterUnprintableCharacters(unsanitizedString);
+ switch (clientLanguage)
+ {
+ case ClientLanguage.Japanese:
+ case ClientLanguage.English:
+ return sanitizedString;
+ case ClientLanguage.German:
+ return FilterByDict(sanitizedString, DESanitizationDict);
+ case ClientLanguage.French:
+ return FilterByDict(sanitizedString, FRSanitizationDict);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null);
+ }
+ }
+
+ private static IEnumerable SanitizeByLanguage(
+ IEnumerable unsanitizedStrings, ClientLanguage clientLanguage)
+ {
+ var sanitizedStrings = new List();
+ switch (clientLanguage)
+ {
+ case ClientLanguage.Japanese:
+ case ClientLanguage.English:
+ sanitizedStrings.AddRange(unsanitizedStrings.Select(FilterUnprintableCharacters));
+ return sanitizedStrings;
+ case ClientLanguage.German:
+ sanitizedStrings.AddRange(
+ unsanitizedStrings.Select(
+ unsanitizedString =>
+ FilterByDict(FilterUnprintableCharacters(unsanitizedString), DESanitizationDict)));
+ return sanitizedStrings;
+ case ClientLanguage.French:
+ sanitizedStrings.AddRange(
+ unsanitizedStrings.Select(
+ unsanitizedString =>
+ FilterByDict(FilterUnprintableCharacters(unsanitizedString), FRSanitizationDict)));
+ return sanitizedStrings;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null);
+ }
+ }
+
+ private static string FilterUnprintableCharacters(string str)
+ {
+ return new string(str?.Where(ch => ch >= 0x20).ToArray());
+ }
+
+ private static string FilterByDict(string str, Dictionary dict)
+ {
+ return dict.Aggregate(
+ str, (current, kvp) =>
+ current.Replace(kvp.Key, kvp.Value));
+ }
+ }
+}