Replace only some groups with Regex
Asked Answered
O

9

242

Let's suppose I have the following regex:

-(\d+)-

and I want to replace, using C#, the Group 1 (\d+) with AA, to obtain:

-AA-

Now I'm replacing it using:

var text = "example-123-example";
var pattern = @"-(\d+)-";
var replaced = Regex.Replace(text, pattern, "-AA-"); 

But I don't really like this, because if I change the pattern to match _(\d+)_ instead, I will have to change the replacement string by _AA_ too, and this is against the DRY principle.

I'm looking for something like:

Keep the matched text exactly how it is, but change Group 1 by this text and Group 2 by another text...

Edit:
That was just an example. I'm just looking for a generic way of doing what I said above.

It should work for:

anything(\d+)more_text and any pattern you can imagine.

All I want to do is replace only groups, and keep the rest of the match.

Overalls answered 15/5, 2011 at 0:7 Comment(0)
C
405

A good idea could be to encapsulate everything inside groups, no matter if need to identify them or not. That way you can use them in your replacement string. For example:

var pattern = @"(-)(\d+)(-)";
var replaced = Regex.Replace(text, pattern, "$1AA$3"); 

or using a MatchEvaluator:

var replaced = Regex.Replace(text, pattern, m => m.Groups[1].Value + "AA" + m.Groups[3].Value);

Another way, slightly messy, could be using a lookbehind/lookahead:

(?<=-)(\d+)(?=-)

Curvilinear answered 15/5, 2011 at 0:13 Comment(9)
I edited your answer to provide more info, but what you said is totally correct. Don't know how I missed that I could put everything inside groups, no matter if will use them or not :). In my opinion, that solution is much better and cleaner than using lookahead and lookbehinds.Overalls
small typo, your replacement pattern should be $1AA$3Klimesh
This works only when your regex pattern matches the entire input string. If your regex is supposed to return more matches, this approach won't work anymore.Coquito
In order for this to work, I had to add .Value to m.Groups[1] etc.Aldora
Also worth noting - if your replacement text starts with a number, the first solution ("$1AA$3") won't work as intended!Vaasta
I like the messy way using lookbehinds/aheads - Althought the question asks what if I change -(\d+)- to _(\d+)_ - Couldn't you just do \D+\d+\D+?Enneastyle
i used this: Regex r = new Regex("(\\d*)(\\.\\w)$", RegexOptions.Compiled); and this r.Replace(@"C:\crexasd\dsD_3245.miu", 654+"$2") didn't worked at all, always returneed the original stringWavy
@OscarMederos you can also use non capturing groups - good for groups you don't use. In (?:foo)(bar), $1 will replace bar. more detailsBrie
Please always use named groups instead of numbers for better readability/maintainability (see: #12962614)Ri
I
42

You can do this using lookahead and lookbehind:

var pattern = @"(?<=-)\d+(?=-)";
var replaced = Regex.Replace(text, pattern, "AA"); 
Inhumanity answered 15/5, 2011 at 0:13 Comment(0)
J
25

I also had need for this and I created the following extension method for it:

public static class RegexExtensions
{
    public static string ReplaceGroup(
        this Regex regex, string input, string groupName, string replacement)
    {
        return regex.Replace(
            input,
            m =>
            {
                var group = m.Groups[groupName];
                var sb = new StringBuilder();
                var previousCaptureEnd = 0;
                foreach (var capture in group.Captures.Cast<Capture>())
                {
                    var currentCaptureEnd =
                        capture.Index + capture.Length - m.Index;
                    var currentCaptureLength =
                        capture.Index - m.Index - previousCaptureEnd;
                    sb.Append(
                        m.Value.Substring(
                            previousCaptureEnd, currentCaptureLength));
                    sb.Append(replacement);
                    previousCaptureEnd = currentCaptureEnd;
                }
                sb.Append(m.Value.Substring(previousCaptureEnd));

                return sb.ToString();
            });
    }
}

Usage:

var input = @"[assembly: AssemblyFileVersion(""2.0.3.0"")][assembly: AssemblyFileVersion(""2.0.3.0"")]";
var regex = new Regex(@"AssemblyFileVersion\(""(?<version>(\d+\.?){4})""\)");


var result = regex.ReplaceGroup(input , "version", "1.2.3");

Result:

[assembly: AssemblyFileVersion("1.2.3")][assembly: AssemblyFileVersion("1.2.3")]
Jackdaw answered 26/8, 2014 at 19:42 Comment(1)
I like this implementation but it does not replace multiple matches. I posted a version which doesDaviddavida
T
14

If you don't want to change your pattern you can use the Group Index and Length properties of a matched group.

var text = "example-123-example";
var pattern = @"-(\d+)-";
var regex = new RegEx(pattern);
var match = regex.Match(text);

var firstPart = text.Substring(0,match.Groups[1].Index);    
var secondPart = text.Substring(match.Groups[1].Index + match.Groups[1].Length);
var fullReplace = firstPart + "AA" + secondPart;
Thorman answered 1/2, 2013 at 15:52 Comment(1)
Please note that this assumes and will only work for the first occurence of the match.Man
O
6

Here is another nice clean option that does not require changing your pattern.

        var text = "example-123-example";
        var pattern = @"-(\d+)-";

        var replaced = Regex.Replace(text, pattern, (_match) =>
        {
            Group group = _match.Groups[1];
            string replace = "AA";
            return String.Format("{0}{1}{2}", _match.Value.Substring(0, group.Index - _match.Index), replace, _match.Value.Substring(group.Index - _match.Index + group.Length));
        });
Oversubtlety answered 15/6, 2015 at 18:24 Comment(0)
D
2

Replace code:

var text = "example-123-example";
var pattern = @"-(\d+)-";
var replaced = Regex.ReplaceGroupValue(text, pattern, 1, "AA");

Extension class:

public static class RegexExtensions
{
    [Pure]
    public static string ReplaceGroupValue(this Regex source, string input, string groupName, string destinationValue)
    {
        return ReplaceGroupValue(
            source,
            input,
            m => m.Groups[groupName],
            p => destinationValue);
    }

    [Pure]
    public static string ReplaceGroupValue(this Regex source, string input, int groupIdx, string destinationValue)
    {
        return ReplaceGroupValue(
            source,
            input,
            m => m.Groups[groupIdx],
            p => destinationValue);
    }

    [Pure]
    public static string ReplaceGroupValue(this Regex source, string input, string groupName, Func<string, string> destinationValueSelector)
    {
        return ReplaceGroupValue(
            source,
            input,
            m => m.Groups[groupName],
            destinationValueSelector);
    }

    [Pure]
    public static string ReplaceGroupValue(this Regex source, string input, int groupIdx, Func<string, string> destinationValueSelector)
    {
        return ReplaceGroupValue(
            source,
            input,
            m => m.Groups[groupIdx],
            destinationValueSelector);
    }

    [Pure]
    private static string ReplaceGroupValue(
        Regex source,
        string input,
        Func<Match, Group> groupSelector,
        Func<string, string> destinationValueSelector)
    {
        var matchResult = source.Matches(input);

        if (matchResult.Count <= 0)
        {
            return input;
        }

        var text = input;

        foreach (var group in matchResult.OfType<Match>().Select(groupSelector).OrderByDescending(p => p.Index))
        {
            var begin = group.Index > 0 ? text.Substring(0, group.Index) : string.Empty;
            var end = group.Index + group.Length < text.Length
                ? text.Substring(group.Index + group.Length)
                : string.Empty;
            var destinationValue = destinationValueSelector.Invoke(group.Value);
            text = $"{begin}{destinationValue}{end}";
        }

        return text;
    }
}
Decease answered 19/5, 2021 at 14:43 Comment(1)
Generally, answers are much more helpful if they include an explanation of what the code is intended to do, and why that solves the problem without introducing others.Bhutan
D
1

Here is a version similar to Daniel's but replacing multiple matches:

public static string ReplaceGroup(string input, string pattern, RegexOptions options, string groupName, string replacement)
{
    Match match;
    while ((match = Regex.Match(input, pattern, options)).Success)
    {
        var group = match.Groups[groupName];

        var sb = new StringBuilder();

        // Anything before the match
        if (match.Index > 0)
            sb.Append(input.Substring(0, match.Index));

        // The match itself
        var startIndex = group.Index - match.Index;
        var length = group.Length;
        var original = match.Value;
        var prior = original.Substring(0, startIndex);
        var trailing = original.Substring(startIndex + length);
        sb.Append(prior);
        sb.Append(replacement);
        sb.Append(trailing);

        // Anything after the match
        if (match.Index + match.Length < input.Length)
            sb.Append(input.Substring(match.Index + match.Length));

        input = sb.ToString();
    }

    return input;
Daviddavida answered 7/7, 2020 at 14:52 Comment(1)
Very nice, just what I needed, thanks. It can cause an infinite loop if the replacement something that will match again though; just gotta be careful.Datcha
M
0

go through the below coding to get the separate group replacement.

new_bib = Regex.Replace(new_bib, @"(?s)(\\bibitem\[[^\]]+\]\{" + pat4 + @"\})[\s\n\v]*([\\\{\}a-zA-Z\.\s\,\;\\\#\\\$\\\%\\\&\*\@\\\!\\\^+\-\\\=\\\~\\\:\\\" + dblqt + @"\\\;\\\`\\\']{20,70})", delegate(Match mts)
                    {
                           var fg = mts.Groups[0].Value.ToString(); 
                           var fs = mts.Groups[1].Value.ToString();
                           var fss = mts.Groups[2].Value.ToString();
                               fss = Regex.Replace(fss, @"[\\\{\}\\\#\\\$\\\%\\\&\*\@\\\!\\\^+\-\\\=\\\~\\\:\\\" + dblqt + @"\\\;\\\`\\\']+", "");
                           return "<augroup>" + fss + "</augroup>" + fs;
                    }, RegexOptions.IgnoreCase);
Marquesan answered 14/10, 2015 at 11:44 Comment(0)
R
0

In 2024 there's another, very performant option that:

  1. uses Span to prevent allocations, and avoids Substring and StringBuilder and other expensive stuff
  2. Works with multiple matches
  3. Incredibly simple and doesn't need you to update the pattern

Here's the code:

var outputString = MyRegex.Replace(inputString, m =>
{
    var grp = m.Groups[1];
    return string.Concat(
        m.ValueSpan.Slice(0, grp.Index - m.Index), //prior part
        PUT_REPLACEMENT_HERE, //replacement
        m.ValueSpan.Slice(grp.Index + grp.Length - m.Index) //trailing part
    );
});

P.S. ValueSpan is avalaible in .NET 6 and later

P.P.S. If you're on .NET 7/8 - use [GeneratedRegex] to pre-build your regex at compile time

Roanne answered 13/4, 2024 at 20:33 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.