Regex pattern definition implementation

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
3
down vote

favorite












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.







share|improve this question















  • 2




    [tT][oO][dD][aA][yY] - you know that you could just use the RegexOptions.IgnoreCase, don't you?
    – t3chb0t
    Jan 26 at 21:07











  • I do but it applies to the complete pattern, in some matches case sensitivity is needed.
    – Raul Sebastian
    Jan 26 at 21:14






  • 2




    Why don't you just add another parameter to the PlaceholderDefinition that is of type RegexOptions?
    – t3chb0t
    Jan 26 at 21:18










  • that is a good idea, I will do so
    – Raul Sebastian
    Jan 26 at 21:30
















up vote
3
down vote

favorite












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.







share|improve this question















  • 2




    [tT][oO][dD][aA][yY] - you know that you could just use the RegexOptions.IgnoreCase, don't you?
    – t3chb0t
    Jan 26 at 21:07











  • I do but it applies to the complete pattern, in some matches case sensitivity is needed.
    – Raul Sebastian
    Jan 26 at 21:14






  • 2




    Why don't you just add another parameter to the PlaceholderDefinition that is of type RegexOptions?
    – t3chb0t
    Jan 26 at 21:18










  • that is a good idea, I will do so
    – Raul Sebastian
    Jan 26 at 21:30












up vote
3
down vote

favorite









up vote
3
down vote

favorite











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.







share|improve this question











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.









share|improve this question










share|improve this question




share|improve this question









asked Jan 26 at 20:52









Raul Sebastian

1184




1184







  • 2




    [tT][oO][dD][aA][yY] - you know that you could just use the RegexOptions.IgnoreCase, don't you?
    – t3chb0t
    Jan 26 at 21:07











  • I do but it applies to the complete pattern, in some matches case sensitivity is needed.
    – Raul Sebastian
    Jan 26 at 21:14






  • 2




    Why don't you just add another parameter to the PlaceholderDefinition that is of type RegexOptions?
    – t3chb0t
    Jan 26 at 21:18










  • that is a good idea, I will do so
    – Raul Sebastian
    Jan 26 at 21:30












  • 2




    [tT][oO][dD][aA][yY] - you know that you could just use the RegexOptions.IgnoreCase, don't you?
    – t3chb0t
    Jan 26 at 21:07











  • I do but it applies to the complete pattern, in some matches case sensitivity is needed.
    – Raul Sebastian
    Jan 26 at 21:14






  • 2




    Why don't you just add another parameter to the PlaceholderDefinition that is of type RegexOptions?
    – t3chb0t
    Jan 26 at 21:18










  • that is a good idea, I will do so
    – Raul Sebastian
    Jan 26 at 21:30







2




2




[tT][oO][dD][aA][yY] - you know that you could just use the RegexOptions.IgnoreCase, don't you?
– t3chb0t
Jan 26 at 21:07





[tT][oO][dD][aA][yY] - you know that you could just use the RegexOptions.IgnoreCase, don't you?
– t3chb0t
Jan 26 at 21:07













I do but it applies to the complete pattern, in some matches case sensitivity is needed.
– Raul Sebastian
Jan 26 at 21:14




I do but it applies to the complete pattern, in some matches case sensitivity is needed.
– Raul Sebastian
Jan 26 at 21:14




2




2




Why don't you just add another parameter to the PlaceholderDefinition that is of type RegexOptions?
– t3chb0t
Jan 26 at 21:18




Why don't you just add another parameter to the PlaceholderDefinition that is of type RegexOptions?
– t3chb0t
Jan 26 at 21:18












that is a good idea, I will do so
– Raul Sebastian
Jan 26 at 21:30




that is a good idea, I will do so
– Raul Sebastian
Jan 26 at 21:30










1 Answer
1






active

oldest

votes

















up vote
3
down vote



accepted











  • Try to use named group. As your pattern becomes more complex, it will get harder to maintain.



    @"(?:[yY][eE][aA][rR])([+-])(d+)"
    var operation = m.Groups[1].Value;
    int.TryParse(m.Groups[2].Value, out int value);



  • PlaceholderDefinition implementation requires way too much boilerplate code to use:



     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();




    It could be boiled down to: content = placeholder.Apply(text);



  • There is no need to use Match.NextMatch & string.Remove, when you can use Regex.Replace



  • _logic seems to be a poorly chosen name. While it describes the essence, but not its concrete job. You should name Func with "Selector, factory, builder, ...", and prefix it with a noun ("result, replacement, ...") and the private member prefix _ (if this is the convention, you choose to follow).


Modified PlaceholderDefinition class:



public class PlaceholderDefinition

public string Name get;
public Regex Pattern get;

private readonly Func<GroupCollection, object> _replacementSelector;

public PlaceholderDefinition(string name, string pattern, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern), replacementSelector)


public PlaceholderDefinition(string name, string pattern, RegexOptions options, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern, options), replacementSelector)


public PlaceholderDefinition(string name, Regex pattern, Func<GroupCollection, object> replacementSelector)

this.Name = name;
this.Pattern = pattern;
this._replacementSelector = replacementSelector;


public string Apply(string input) => Pattern.Replace(input, m => _replacementSelector(m.Groups).ToString());



There is various overloads of ctor to accommodate different needs, like the one suggested by @t3chb0t. Feels free to add more, if it help you to keep the declarations as clean as possible.



And, the rest of the code:



var text = "Today is today. The year is year. Last year was year-1.";
var placeholders = new List<PlaceholderDefinition>();
placeholders.Add(new PlaceholderDefinition("Today", @"today", RegexOptions.IgnoreCase, _ => DateTime.Today.ToShortDateString()));
placeholders.Add(new PlaceholderDefinition("Year", @"year", RegexOptions.IgnoreCase, _ => DateTime.Today.Year));
placeholders.Add(new PlaceholderDefinition(
"YearAddition",
@"year(?<sign>[+-])(?<value>d+)",
RegexOptions.IgnoreCase,
g => DateTime.Today.Year + int.Parse(g["sign"].Value + g["value"].Value)
));

foreach (var placeholder in placeholders)

text = placeholder.Apply(text);




  • The _replacementSelector takes in GroupCollection, so we can skip writing m => m.Group..., and the object TResult allows us to skip the necessary .ToString() or a parenthesis on binary operation.


  • int.Parse will parse number prefixed by a + sign as positive number, allowing us to skip the ternary operation.





share|improve this answer























  • Thanks for the effort. These are some great suggestions.
    – Raul Sebastian
    Jan 26 at 22:38










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186088%2fregex-pattern-definition-implementation%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
3
down vote



accepted











  • Try to use named group. As your pattern becomes more complex, it will get harder to maintain.



    @"(?:[yY][eE][aA][rR])([+-])(d+)"
    var operation = m.Groups[1].Value;
    int.TryParse(m.Groups[2].Value, out int value);



  • PlaceholderDefinition implementation requires way too much boilerplate code to use:



     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();




    It could be boiled down to: content = placeholder.Apply(text);



  • There is no need to use Match.NextMatch & string.Remove, when you can use Regex.Replace



  • _logic seems to be a poorly chosen name. While it describes the essence, but not its concrete job. You should name Func with "Selector, factory, builder, ...", and prefix it with a noun ("result, replacement, ...") and the private member prefix _ (if this is the convention, you choose to follow).


Modified PlaceholderDefinition class:



public class PlaceholderDefinition

public string Name get;
public Regex Pattern get;

private readonly Func<GroupCollection, object> _replacementSelector;

public PlaceholderDefinition(string name, string pattern, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern), replacementSelector)


public PlaceholderDefinition(string name, string pattern, RegexOptions options, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern, options), replacementSelector)


public PlaceholderDefinition(string name, Regex pattern, Func<GroupCollection, object> replacementSelector)

this.Name = name;
this.Pattern = pattern;
this._replacementSelector = replacementSelector;


public string Apply(string input) => Pattern.Replace(input, m => _replacementSelector(m.Groups).ToString());



There is various overloads of ctor to accommodate different needs, like the one suggested by @t3chb0t. Feels free to add more, if it help you to keep the declarations as clean as possible.



And, the rest of the code:



var text = "Today is today. The year is year. Last year was year-1.";
var placeholders = new List<PlaceholderDefinition>();
placeholders.Add(new PlaceholderDefinition("Today", @"today", RegexOptions.IgnoreCase, _ => DateTime.Today.ToShortDateString()));
placeholders.Add(new PlaceholderDefinition("Year", @"year", RegexOptions.IgnoreCase, _ => DateTime.Today.Year));
placeholders.Add(new PlaceholderDefinition(
"YearAddition",
@"year(?<sign>[+-])(?<value>d+)",
RegexOptions.IgnoreCase,
g => DateTime.Today.Year + int.Parse(g["sign"].Value + g["value"].Value)
));

foreach (var placeholder in placeholders)

text = placeholder.Apply(text);




  • The _replacementSelector takes in GroupCollection, so we can skip writing m => m.Group..., and the object TResult allows us to skip the necessary .ToString() or a parenthesis on binary operation.


  • int.Parse will parse number prefixed by a + sign as positive number, allowing us to skip the ternary operation.





share|improve this answer























  • Thanks for the effort. These are some great suggestions.
    – Raul Sebastian
    Jan 26 at 22:38














up vote
3
down vote



accepted











  • Try to use named group. As your pattern becomes more complex, it will get harder to maintain.



    @"(?:[yY][eE][aA][rR])([+-])(d+)"
    var operation = m.Groups[1].Value;
    int.TryParse(m.Groups[2].Value, out int value);



  • PlaceholderDefinition implementation requires way too much boilerplate code to use:



     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();




    It could be boiled down to: content = placeholder.Apply(text);



  • There is no need to use Match.NextMatch & string.Remove, when you can use Regex.Replace



  • _logic seems to be a poorly chosen name. While it describes the essence, but not its concrete job. You should name Func with "Selector, factory, builder, ...", and prefix it with a noun ("result, replacement, ...") and the private member prefix _ (if this is the convention, you choose to follow).


Modified PlaceholderDefinition class:



public class PlaceholderDefinition

public string Name get;
public Regex Pattern get;

private readonly Func<GroupCollection, object> _replacementSelector;

public PlaceholderDefinition(string name, string pattern, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern), replacementSelector)


public PlaceholderDefinition(string name, string pattern, RegexOptions options, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern, options), replacementSelector)


public PlaceholderDefinition(string name, Regex pattern, Func<GroupCollection, object> replacementSelector)

this.Name = name;
this.Pattern = pattern;
this._replacementSelector = replacementSelector;


public string Apply(string input) => Pattern.Replace(input, m => _replacementSelector(m.Groups).ToString());



There is various overloads of ctor to accommodate different needs, like the one suggested by @t3chb0t. Feels free to add more, if it help you to keep the declarations as clean as possible.



And, the rest of the code:



var text = "Today is today. The year is year. Last year was year-1.";
var placeholders = new List<PlaceholderDefinition>();
placeholders.Add(new PlaceholderDefinition("Today", @"today", RegexOptions.IgnoreCase, _ => DateTime.Today.ToShortDateString()));
placeholders.Add(new PlaceholderDefinition("Year", @"year", RegexOptions.IgnoreCase, _ => DateTime.Today.Year));
placeholders.Add(new PlaceholderDefinition(
"YearAddition",
@"year(?<sign>[+-])(?<value>d+)",
RegexOptions.IgnoreCase,
g => DateTime.Today.Year + int.Parse(g["sign"].Value + g["value"].Value)
));

foreach (var placeholder in placeholders)

text = placeholder.Apply(text);




  • The _replacementSelector takes in GroupCollection, so we can skip writing m => m.Group..., and the object TResult allows us to skip the necessary .ToString() or a parenthesis on binary operation.


  • int.Parse will parse number prefixed by a + sign as positive number, allowing us to skip the ternary operation.





share|improve this answer























  • Thanks for the effort. These are some great suggestions.
    – Raul Sebastian
    Jan 26 at 22:38












up vote
3
down vote



accepted







up vote
3
down vote



accepted







  • Try to use named group. As your pattern becomes more complex, it will get harder to maintain.



    @"(?:[yY][eE][aA][rR])([+-])(d+)"
    var operation = m.Groups[1].Value;
    int.TryParse(m.Groups[2].Value, out int value);



  • PlaceholderDefinition implementation requires way too much boilerplate code to use:



     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();




    It could be boiled down to: content = placeholder.Apply(text);



  • There is no need to use Match.NextMatch & string.Remove, when you can use Regex.Replace



  • _logic seems to be a poorly chosen name. While it describes the essence, but not its concrete job. You should name Func with "Selector, factory, builder, ...", and prefix it with a noun ("result, replacement, ...") and the private member prefix _ (if this is the convention, you choose to follow).


Modified PlaceholderDefinition class:



public class PlaceholderDefinition

public string Name get;
public Regex Pattern get;

private readonly Func<GroupCollection, object> _replacementSelector;

public PlaceholderDefinition(string name, string pattern, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern), replacementSelector)


public PlaceholderDefinition(string name, string pattern, RegexOptions options, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern, options), replacementSelector)


public PlaceholderDefinition(string name, Regex pattern, Func<GroupCollection, object> replacementSelector)

this.Name = name;
this.Pattern = pattern;
this._replacementSelector = replacementSelector;


public string Apply(string input) => Pattern.Replace(input, m => _replacementSelector(m.Groups).ToString());



There is various overloads of ctor to accommodate different needs, like the one suggested by @t3chb0t. Feels free to add more, if it help you to keep the declarations as clean as possible.



And, the rest of the code:



var text = "Today is today. The year is year. Last year was year-1.";
var placeholders = new List<PlaceholderDefinition>();
placeholders.Add(new PlaceholderDefinition("Today", @"today", RegexOptions.IgnoreCase, _ => DateTime.Today.ToShortDateString()));
placeholders.Add(new PlaceholderDefinition("Year", @"year", RegexOptions.IgnoreCase, _ => DateTime.Today.Year));
placeholders.Add(new PlaceholderDefinition(
"YearAddition",
@"year(?<sign>[+-])(?<value>d+)",
RegexOptions.IgnoreCase,
g => DateTime.Today.Year + int.Parse(g["sign"].Value + g["value"].Value)
));

foreach (var placeholder in placeholders)

text = placeholder.Apply(text);




  • The _replacementSelector takes in GroupCollection, so we can skip writing m => m.Group..., and the object TResult allows us to skip the necessary .ToString() or a parenthesis on binary operation.


  • int.Parse will parse number prefixed by a + sign as positive number, allowing us to skip the ternary operation.





share|improve this answer
















  • Try to use named group. As your pattern becomes more complex, it will get harder to maintain.



    @"(?:[yY][eE][aA][rR])([+-])(d+)"
    var operation = m.Groups[1].Value;
    int.TryParse(m.Groups[2].Value, out int value);



  • PlaceholderDefinition implementation requires way too much boilerplate code to use:



     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();




    It could be boiled down to: content = placeholder.Apply(text);



  • There is no need to use Match.NextMatch & string.Remove, when you can use Regex.Replace



  • _logic seems to be a poorly chosen name. While it describes the essence, but not its concrete job. You should name Func with "Selector, factory, builder, ...", and prefix it with a noun ("result, replacement, ...") and the private member prefix _ (if this is the convention, you choose to follow).


Modified PlaceholderDefinition class:



public class PlaceholderDefinition

public string Name get;
public Regex Pattern get;

private readonly Func<GroupCollection, object> _replacementSelector;

public PlaceholderDefinition(string name, string pattern, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern), replacementSelector)


public PlaceholderDefinition(string name, string pattern, RegexOptions options, Func<GroupCollection, object> replacementSelector)
: this(name, new Regex(pattern, options), replacementSelector)


public PlaceholderDefinition(string name, Regex pattern, Func<GroupCollection, object> replacementSelector)

this.Name = name;
this.Pattern = pattern;
this._replacementSelector = replacementSelector;


public string Apply(string input) => Pattern.Replace(input, m => _replacementSelector(m.Groups).ToString());



There is various overloads of ctor to accommodate different needs, like the one suggested by @t3chb0t. Feels free to add more, if it help you to keep the declarations as clean as possible.



And, the rest of the code:



var text = "Today is today. The year is year. Last year was year-1.";
var placeholders = new List<PlaceholderDefinition>();
placeholders.Add(new PlaceholderDefinition("Today", @"today", RegexOptions.IgnoreCase, _ => DateTime.Today.ToShortDateString()));
placeholders.Add(new PlaceholderDefinition("Year", @"year", RegexOptions.IgnoreCase, _ => DateTime.Today.Year));
placeholders.Add(new PlaceholderDefinition(
"YearAddition",
@"year(?<sign>[+-])(?<value>d+)",
RegexOptions.IgnoreCase,
g => DateTime.Today.Year + int.Parse(g["sign"].Value + g["value"].Value)
));

foreach (var placeholder in placeholders)

text = placeholder.Apply(text);




  • The _replacementSelector takes in GroupCollection, so we can skip writing m => m.Group..., and the object TResult allows us to skip the necessary .ToString() or a parenthesis on binary operation.


  • int.Parse will parse number prefixed by a + sign as positive number, allowing us to skip the ternary operation.






share|improve this answer















share|improve this answer



share|improve this answer








edited Jan 26 at 22:45


























answered Jan 26 at 22:33









Xiaoy312

2,767915




2,767915











  • Thanks for the effort. These are some great suggestions.
    – Raul Sebastian
    Jan 26 at 22:38
















  • Thanks for the effort. These are some great suggestions.
    – Raul Sebastian
    Jan 26 at 22:38















Thanks for the effort. These are some great suggestions.
– Raul Sebastian
Jan 26 at 22:38




Thanks for the effort. These are some great suggestions.
– Raul Sebastian
Jan 26 at 22:38












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186088%2fregex-pattern-definition-implementation%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Chat program with C++ and SFML

Function to Return a JSON Like Objects Using VBA Collections and Arrays

Will my employers contract hold up in court?