From 2729f6c6a2768821329affe88b39bd9797975f8d Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Fri, 11 Jun 2021 09:25:46 -0400 Subject: [PATCH] removed "RaplaceCached"... was a dumb idea --- Sources/Towel/Extensions.cs | 78 ++++++------------------------- Sources/Towel/Towel.xml | 21 +-------- Tools/Towel_Testing/Extensions.cs | 24 ++-------- 3 files changed, 20 insertions(+), 103 deletions(-) diff --git a/Sources/Towel/Extensions.cs b/Sources/Towel/Extensions.cs index c741b810..9d52ef52 100644 --- a/Sources/Towel/Extensions.cs +++ b/Sources/Towel/Extensions.cs @@ -13,81 +13,33 @@ public static partial class Extensions { #region System.String - #region Replace + ReplaceCached (multiple replace) + #region Replace (multiple replace) /// Returns a new in which all occurrences of Unicode patterns in this instance are replaced with a relative Unicode replacements. /// Uses Regex without a timeout. - /// The to perform the replacements on. + /// The to perform the replacements on. /// The patterns and relative replacements to apply to this . /// A new in which all occurrences of Unicode patterns in this instance are replaced with a relative Unicode replacements. /// Thrown if any of the parameters are null or contain null values. /// Thrown if is empty, contains empty patterns, or contains duplicate patterns. - public static string Replace(this string @this, params (string Pattern, string Replacement)[] rules) => ReplaceBase(@this, rules, false); - - /// Returns a new in which all occurrences of Unicode patterns in this instance are replaced with a relative Unicode replacements. Caches internal values relative to the instance of rules. - /// Uses Regex without a timeout. This method is not thread-safe. - /// The to perform the replacements on. - /// The patterns and relative replacements to apply to this . - /// A new in which all occurrences of Unicode patterns in this instance are replaced with a relative Unicode replacements. - /// Thrown if any of the parameters are null or contain null values. - /// Thrown if is empty, contains empty patterns, or contains duplicate patterns. - public static string ReplaceCached(this string @this, params (string Pattern, string Replacement)[] rules) => ReplaceBase(@this, rules, true); - - /// Cache for the method. - private readonly static System.Collections.Generic.Dictionary RuleSet)> ReplaceCachedCache = new(); - - private static string ReplaceBase(string @this, (string Pattern, string Replacement)[] rules, bool cached) + public static string Replace(this string original, params (string A, string B)[] rules) { - if (@this is null) throw new ArgumentNullException(nameof(@this)); + if (original is null) throw new ArgumentNullException(nameof(original)); if (rules is null) throw new ArgumentNullException(nameof(rules)); - if (rules.Length <= 0) throw new ArgumentException($"{nameof(rules)}.{nameof(rules.Length)} <= 0"); - if (!cached || !ReplaceCachedCache.TryGetValue(rules, out (Regex Regex, System.Collections.Generic.Dictionary RuleSet) regexAndRuleSet)) - { - regexAndRuleSet.RuleSet = new System.Collections.Generic.Dictionary(rules.Length); - foreach (var (Pattern, Replacement) in rules) - { - if (Pattern is null) throw new ArgumentNullException(nameof(rules), $"{nameof(rules)} contains null {nameof(Pattern)}s"); - if (Replacement is null) throw new ArgumentNullException(nameof(rules), $"{nameof(rules)} contains null {nameof(Replacement)}s"); - if (Pattern == string.Empty) throw new ArgumentException($"{nameof(rules)} contains empty {nameof(Pattern)}s", nameof(rules)); - #warning TODO: TryAdd when available - if (regexAndRuleSet.RuleSet.ContainsKey(Pattern)) - { - throw new ArgumentException($"{nameof(rules)} contains duplicate {nameof(Pattern)}s"); - } - else - { - regexAndRuleSet.RuleSet.Add(Pattern, Replacement); - } - } - string regexPattern = string.Join("|", System.Linq.Enumerable.Select(rules, rule => Regex.Escape(rule.Pattern))); - RegexOptions regexOptions = cached ? RegexOptions.Compiled : RegexOptions.None; - - // NOTE: - // Regex has a matchTimeout parameter that prevents security threats of long-running - // regex patterns. However, an overload with a TimeSpan parameter likely is not required as - // this impementation is not allowing special syntax in the regex. It is only flat strings - // that are OR-ed together. - - regexAndRuleSet.Regex = new Regex(regexPattern, regexOptions); - if (cached) + if (rules.Length is 0) throw new ArgumentException(paramName: nameof(rules), message: $"{nameof(rules)}.{nameof(rules.Length)} is 0"); + MapHashLinked map = new(expectedCount: rules.Length); + foreach (var (a, b) in rules) + { + if (a is null) throw new ArgumentNullException(paramName: nameof(rules), message: $"{nameof(rules)} contains null {nameof(a)}s"); + if (b is null) throw new ArgumentNullException(paramName: nameof(rules), message: $"{nameof(rules)} contains null {nameof(b)}s"); + if (a.Length is 0) throw new ArgumentException(paramName: nameof(rules), message: $"{nameof(rules)} contains 0 length {nameof(a)}s"); + if (!map.TryAdd(a, b)) { - ReplaceCachedCache.Add(rules, regexAndRuleSet); + throw new ArgumentException($"{nameof(rules)} contains duplicate {nameof(a)}s"); } } - return regexAndRuleSet.Regex.Replace(@this, match => regexAndRuleSet.RuleSet[match.Value]); - } - - /// Clears the cache for the method. - public static void ClearReplaceCache() => ReplaceCachedCache.Clear(); - - /// Removes a rule set from the method cache if it exists. - /// The rule set to remove from the cache. - public static void RemoveFromReplaceCache((string Pattern, string Replacement)[] rules) - { - if (ReplaceCachedCache.ContainsKey(rules)) - { - ReplaceCachedCache.Remove(rules); - } + string regexPattern = string.Join("|", map.GetKeys()); + return Regex.Replace(original, regexPattern, match => map[match.Value], RegexOptions.None, Regex.InfiniteMatchTimeout); } #endregion diff --git a/Sources/Towel/Towel.xml b/Sources/Towel/Towel.xml index e7ccdb5e..971a78da 100644 --- a/Sources/Towel/Towel.xml +++ b/Sources/Towel/Towel.xml @@ -15427,31 +15427,12 @@ Returns a new in which all occurrences of Unicode patterns in this instance are replaced with a relative Unicode replacements. Uses Regex without a timeout. - The to perform the replacements on. + The to perform the replacements on. The patterns and relative replacements to apply to this . A new in which all occurrences of Unicode patterns in this instance are replaced with a relative Unicode replacements. Thrown if any of the parameters are null or contain null values. Thrown if is empty, contains empty patterns, or contains duplicate patterns. - - Returns a new in which all occurrences of Unicode patterns in this instance are replaced with a relative Unicode replacements. Caches internal values relative to the instance of rules. - Uses Regex without a timeout. This method is not thread-safe. - The to perform the replacements on. - The patterns and relative replacements to apply to this . - A new in which all occurrences of Unicode patterns in this instance are replaced with a relative Unicode replacements. - Thrown if any of the parameters are null or contain null values. - Thrown if is empty, contains empty patterns, or contains duplicate patterns. - - - Cache for the method. - - - Clears the cache for the method. - - - Removes a rule set from the method cache if it exists. - The rule set to remove from the cache. - Checks if a string contains any of a collections on characters. The string to see if it contains any of the specified characters. diff --git a/Tools/Towel_Testing/Extensions.cs b/Tools/Towel_Testing/Extensions.cs index 7a922b6f..c369787a 100644 --- a/Tools/Towel_Testing/Extensions.cs +++ b/Tools/Towel_Testing/Extensions.cs @@ -17,32 +17,16 @@ [TestMethod] public void String_Replace() Assert.AreEqual("aaa bbb c d e", "a b c d e".Replace(("a", "aaa"), ("b", "bbb"), ("aaa", "ERROR"))); - Assert.ThrowsException(() => Extensions.Replace(null, ("a", "b"))); - Assert.ThrowsException(() => string.Empty.Replace(null)); - Assert.ThrowsException(() => string.Empty.Replace((null, "a"))); - Assert.ThrowsException(() => string.Empty.Replace(("a", null))); + Assert.ThrowsException(() => Extensions.Replace(null!, ("a", "b"))); + Assert.ThrowsException(() => string.Empty.Replace(null!)); + Assert.ThrowsException(() => string.Empty.Replace((null!, "a"))); + Assert.ThrowsException(() => string.Empty.Replace(("a", null!))); Assert.ThrowsException(() => string.Empty.Replace()); Assert.ThrowsException(() => string.Empty.Replace(("a", "b"), ("a", "c"))); Assert.ThrowsException(() => string.Empty.Replace((string.Empty, "a"))); } - [TestMethod] public void String_ReplaceCached() - { - Assert.AreEqual("aaa bbb c ddd e", "a b c d e".ReplaceCached(("a", "aaa"), ("b", "bbb"), ("d", "ddd"))); - - Assert.AreEqual("aaa bbb c d e", "a b c d e".ReplaceCached(("a", "aaa"), ("b", "bbb"), ("aaa", "ERROR"))); - - Assert.ThrowsException(() => Extensions.ReplaceCached(null, ("a", "b"))); - Assert.ThrowsException(() => string.Empty.ReplaceCached(null)); - Assert.ThrowsException(() => string.Empty.ReplaceCached((null, "a"))); - Assert.ThrowsException(() => string.Empty.ReplaceCached(("a", null))); - - Assert.ThrowsException(() => string.Empty.ReplaceCached()); - Assert.ThrowsException(() => string.Empty.ReplaceCached(("a", "b"), ("a", "c"))); - Assert.ThrowsException(() => string.Empty.ReplaceCached((string.Empty, "a"))); - } - #endregion #region Decimal Testing