diff --git a/csharp/Platform.RegularExpressions.Transformer.Tests/RegexEngineTests.cs b/csharp/Platform.RegularExpressions.Transformer.Tests/RegexEngineTests.cs new file mode 100644 index 0000000..8bfb7bb --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer.Tests/RegexEngineTests.cs @@ -0,0 +1,179 @@ +using System; +using Xunit; + +namespace Platform.RegularExpressions.Transformer.Tests +{ + /// + /// + /// Represents the regex engine tests. + /// + /// + /// + public class RegexEngineTests + { + /// + /// + /// Tests that system regex engine creates patterns correctly. + /// + /// + /// + [Fact] + public void SystemRegexEngine_CreatePattern_Test() + { + var engine = new SystemRegexEngine(); + var pattern = engine.CreatePattern(@"hello\s+world"); + + Assert.NotNull(pattern); + Assert.Equal(@"hello\s+world", pattern.Pattern); + Assert.Equal("System.Text.RegularExpressions", engine.Name); + Assert.True(pattern.IsMatch("hello world")); + Assert.True(pattern.IsMatch("hello world")); + Assert.False(pattern.IsMatch("helloworld")); + } + + /// + /// + /// Tests that PCRE regex engine creates patterns correctly. + /// + /// + /// + [Fact] + public void PcreRegexEngine_CreatePattern_Test() + { + var engine = new PcreRegexEngine(); + var pattern = engine.CreatePattern(@"hello\s+world"); + + Assert.NotNull(pattern); + Assert.Equal(@"hello\s+world", pattern.Pattern); + Assert.Equal("PCRE2", engine.Name); + Assert.True(pattern.IsMatch("hello world")); + Assert.True(pattern.IsMatch("hello world")); + Assert.False(pattern.IsMatch("helloworld")); + } + + /// + /// + /// Tests that system regex engine creates patterns with timeout correctly. + /// + /// + /// + [Fact] + public void SystemRegexEngine_CreatePatternWithTimeout_Test() + { + var engine = new SystemRegexEngine(); + var timeout = TimeSpan.FromSeconds(30); + var pattern = engine.CreatePattern(@"test", timeout); + + Assert.NotNull(pattern); + Assert.Equal(@"test", pattern.Pattern); + Assert.Equal(timeout, pattern.MatchTimeout); + } + + /// + /// + /// Tests that PCRE regex engine creates patterns with timeout correctly. + /// + /// + /// + [Fact] + public void PcreRegexEngine_CreatePatternWithTimeout_Test() + { + var engine = new PcreRegexEngine(); + var timeout = TimeSpan.FromSeconds(30); + var pattern = engine.CreatePattern(@"test", timeout); + + Assert.NotNull(pattern); + Assert.Equal(@"test", pattern.Pattern); + Assert.Equal(timeout, pattern.MatchTimeout); + } + + /// + /// + /// Tests that regex engine factory creates engines correctly. + /// + /// + /// + [Fact] + public void RegexEngineFactory_CreateEngine_Test() + { + var systemEngine = RegexEngineFactory.CreateEngine(RegexEngineType.SystemRegex); + var pcreEngine = RegexEngineFactory.CreateEngine(RegexEngineType.PCRE2); + + Assert.IsType(systemEngine); + Assert.IsType(pcreEngine); + Assert.Equal("System.Text.RegularExpressions", systemEngine.Name); + Assert.Equal("PCRE2", pcreEngine.Name); + } + + /// + /// + /// Tests that default engine setting works correctly. + /// + /// + /// + [Fact] + public void RegexEngineFactory_DefaultEngine_Test() + { + // Save original default + var originalDefault = RegexEngineFactory.DefaultEngine; + + try + { + // Test setting PCRE2 as default + RegexEngineFactory.SetDefaultEngine(RegexEngineType.PCRE2); + Assert.IsType(RegexEngineFactory.DefaultEngine); + Assert.Equal(RegexEngineType.PCRE2, RegexEngineFactory.GetDefaultEngineType()); + + // Test setting System as default + RegexEngineFactory.SetDefaultEngine(RegexEngineType.SystemRegex); + Assert.IsType(RegexEngineFactory.DefaultEngine); + Assert.Equal(RegexEngineType.SystemRegex, RegexEngineFactory.GetDefaultEngineType()); + } + finally + { + // Restore original default + RegexEngineFactory.DefaultEngine = originalDefault; + } + } + + /// + /// + /// Tests that pattern replacement works correctly with both engines. + /// + /// + /// + [Theory] + [InlineData(typeof(SystemRegexEngine))] + [InlineData(typeof(PcreRegexEngine))] + public void RegexPattern_Replace_Test(Type engineType) + { + var engine = (IRegexEngine)Activator.CreateInstance(engineType)!; + var pattern = engine.CreatePattern(@"\b\d{4}\b"); + + var input = "The year 2024 was amazing, unlike 2023."; + var result = pattern.Replace(input, "XXXX"); + + Assert.Equal("The year XXXX was amazing, unlike XXXX.", result); + } + + /// + /// + /// Tests that pattern WithTimeout works correctly. + /// + /// + /// + [Fact] + public void RegexPattern_WithTimeout_Test() + { + var engine = new SystemRegexEngine(); + var pattern = engine.CreatePattern(@"test"); + var newTimeout = TimeSpan.FromMinutes(2); + + var newPattern = pattern.WithTimeout(newTimeout); + + Assert.NotEqual(pattern.MatchTimeout, newPattern.MatchTimeout); + Assert.Equal(newTimeout, newPattern.MatchTimeout); + Assert.Equal(pattern.Pattern, newPattern.Pattern); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer.Tests/SubstitutionRuleEngineTests.cs b/csharp/Platform.RegularExpressions.Transformer.Tests/SubstitutionRuleEngineTests.cs new file mode 100644 index 0000000..2747325 --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer.Tests/SubstitutionRuleEngineTests.cs @@ -0,0 +1,215 @@ +using System; +using System.Text.RegularExpressions; +using Xunit; + +namespace Platform.RegularExpressions.Transformer.Tests +{ + /// + /// + /// Represents the substitution rule engine tests. + /// + /// + /// + public class SubstitutionRuleEngineTests + { + /// + /// + /// Tests that SubstitutionRule works with SystemRegex engine. + /// + /// + /// + [Fact] + public void SubstitutionRule_SystemRegexEngine_Test() + { + var engine = new SystemRegexEngine(); + var pattern = engine.CreatePattern(@"hello"); + var rule = new SubstitutionRule(pattern, "hi"); + + Assert.NotNull(rule.MatchPattern); + Assert.Equal("hello", rule.MatchPattern.Pattern); + Assert.Equal("hi", rule.SubstitutionPattern); + Assert.IsType(rule.MatchPattern); + } + + /// + /// + /// Tests that SubstitutionRule works with PCRE2 engine. + /// + /// + /// + [Fact] + public void SubstitutionRule_PcreEngine_Test() + { + var engine = new PcreRegexEngine(); + var pattern = engine.CreatePattern(@"hello"); + var rule = new SubstitutionRule(pattern, "hi"); + + Assert.NotNull(rule.MatchPattern); + Assert.Equal("hello", rule.MatchPattern.Pattern); + Assert.Equal("hi", rule.SubstitutionPattern); + Assert.IsType(rule.MatchPattern); + } + + /// + /// + /// Tests that SubstitutionRule implicit conversion uses default engine. + /// + /// + /// + [Fact] + public void SubstitutionRule_ImplicitConversion_UsesDefaultEngine_Test() + { + // Save original default + var originalDefault = RegexEngineFactory.DefaultEngine; + + try + { + // Test with System regex as default + RegexEngineFactory.SetDefaultEngine(RegexEngineType.SystemRegex); + SubstitutionRule rule1 = ("hello", "hi"); + Assert.IsType(rule1.MatchPattern); + + // Test with PCRE2 as default + RegexEngineFactory.SetDefaultEngine(RegexEngineType.PCRE2); + SubstitutionRule rule2 = ("hello", "hi"); + Assert.IsType(rule2.MatchPattern); + } + finally + { + // Restore original default + RegexEngineFactory.DefaultEngine = originalDefault; + } + } + + /// + /// + /// Tests that legacy Regex constructor still works. + /// + /// + /// + [Fact] + public void SubstitutionRule_LegacyRegexConstructor_Test() + { + var regex = new Regex("hello"); + var rule = new SubstitutionRule(regex, "hi"); + + Assert.NotNull(rule.MatchPattern); + Assert.Equal("hello", rule.MatchPattern.Pattern); + Assert.Equal("hi", rule.SubstitutionPattern); + Assert.NotNull(rule.LegacyMatchPattern); + Assert.Equal("hello", rule.LegacyMatchPattern.ToString()); + } + + /// + /// + /// Tests backward compatibility through LegacyMatchPattern property. + /// + /// + /// + [Fact] + public void SubstitutionRule_LegacyMatchPattern_BackwardCompatibility_Test() + { + // Test with SystemRegexPattern + var systemEngine = new SystemRegexEngine(); + var systemPattern = systemEngine.CreatePattern(@"test"); + var systemRule = new SubstitutionRule(systemPattern, "replacement"); + + Assert.NotNull(systemRule.LegacyMatchPattern); + Assert.IsType(systemRule.LegacyMatchPattern); + Assert.Equal("test", systemRule.LegacyMatchPattern.ToString()); + + // Test with PcreRegexPattern + var pcreEngine = new PcreRegexEngine(); + var pcrePattern = pcreEngine.CreatePattern(@"test"); + var pcreRule = new SubstitutionRule(pcrePattern, "replacement"); + + Assert.NotNull(pcreRule.LegacyMatchPattern); + Assert.IsType(pcreRule.LegacyMatchPattern); + Assert.Equal("test", pcreRule.LegacyMatchPattern.ToString()); + } + + /// + /// + /// Tests that TextTransformer works with different regex engines. + /// + /// + /// + [Theory] + [InlineData(typeof(SystemRegexEngine))] + [InlineData(typeof(PcreRegexEngine))] + public void TextTransformer_WithDifferentEngines_Test(Type engineType) + { + var engine = (IRegexEngine)Activator.CreateInstance(engineType)!; + var pattern = engine.CreatePattern(@"\b\d{4}\b"); + var rule = new SubstitutionRule(pattern, "YEAR"); + + var transformer = new TextTransformer(new[] { rule }); + var input = "In 2024, we had great success."; + var result = transformer.Transform(input); + + Assert.Equal("In YEAR, we had great success.", result); + } + + /// + /// + /// Tests that both engines produce equivalent results for common patterns. + /// + /// + /// + [Theory] + [InlineData(@"\d+", "abc123def", "XXX", "abcXXXdef")] + [InlineData(@"\s+", "hello world", "_", "hello_world")] + [InlineData(@"[A-Z]+", "Hello WORLD Test", "***", "***ello *** ***est")] + public void BothEngines_ProduceEquivalentResults_Test(string pattern, string input, string replacement, string expected) + { + var systemEngine = new SystemRegexEngine(); + var pcreEngine = new PcreRegexEngine(); + + var systemPattern = systemEngine.CreatePattern(pattern); + var pcrePattern = pcreEngine.CreatePattern(pattern); + + var systemResult = systemPattern.Replace(input, replacement); + var pcreResult = pcrePattern.Replace(input, replacement); + + Assert.Equal(expected, systemResult); + Assert.Equal(expected, pcreResult); + Assert.Equal(systemResult, pcreResult); + } + + /// + /// + /// Tests that engine switching works correctly during runtime. + /// + /// + /// + [Fact] + public void RegexEngine_SwitchingDuringRuntime_Test() + { + // Save original default + var originalDefault = RegexEngineFactory.DefaultEngine; + + try + { + // Create rules with specific engines to demonstrate both engines work + var systemEngine = new SystemRegexEngine(); + var pcreEngine = new PcreRegexEngine(); + + var rule1 = new SubstitutionRule(systemEngine.CreatePattern(@"\d+"), "123"); + var rule2 = new SubstitutionRule(pcreEngine.CreatePattern(@"[A-Z]"), "X"); + + var transformer = new TextTransformer(new[] { rule1, rule2 }); + var result = transformer.Transform("A1B2C"); + + // The rules are applied sequentially, so "A1B2C" becomes: + // 1. rule1 applies: "A1B2C" -> "A123B123C" (1,2 -> 123) + // 2. rule2 applies: "A123B123C" -> "X123X123X" (A,B,C -> X) + Assert.Equal("X123X123X", result); + } + finally + { + // Restore original default + RegexEngineFactory.DefaultEngine = originalDefault; + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer.Tests/SubstitutionRuleTests.cs b/csharp/Platform.RegularExpressions.Transformer.Tests/SubstitutionRuleTests.cs index 2c9e2fc..42b3d81 100644 --- a/csharp/Platform.RegularExpressions.Transformer.Tests/SubstitutionRuleTests.cs +++ b/csharp/Platform.RegularExpressions.Transformer.Tests/SubstitutionRuleTests.cs @@ -21,7 +21,7 @@ public class SubstitutionRuleTests public void OptionsOverrideTest() { SubstitutionRule rule = (new Regex(@"^\s*?\#pragma[\sa-zA-Z0-9\/]+$"), "", 0); - Assert.Equal(RegexOptions.Compiled | RegexOptions.Multiline, rule.MatchPattern.Options); + Assert.Equal(RegexOptions.Compiled | RegexOptions.Multiline, rule.LegacyMatchPattern.Options); } } } diff --git a/csharp/Platform.RegularExpressions.Transformer/IRegexEngine.cs b/csharp/Platform.RegularExpressions.Transformer/IRegexEngine.cs new file mode 100644 index 0000000..4a3c18c --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer/IRegexEngine.cs @@ -0,0 +1,66 @@ +using System; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.RegularExpressions.Transformer +{ + /// + /// + /// Defines the regex engine interface for creating regex patterns. + /// + /// + /// + public interface IRegexEngine + { + /// + /// + /// Gets the name of the regex engine. + /// + /// + /// + string Name + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// + /// Creates a regex pattern from the specified pattern string. + /// + /// + /// + /// + /// The regex pattern string. + /// + /// + /// + /// A regex pattern instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + IRegexPattern CreatePattern(string pattern); + + /// + /// + /// Creates a regex pattern from the specified pattern string with timeout. + /// + /// + /// + /// + /// The regex pattern string. + /// + /// + /// + /// The match timeout. + /// + /// + /// + /// A regex pattern instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + IRegexPattern CreatePattern(string pattern, TimeSpan timeout); + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer/IRegexPattern.cs b/csharp/Platform.RegularExpressions.Transformer/IRegexPattern.cs new file mode 100644 index 0000000..5a3a6e3 --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer/IRegexPattern.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.RegularExpressions.Transformer +{ + /// + /// + /// Defines the regex pattern interface that abstracts different regex engines. + /// + /// + /// + public interface IRegexPattern + { + /// + /// + /// Gets the pattern string value. + /// + /// + /// + string Pattern + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// + /// Gets the timeout value. + /// + /// + /// + TimeSpan MatchTimeout + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// + /// Determines whether the specified input matches the pattern. + /// + /// + /// + /// + /// The input string. + /// + /// + /// + /// True if the pattern matches; otherwise, false. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + bool IsMatch(string input); + + /// + /// + /// Replaces all matches in the input string with the replacement pattern. + /// + /// + /// + /// + /// The input string. + /// + /// + /// + /// The replacement pattern. + /// + /// + /// + /// The modified string with replacements. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + string Replace(string input, string replacement); + + /// + /// + /// Creates a new regex pattern with overridden options. + /// + /// + /// + /// + /// The match timeout. + /// + /// + /// + /// A new regex pattern with the specified timeout. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + IRegexPattern WithTimeout(TimeSpan timeout); + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer/ISubstitutionRule.cs b/csharp/Platform.RegularExpressions.Transformer/ISubstitutionRule.cs index f24e825..fefad51 100644 --- a/csharp/Platform.RegularExpressions.Transformer/ISubstitutionRule.cs +++ b/csharp/Platform.RegularExpressions.Transformer/ISubstitutionRule.cs @@ -19,7 +19,7 @@ public interface ISubstitutionRule /// /// /// - Regex MatchPattern + IRegexPattern MatchPattern { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; @@ -48,5 +48,17 @@ int MaximumRepeatCount [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } + + /// + /// + /// Gets the legacy System.Text.RegularExpressions.Regex pattern for backward compatibility. + /// + /// + /// + Regex LegacyMatchPattern + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } } } \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer/PcreRegexEngine.cs b/csharp/Platform.RegularExpressions.Transformer/PcreRegexEngine.cs new file mode 100644 index 0000000..f86d2d7 --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer/PcreRegexEngine.cs @@ -0,0 +1,126 @@ +using System; +using System.Runtime.CompilerServices; +using PCRE; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.RegularExpressions.Transformer +{ + /// + /// + /// Represents a regex engine implementation using PCRE.NET. + /// + /// + /// + public class PcreRegexEngine : IRegexEngine + { + /// + /// + /// The default PCRE options for the PCRE regex engine. + /// + /// + /// + public static readonly PcreOptions DefaultPcreOptions = PcreOptions.Compiled | PcreOptions.MultiLine; + + /// + /// + /// The default match timeout for the PCRE regex engine. + /// + /// + /// + public static readonly TimeSpan DefaultMatchTimeout = TimeSpan.FromMinutes(5); + + private readonly PcreOptions _options; + private readonly TimeSpan _defaultTimeout; + + /// + /// + /// Gets the name of the regex engine. + /// + /// + /// + public string Name + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => "PCRE2"; + } + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// The default PCRE options. + /// + /// + /// + /// The default match timeout. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PcreRegexEngine(PcreOptions options, TimeSpan defaultTimeout) + { + _options = options; + _defaultTimeout = defaultTimeout; + } + + /// + /// + /// Initializes a new instance with default options. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PcreRegexEngine() : this(DefaultPcreOptions, DefaultMatchTimeout) + { + } + + /// + /// + /// Creates a regex pattern from the specified pattern string. + /// + /// + /// + /// + /// The regex pattern string. + /// + /// + /// + /// A regex pattern instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IRegexPattern CreatePattern(string pattern) + { + var regex = new PcreRegex(pattern, _options); + return new PcreRegexPattern(regex, _defaultTimeout); + } + + /// + /// + /// Creates a regex pattern from the specified pattern string with timeout. + /// + /// + /// + /// + /// The regex pattern string. + /// + /// + /// + /// The match timeout. + /// + /// + /// + /// A regex pattern instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IRegexPattern CreatePattern(string pattern, TimeSpan timeout) + { + var regex = new PcreRegex(pattern, _options); + return new PcreRegexPattern(regex, timeout); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer/PcreRegexPattern.cs b/csharp/Platform.RegularExpressions.Transformer/PcreRegexPattern.cs new file mode 100644 index 0000000..6909aa7 --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer/PcreRegexPattern.cs @@ -0,0 +1,160 @@ +using System; +using System.Runtime.CompilerServices; +using PCRE; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.RegularExpressions.Transformer +{ + /// + /// + /// Represents a regex pattern implementation using PCRE.NET. + /// + /// + /// + public class PcreRegexPattern : IRegexPattern + { + private readonly PcreRegex _regex; + private readonly TimeSpan _timeout; + + /// + /// + /// Gets the pattern string value. + /// + /// + /// + public string Pattern + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _regex.ToString(); + } + + /// + /// + /// Gets the timeout value. + /// + /// + /// + public TimeSpan MatchTimeout + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _timeout; + } + + /// + /// + /// Gets the underlying PCRE.NET regex instance. + /// + /// + /// + public PcreRegex UnderlyingRegex + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _regex; + } + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// The underlying PCRE regex instance. + /// + /// + /// + /// The match timeout. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PcreRegexPattern(PcreRegex regex, TimeSpan timeout) + { + _regex = regex ?? throw new ArgumentNullException(nameof(regex)); + _timeout = timeout; + } + + /// + /// + /// Determines whether the specified input matches the pattern. + /// + /// + /// + /// + /// The input string. + /// + /// + /// + /// True if the pattern matches; otherwise, false. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsMatch(string input) + { + if (input == null) + return false; + + return _regex.IsMatch(input); + } + + /// + /// + /// Replaces all matches in the input string with the replacement pattern. + /// + /// + /// + /// + /// The input string. + /// + /// + /// + /// The replacement pattern. + /// + /// + /// + /// The modified string with replacements. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string Replace(string input, string replacement) + { + if (input == null) + return null; + + return _regex.Replace(input, replacement); + } + + /// + /// + /// Creates a new regex pattern with overridden options. + /// + /// + /// + /// + /// The match timeout. + /// + /// + /// + /// A new regex pattern with the specified timeout. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IRegexPattern WithTimeout(TimeSpan timeout) + { + return new PcreRegexPattern(_regex, timeout); + } + + /// + /// + /// Returns the string representation of the regex pattern. + /// + /// + /// + /// + /// The pattern string. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override string ToString() => _regex.ToString(); + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer/Platform.RegularExpressions.Transformer.csproj b/csharp/Platform.RegularExpressions.Transformer/Platform.RegularExpressions.Transformer.csproj index e39f96a..a13f2fe 100644 --- a/csharp/Platform.RegularExpressions.Transformer/Platform.RegularExpressions.Transformer.csproj +++ b/csharp/Platform.RegularExpressions.Transformer/Platform.RegularExpressions.Transformer.csproj @@ -4,12 +4,12 @@ LinksPlatform's Platform.RegularExpressions.Transformer Class Library Konstantin Diachenko Platform.RegularExpressions.Transformer - 0.3.4 + 0.4.0 Konstantin Diachenko net8 Platform.RegularExpressions.Transformer Platform.RegularExpressions.Transformer - LinksPlatform;RegularExpressions.Transformer;IContext;ISubstitutionRule;ITransformer;Context;RegexExtensions;SubstitutionRule;Transformer;TransformerCLI + LinksPlatform;RegularExpressions.Transformer;IContext;ISubstitutionRule;ITransformer;Context;RegexExtensions;SubstitutionRule;Transformer;TransformerCLI;PCRE;PCRE2;RegexEngine;RegexEngineFactory https://raw.githubusercontent.com/linksplatform/Documentation/18469f4d033ee9a5b7b84caab9c585acab2ac519/doc/Avatar-rainbow-icon-64x64.png https://linksplatform.github.io/RegularExpressions.Transformer Unlicensed @@ -23,7 +23,7 @@ true snupkg latest - Update target framework from net7 to net8. + Add support for PCRE2 regex engine. Users can now switch between System.Text.RegularExpressions and PCRE2 engines using RegexEngineFactory. Includes backward compatibility with existing code. true enable @@ -34,6 +34,7 @@ + diff --git a/csharp/Platform.RegularExpressions.Transformer/RegexEngineFactory.cs b/csharp/Platform.RegularExpressions.Transformer/RegexEngineFactory.cs new file mode 100644 index 0000000..2501a8f --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer/RegexEngineFactory.cs @@ -0,0 +1,94 @@ +using System; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.RegularExpressions.Transformer +{ + /// + /// + /// Represents a factory for creating regex engines. + /// + /// + /// + public static class RegexEngineFactory + { + private static IRegexEngine? _defaultEngine; + + /// + /// + /// Gets or sets the default regex engine used throughout the application. + /// + /// + /// + public static IRegexEngine DefaultEngine + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _defaultEngine ??= CreateEngine(RegexEngineType.SystemRegex); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _defaultEngine = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// + /// Creates a regex engine of the specified type. + /// + /// + /// + /// + /// The type of regex engine to create. + /// + /// + /// + /// A regex engine instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IRegexEngine CreateEngine(RegexEngineType engineType) + { + return engineType switch + { + RegexEngineType.SystemRegex => new SystemRegexEngine(), + RegexEngineType.PCRE2 => new PcreRegexEngine(), + _ => throw new ArgumentOutOfRangeException(nameof(engineType), engineType, "Unknown regex engine type") + }; + } + + /// + /// + /// Sets the default regex engine type for the application. + /// + /// + /// + /// + /// The type of regex engine to use as default. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetDefaultEngine(RegexEngineType engineType) + { + DefaultEngine = CreateEngine(engineType); + } + + /// + /// + /// Gets the current default engine type. + /// + /// + /// + /// + /// The current default engine type. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RegexEngineType GetDefaultEngineType() + { + return DefaultEngine switch + { + SystemRegexEngine => RegexEngineType.SystemRegex, + PcreRegexEngine => RegexEngineType.PCRE2, + _ => RegexEngineType.SystemRegex + }; + } + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer/RegexEngineType.cs b/csharp/Platform.RegularExpressions.Transformer/RegexEngineType.cs new file mode 100644 index 0000000..ee153e9 --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer/RegexEngineType.cs @@ -0,0 +1,31 @@ +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.RegularExpressions.Transformer +{ + /// + /// + /// Represents the available regex engine types. + /// + /// + /// + public enum RegexEngineType + { + /// + /// + /// System.Text.RegularExpressions engine (default .NET regex engine). + /// + /// + /// + SystemRegex = 0, + + /// + /// + /// PCRE2 engine (Perl Compatible Regular Expressions). + /// + /// + /// + PCRE2 = 1 + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer/RegexExtensions.cs b/csharp/Platform.RegularExpressions.Transformer/RegexExtensions.cs index fd56eb9..c7651a3 100644 --- a/csharp/Platform.RegularExpressions.Transformer/RegexExtensions.cs +++ b/csharp/Platform.RegularExpressions.Transformer/RegexExtensions.cs @@ -45,5 +45,21 @@ public static Regex OverrideOptions(this Regex regex, RegexOptions options, Time } return new Regex(regex.ToString(), options, matchTimeout); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IRegexPattern OverrideOptions(this IRegexPattern pattern, RegexOptions options, TimeSpan matchTimeout) + { + if (pattern == null) + { + return null; + } + + if (pattern is SystemRegexPattern systemPattern) + { + return new SystemRegexPattern(systemPattern.UnderlyingRegex.OverrideOptions(options, matchTimeout)); + } + + return pattern.WithTimeout(matchTimeout); + } } } diff --git a/csharp/Platform.RegularExpressions.Transformer/SubstitutionRule.cs b/csharp/Platform.RegularExpressions.Transformer/SubstitutionRule.cs index 150343a..7866928 100644 --- a/csharp/Platform.RegularExpressions.Transformer/SubstitutionRule.cs +++ b/csharp/Platform.RegularExpressions.Transformer/SubstitutionRule.cs @@ -37,7 +37,7 @@ public class SubstitutionRule : ISubstitutionRule /// /// /// - public Regex MatchPattern + public IRegexPattern MatchPattern { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; @@ -45,6 +45,28 @@ public Regex MatchPattern set; } + /// + /// + /// Gets the legacy System.Text.RegularExpressions.Regex pattern for backward compatibility. + /// + /// + /// + public Regex LegacyMatchPattern + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (MatchPattern is SystemRegexPattern systemPattern) + { + return systemPattern.UnderlyingRegex; + } + + // For PCRE patterns, create a fallback System.Text.RegularExpressions.Regex + // This maintains backward compatibility but may not have exact feature parity + return new Regex(MatchPattern.Pattern, SubstitutionRule.DefaultMatchPatternRegexOptions, MatchPattern.MatchTimeout); + } + } + /// /// /// Gets or sets the substitution pattern value. @@ -65,7 +87,7 @@ public string SubstitutionPattern /// /// /// - public Regex PathPattern + public IRegexPattern? PathPattern { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; @@ -73,6 +95,30 @@ public Regex PathPattern set; } + /// + /// + /// Gets the legacy System.Text.RegularExpressions.Regex path pattern for backward compatibility. + /// + /// + /// + public Regex? LegacyPathPattern + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (PathPattern == null) + return null; + + if (PathPattern is SystemRegexPattern systemPattern) + { + return systemPattern.UnderlyingRegex; + } + + // For PCRE patterns, create a fallback System.Text.RegularExpressions.Regex + return new Regex(PathPattern.Pattern, SubstitutionRule.DefaultMatchPatternRegexOptions, PathPattern.MatchTimeout); + } + } + /// /// /// Gets or sets the maximum repeat count value. @@ -105,6 +151,49 @@ public int MaximumRepeatCount /// A maximum repeat count. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SubstitutionRule(IRegexPattern matchPattern, string substitutionPattern, int maximumRepeatCount) + { + MatchPattern = matchPattern; + SubstitutionPattern = substitutionPattern; + MaximumRepeatCount = maximumRepeatCount; + } + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A match pattern. + /// + /// + /// + /// A substitution pattern. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SubstitutionRule(IRegexPattern matchPattern, string substitutionPattern) : this(matchPattern, substitutionPattern, 0) { } + + /// + /// + /// Initializes a new instance with legacy System.Text.RegularExpressions.Regex. + /// + /// + /// + /// + /// A match pattern. + /// + /// + /// + /// A substitution pattern. + /// + /// + /// + /// A maximum repeat count. + /// + /// /// /// A match pattern options. /// @@ -116,7 +205,9 @@ public int MaximumRepeatCount [MethodImpl(MethodImplOptions.AggressiveInlining)] public SubstitutionRule(Regex matchPattern, string substitutionPattern, int maximumRepeatCount, RegexOptions? matchPatternOptions, TimeSpan? matchTimeout) { - MatchPattern = matchPattern; + var engine = RegexEngineFactory.DefaultEngine; + var pattern = engine.CreatePattern(matchPattern.ToString(), matchTimeout ?? matchPattern.MatchTimeout); + MatchPattern = pattern; SubstitutionPattern = substitutionPattern; MaximumRepeatCount = maximumRepeatCount; OverrideMatchPatternOptions(matchPatternOptions ?? matchPattern.Options, matchTimeout ?? matchPattern.MatchTimeout); @@ -124,7 +215,7 @@ public SubstitutionRule(Regex matchPattern, string substitutionPattern, int maxi /// /// - /// Initializes a new instance. + /// Initializes a new instance with legacy System.Text.RegularExpressions.Regex. /// /// /// @@ -149,7 +240,7 @@ public SubstitutionRule(Regex matchPattern, string substitutionPattern, int maxi /// /// - /// Initializes a new instance. + /// Initializes a new instance with legacy System.Text.RegularExpressions.Regex. /// /// /// @@ -170,7 +261,7 @@ public SubstitutionRule(Regex matchPattern, string substitutionPattern, int maxi /// /// - /// Initializes a new instance. + /// Initializes a new instance with legacy System.Text.RegularExpressions.Regex. /// /// /// @@ -186,17 +277,23 @@ public SubstitutionRule(Regex matchPattern, string substitutionPattern, int maxi public SubstitutionRule(Regex matchPattern, string substitutionPattern) : this(matchPattern, substitutionPattern, 0) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator SubstitutionRule(ValueTuple tuple) => new SubstitutionRule(new Regex(tuple.Item1), tuple.Item2); + public static implicit operator SubstitutionRule(ValueTuple tuple) => new SubstitutionRule(RegexEngineFactory.DefaultEngine.CreatePattern(tuple.Item1), tuple.Item2); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator SubstitutionRule(ValueTuple tuple) => new SubstitutionRule(tuple.Item1, tuple.Item2); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator SubstitutionRule(ValueTuple tuple) => new SubstitutionRule(new Regex(tuple.Item1), tuple.Item2, tuple.Item3); + public static implicit operator SubstitutionRule(ValueTuple tuple) => new SubstitutionRule(RegexEngineFactory.DefaultEngine.CreatePattern(tuple.Item1), tuple.Item2, tuple.Item3); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator SubstitutionRule(ValueTuple tuple) => new SubstitutionRule(tuple.Item1, tuple.Item2, tuple.Item3); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator SubstitutionRule(ValueTuple tuple) => new SubstitutionRule(tuple.Item1, tuple.Item2); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator SubstitutionRule(ValueTuple tuple) => new SubstitutionRule(tuple.Item1, tuple.Item2, tuple.Item3); + /// /// /// Overrides the match pattern options using the specified options. @@ -212,7 +309,17 @@ public SubstitutionRule(Regex matchPattern, string substitutionPattern) : this(m /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void OverrideMatchPatternOptions(RegexOptions options, TimeSpan matchTimeout) => MatchPattern = MatchPattern.OverrideOptions(options, matchTimeout); + public void OverrideMatchPatternOptions(RegexOptions options, TimeSpan matchTimeout) + { + if (MatchPattern is SystemRegexPattern systemPattern) + { + MatchPattern = new SystemRegexPattern(systemPattern.UnderlyingRegex.OverrideOptions(options, matchTimeout)); + } + else + { + MatchPattern = MatchPattern.WithTimeout(matchTimeout); + } + } /// /// @@ -229,7 +336,20 @@ public SubstitutionRule(Regex matchPattern, string substitutionPattern) : this(m /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void OverridePathPatternOptions(RegexOptions options, TimeSpan matchTimeout) => PathPattern = PathPattern.OverrideOptions(options, matchTimeout); + public void OverridePathPatternOptions(RegexOptions options, TimeSpan matchTimeout) + { + if (PathPattern == null) + return; + + if (PathPattern is SystemRegexPattern systemPattern) + { + PathPattern = new SystemRegexPattern(systemPattern.UnderlyingRegex.OverrideOptions(options, matchTimeout)); + } + else + { + PathPattern = PathPattern.WithTimeout(matchTimeout); + } + } /// /// @@ -246,7 +366,7 @@ public override string ToString() { var sb = new StringBuilder(); sb.Append('"'); - sb.Append(MatchPattern.ToString()); + sb.Append(MatchPattern?.ToString() ?? ""); sb.Append('"'); sb.Append(" -> "); sb.Append('"'); diff --git a/csharp/Platform.RegularExpressions.Transformer/SystemRegexEngine.cs b/csharp/Platform.RegularExpressions.Transformer/SystemRegexEngine.cs new file mode 100644 index 0000000..06edf97 --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer/SystemRegexEngine.cs @@ -0,0 +1,126 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.RegularExpressions.Transformer +{ + /// + /// + /// Represents a regex engine implementation using System.Text.RegularExpressions. + /// + /// + /// + public class SystemRegexEngine : IRegexEngine + { + /// + /// + /// The default regex options for the System regex engine. + /// + /// + /// + public static readonly RegexOptions DefaultRegexOptions = RegexOptions.Compiled | RegexOptions.Multiline; + + /// + /// + /// The default match timeout for the System regex engine. + /// + /// + /// + public static readonly TimeSpan DefaultMatchTimeout = TimeSpan.FromMinutes(5); + + private readonly RegexOptions _options; + private readonly TimeSpan _defaultTimeout; + + /// + /// + /// Gets the name of the regex engine. + /// + /// + /// + public string Name + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => "System.Text.RegularExpressions"; + } + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// The default regex options. + /// + /// + /// + /// The default match timeout. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SystemRegexEngine(RegexOptions options, TimeSpan defaultTimeout) + { + _options = options; + _defaultTimeout = defaultTimeout; + } + + /// + /// + /// Initializes a new instance with default options. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SystemRegexEngine() : this(DefaultRegexOptions, DefaultMatchTimeout) + { + } + + /// + /// + /// Creates a regex pattern from the specified pattern string. + /// + /// + /// + /// + /// The regex pattern string. + /// + /// + /// + /// A regex pattern instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IRegexPattern CreatePattern(string pattern) + { + var regex = new Regex(pattern, _options, _defaultTimeout); + return new SystemRegexPattern(regex); + } + + /// + /// + /// Creates a regex pattern from the specified pattern string with timeout. + /// + /// + /// + /// + /// The regex pattern string. + /// + /// + /// + /// The match timeout. + /// + /// + /// + /// A regex pattern instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IRegexPattern CreatePattern(string pattern, TimeSpan timeout) + { + var regex = new Regex(pattern, _options, timeout); + return new SystemRegexPattern(regex); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.RegularExpressions.Transformer/SystemRegexPattern.cs b/csharp/Platform.RegularExpressions.Transformer/SystemRegexPattern.cs new file mode 100644 index 0000000..a9f216c --- /dev/null +++ b/csharp/Platform.RegularExpressions.Transformer/SystemRegexPattern.cs @@ -0,0 +1,143 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.RegularExpressions.Transformer +{ + /// + /// + /// Represents a regex pattern implementation using System.Text.RegularExpressions. + /// + /// + /// + public class SystemRegexPattern : IRegexPattern + { + private readonly Regex _regex; + + /// + /// + /// Gets the pattern string value. + /// + /// + /// + public string Pattern + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _regex.ToString(); + } + + /// + /// + /// Gets the timeout value. + /// + /// + /// + public TimeSpan MatchTimeout + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _regex.MatchTimeout; + } + + /// + /// + /// Gets the underlying System.Text.RegularExpressions.Regex instance. + /// + /// + /// + public Regex UnderlyingRegex + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _regex; + } + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// The underlying regex instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SystemRegexPattern(Regex regex) + { + _regex = regex ?? throw new ArgumentNullException(nameof(regex)); + } + + /// + /// + /// Determines whether the specified input matches the pattern. + /// + /// + /// + /// + /// The input string. + /// + /// + /// + /// True if the pattern matches; otherwise, false. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsMatch(string input) => _regex.IsMatch(input); + + /// + /// + /// Replaces all matches in the input string with the replacement pattern. + /// + /// + /// + /// + /// The input string. + /// + /// + /// + /// The replacement pattern. + /// + /// + /// + /// The modified string with replacements. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string Replace(string input, string replacement) => _regex.Replace(input, replacement); + + /// + /// + /// Creates a new regex pattern with overridden options. + /// + /// + /// + /// + /// The match timeout. + /// + /// + /// + /// A new regex pattern with the specified timeout. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IRegexPattern WithTimeout(TimeSpan timeout) + { + var newRegex = new Regex(_regex.ToString(), _regex.Options, timeout); + return new SystemRegexPattern(newRegex); + } + + /// + /// + /// Returns the string representation of the regex pattern. + /// + /// + /// + /// + /// The pattern string. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override string ToString() => _regex.ToString(); + } +} \ No newline at end of file diff --git a/examples/RegexEngineDemo.cs b/examples/RegexEngineDemo.cs new file mode 100644 index 0000000..7fb0d99 --- /dev/null +++ b/examples/RegexEngineDemo.cs @@ -0,0 +1,116 @@ +using System; +using Platform.RegularExpressions.Transformer; + +namespace Platform.RegularExpressions.Transformer.Examples +{ + /// + /// Demonstrates how to use the new PCRE2 regex engine support alongside System.Text.RegularExpressions + /// + public class RegexEngineDemo + { + public static void Main(string[] args) + { + Console.WriteLine("=== PCRE2 Regex Engine Support Demo ==="); + Console.WriteLine(); + + // 1. Default behavior (uses System.Text.RegularExpressions) + Console.WriteLine("1. Using default System.Text.RegularExpressions engine:"); + DemonstrateSystemRegexEngine(); + Console.WriteLine(); + + // 2. Switching to PCRE2 engine + Console.WriteLine("2. Switching to PCRE2 engine:"); + DemonstratePcreEngine(); + Console.WriteLine(); + + // 3. Using both engines in same transformation + Console.WriteLine("3. Using both engines in the same transformation:"); + DemonstrateMixedEngines(); + Console.WriteLine(); + + // 4. Engine switching at runtime + Console.WriteLine("4. Runtime engine switching:"); + DemonstrateRuntimeSwitching(); + } + + private static void DemonstrateSystemRegexEngine() + { + // Default engine is System.Text.RegularExpressions + SubstitutionRule rule = (@"\b\d{4}\b", "YEAR"); + var transformer = new TextTransformer(new[] { rule }); + + string input = "The years 2023 and 2024 were eventful."; + string result = transformer.Transform(input); + + Console.WriteLine($"Input: {input}"); + Console.WriteLine($"Output: {result}"); + Console.WriteLine($"Engine: {RegexEngineFactory.DefaultEngine.Name}"); + } + + private static void DemonstratePcreEngine() + { + // Switch to PCRE2 engine + RegexEngineFactory.SetDefaultEngine(RegexEngineType.PCRE2); + + SubstitutionRule rule = (@"\b[A-Z]{3}\b", "***"); + var transformer = new TextTransformer(new[] { rule }); + + string input = "The CEO met with CTO and CFO."; + string result = transformer.Transform(input); + + Console.WriteLine($"Input: {input}"); + Console.WriteLine($"Output: {result}"); + Console.WriteLine($"Engine: {RegexEngineFactory.DefaultEngine.Name}"); + + // Reset to default + RegexEngineFactory.SetDefaultEngine(RegexEngineType.SystemRegex); + } + + private static void DemonstrateMixedEngines() + { + // Create engines explicitly + var systemEngine = new SystemRegexEngine(); + var pcreEngine = new PcreRegexEngine(); + + // Create rules using different engines + var rule1 = new SubstitutionRule(systemEngine.CreatePattern(@"\d+"), "###"); + var rule2 = new SubstitutionRule(pcreEngine.CreatePattern(@"[A-Z]+"), "XXX"); + + var transformer = new TextTransformer(new[] { rule1, rule2 }); + + string input = "Order 123 for PRODUCT ABC"; + string result = transformer.Transform(input); + + Console.WriteLine($"Input: {input}"); + Console.WriteLine($"Output: {result}"); + Console.WriteLine($"Rule 1 Engine: {rule1.MatchPattern.GetType().Name} (System.Text.RegularExpressions)"); + Console.WriteLine($"Rule 2 Engine: {rule2.MatchPattern.GetType().Name} (PCRE2)"); + } + + private static void DemonstrateRuntimeSwitching() + { + Console.WriteLine("Creating rules with different engines at runtime..."); + + // Start with System engine + RegexEngineFactory.SetDefaultEngine(RegexEngineType.SystemRegex); + SubstitutionRule rule1 = (@"hello", "hi"); + Console.WriteLine($"Rule1 created with: {RegexEngineFactory.DefaultEngine.Name}"); + + // Switch to PCRE2 engine + RegexEngineFactory.SetDefaultEngine(RegexEngineType.PCRE2); + SubstitutionRule rule2 = (@"world", "universe"); + Console.WriteLine($"Rule2 created with: {RegexEngineFactory.DefaultEngine.Name}"); + + var transformer = new TextTransformer(new[] { rule1, rule2 }); + + string input = "hello world"; + string result = transformer.Transform(input); + + Console.WriteLine($"Input: {input}"); + Console.WriteLine($"Output: {result}"); + + // Reset to default + RegexEngineFactory.SetDefaultEngine(RegexEngineType.SystemRegex); + } + } +} \ No newline at end of file