Oscar Mederos
Oscar Mederos

Reputation: 29823

Replace only some groups with Regex

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.

Upvotes: 247

Views: 180066

Answers (9)

Alex from Jitbit
Alex from Jitbit

Reputation: 60642

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 available 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

Upvotes: 1

KamilKaczorek
KamilKaczorek

Reputation: 31

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;
    }
}

Upvotes: 2

Vladimir
Vladimir

Reputation: 1420

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;

Upvotes: 1

BalaS
BalaS

Reputation: 1

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

Upvotes: 0

curlyhairedgenius
curlyhairedgenius

Reputation: 922

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

Upvotes: 6

Daniel Hilgarth
Daniel Hilgarth

Reputation: 174309

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")]

Upvotes: 26

Dick Verweij
Dick Verweij

Reputation: 149

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;

Upvotes: 14

bluepnume
bluepnume

Reputation: 17128

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+)(?=-)

Upvotes: 412

LukeH
LukeH

Reputation: 269368

You can do this using lookahead and lookbehind:

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

Upvotes: 42

Related Questions