Find all words enclosed within @{{Word}}@ or {{Word}} from string

Viewed 107

I am writing code to extract all the words enclosed within @{{}}@ and {{}}, so far I have searched the web and found the below code which works as expected.

string sampleString = "A @{{Quick}}@ brown @{{fox}}@ jumps @{{over}}@ a lazy {{dog}}.";
List<string> keywordList = new List<string>();
MatchCollection matchedCollection = Regex.Matches(sampleString, @"(@{{(.*?)}}@|{{(.*?)}})");

foreach (Match m in matchedCollection)
{
    keywordList.Add(m.ToString());
}

Above code works fine, it gives me 4 items listed below and that is correct.

  1. @{{Quick}}@
  2. @{{Fox}}@
  3. @{{Over}}@
  4. {{dog}}

But, the problem arises when the word is not properly enclosed in the brackets/pattern. For example, if I have improperly formatted string like below, I'll get incorrect result.

string sampleString = "A @{{Quick}}@ brown @{{fox jumps @{{over}}@ a lazy {{dog}}.";

Code with above string input will give me three items in list.

Current Result:

  1. @{{Quick}}@
  2. @{{fox jumps @{{over}}@
  3. {{dog}}

Expected Result

  1. @{{Quick}}@
  2. @{{over}}@
  3. {{dog}}

Any suggestion to correct this would be really appreciated.

4 Answers

If all you want is words-without-spaces inside, then you can use \S instead of . (any character that isn't a space instead of any character)

MatchCollection matchedCollection = Regex.Matches(sampleString, @"(@{{(\S*?)}}@|{{(\S*?)}})");

The repetition in the regex seems redundant, unless you really care to know whether the @ were present or not. If you're stripping them off then Regex.Matches(sampleString, @"{{(\S*?)}}") will be fine

If the double curly braces should be exact 2, you can use lookarounds to assert that there are no more curly braces before and after.

(@?)(?<!{){{(?!{)\S+?(?<!})}}(?!})\1

Explanation

  • (@?) Capture group 1, match an optional @
  • (?<!{){{(?!{) Match {{ not preceded or followed by {
  • \S+? Match 1+ times as least as possible non whitespace chars
  • (?<!})}}(?!}) Match }} not preceded or followed by }
  • \1 Backreference to what is captured in group 1

.NET regex demo

If it is no problem to have more than 2 curly braces, you can omit the lookarounds

(@?){{\S+?}}\1

.NET regex demo

You should not worry if your matches contain spaces or not, use a proper regex like

(@)?{{(?:(?!@{{).)*?}}(?(1)@|)

See proof.

C# code:

var matchedCollection = Regex.Matches(sampleString, @"(@)?{{(?:(?!@{{).)*?}}(?(1)@|)", RegexOptions.Singleline);

Explanation

--------------------------------------------------------------------------------
  (                        group and capture to \1 (optional):
--------------------------------------------------------------------------------
    @                        '@'
--------------------------------------------------------------------------------
  )?                       end of \1
--------------------------------------------------------------------------------
  {{                       '{{'
--------------------------------------------------------------------------------
  (?:                      group, but do not capture (0 or more times
                           (matching the least amount possible)):
--------------------------------------------------------------------------------
    (?!                      look ahead to see if there is not:
--------------------------------------------------------------------------------
      @{{                      '@{{'
--------------------------------------------------------------------------------
    )                        end of look-ahead
--------------------------------------------------------------------------------
    .                        any character (including \n
                             with RegexOptions.Singleline)
--------------------------------------------------------------------------------
  )*?                      end of grouping
--------------------------------------------------------------------------------
  }}                       '}}'
--------------------------------------------------------------------------------
  (?(1)                    if back-reference \1 matched, then:
--------------------------------------------------------------------------------
    @                        '@'
--------------------------------------------------------------------------------
   |                        else:
--------------------------------------------------------------------------------
                             succeed
--------------------------------------------------------------------------------
  )                        end of conditional on \1
Related