My requirement is to design a part of a solution in such matter that specific placeholders are replaces dynamically with a defined logic and new rules can be easily added.
Simple example:
Today is {today}. The year is {year}. Last year was {year-1}.
Should output something like:
Today is 26 Jan 2018. The year is 2018. Last year was 2017.
There are several more business and context specific placeholders and new rules can follow frequently.
As such I came up with this class:
public class PlaceholderDefinition
{
public string Name { get; }
public string Pattern { get; }
private readonly Func<Match, string> _logic;
public PlaceholderDefinition(string name, string pattern, Func<Match, string> logic)
{
Name = name;
Pattern = pattern;
_logic = logic;
}
public string Apply(Match match) => _logic.Invoke(match);
}
To fulfill the above example, I register the following PlaceholderDefinitions
in the service configuration:
RegisteredPlaceholders.Add(
new PlaceholderDefinition(
"Today",
@"{[tT][oO][dD][aA][yY]}",
(_) => DateTime.Today.ToShortDateString()
));
RegisteredPlaceholders.Add(
new PlaceholderDefinition(
"Year",
@"{[yY][eE][aA][rR]}",
(_) => DateTime.Today.Year.ToString()
));
RegisteredPlaceholders.Add(
new PlaceholderDefinition(
"YearAddition",
@"(?:{[yY][eE][aA][rR])([+-])(\d+)}",
(m) =>
{
var operation = m.Groups[1].Value;
int.TryParse(m.Groups[2].Value, out int value);
return (operation == "+" ? DateTime.Today.Year + value : DateTime.Today.Year - value).ToString();
}
));
The RegisteredPlaceholders
is an enumerable of PlaceholderDefinition held in the container.
The logic of these patterns/placeholders is applied like this:
foreach (var placeholder in RegisteredPlaceholders)
{
var match = new Regex(placeholder.Pattern).Match(content);
while (match.Success)
{
content = content.Remove(match.Index, match.Length).Insert(match.Index, placeholder.Apply(match));
match = match.NextMatch();
}
}
I decided to take this approach instead of using Regex.Replace()
to not reveal the content or text to the class defining the pattern logic. Maybe there is a more elegant solution, that I didn't come up with.
Every critique, improvement proposal, code smells are welcome.
[tT][oO][dD][aA][yY]
- you know that you could just use theRegexOptions.IgnoreCase
, don't you? \$\endgroup\$PlaceholderDefinition
that is of typeRegexOptions
? \$\endgroup\$