JavaScript - Capture repeated group

Viewed 86

Using JavaScript (or VBScript) Is it possible to separately capture from %n:32+5000 the following groups?:

  • Capture group 1: %n
  • Capture group 2: :32
  • Capture group 3: +5000

I tried a search through this forum about capturing repeated groups, but the examples given were either in a different language like .NET or the asker wasn't asking specifically how to do what I need.

The best attempt I've made thus far is (%n)(([:\+]\d+){0,2}) with global turned on. Also, I am using https://regex101.com/r/qBylQX/1 to help me visualize what's happening; but, so far I haven't cracked it.

Notes:

  • only one instance of %n is allowed per
  • only one appearance of :\d+ is allowed per match
  • only one appearance of +\d+ is allowed per match.
  • the pattern can appear anywhere in a string.
2 Answers

Use

(%n)(?:(:\d+)(\+\d+)?)?

See regex proof.

EXPLANATION

--------------------------------------------------------------------------------
  (                        group and capture to \1:
--------------------------------------------------------------------------------
    %n                       '%n'
--------------------------------------------------------------------------------
  )                        end of \1
--------------------------------------------------------------------------------
  (?:                      group, but do not capture (optional
                           (matching the most amount possible)):
--------------------------------------------------------------------------------
    (                        group and capture to \2:
--------------------------------------------------------------------------------
      :                        ':'
--------------------------------------------------------------------------------
      \d+                      digits (0-9) (1 or more times
                               (matching the most amount possible))
--------------------------------------------------------------------------------
    )                        end of \2
--------------------------------------------------------------------------------
    (                        group and capture to \3 (optional
                             (matching the most amount possible)):
--------------------------------------------------------------------------------
      \+                       '+'
--------------------------------------------------------------------------------
      \d+                      digits (0-9) (1 or more times
                               (matching the most amount possible))
--------------------------------------------------------------------------------
    )?                       end of \3 (NOTE: because you are using a
                             quantifier on this capture, only the
                             LAST repetition of the captured pattern
                             will be stored in \3)
--------------------------------------------------------------------------------
  )?                       end of grouping

Using this pattern (%n)(([:\+]\d+){0,2}) can also match 2 times :\d+ or 2 times +\d+ as both the : and + are in the character class.

Repeating a capture group like ([:\+]\d+){0,2} will only capture the value of the last iteration in the group.

As this repetition is inside the outer capture group 2, the group 2 value will contain the whole match and you don't have a nice separation of values in groups that you are after.


According to the notes only one occurrence can of either one of them can be in the match.

If you want to match both combinations of : and + but if they can not be the same, you can use a capture group with a backreference.

The values are in group 1, 3 and 4.

(%n)(?:([:+])(\d+)(?:(?!\2)([:+]\d+))?)?(?!\S)

The pattern matches:

  • (%n) Capture group 1, match %n
  • (?: Non capture group to match as a whole
    • ([:+]) Capture group 2, match either : or +
    • (\d+) Capture group 3, match 1+ digits
    • (?: Non capture group to match as a whole
      • (?!\2) Negative lookahead, assert what is directly to the right is not the same value as captured in group 2
      • ([:+]\d+) Capture group 4, match : or +
    • )? Close group and make it optional
  • )? Close group and make it optional
  • (?!\S) Assert a whitespace boundary to the right to prevent a partial match

Regex demo

const regex = /(%n)(?:([:+])(\d+)(?:(?!\2)([:+]\d+))?)?(?!\S)/;
[
  "%n:32+5000",
  "%n:32",
  "%n+5000",
  "%n",
  "%n+32:5000",
  "%n+32",
  "%n:5000",
  "%n:32:5000",
  "%n+32+5000"
].forEach(s => console.log(`${regex.test(s)} ==> ${s}`));

Related