Regex behavior regarding odd/even number of characters

Viewed 256

I'm trying to create a regex in JS that will match a text at the end of the string composed by an "@" symbol followed by some characters (anything that's not a whitespace character or another "@").

Here are some examples of some expected behaviors I'm looking for:

String Match Group 1
test No match -
@test @test test
@test aux No match -
@test @aux @aux aux
@test @@aux No match -
@test @@@aux @aux aux
@test @@@@aux No match -
@test @@@@@aux @aux aux

The current regex I have - /(?<!@(?:@@)*)@([^\s@]*)$/ - doesn't work in the last use cases, when more than 3 "@" characters are used. It's supposed to find a match only if an odd number of "@" characters are used. I would appreciate if someone could help me out with this, thanks.

2 Answers

You may use match here:

input = ["@test @aux", "@test @@aux", "@test @@@aux"];
for (var i = 0; i < input.length; ++i) {
    result = input[i].match(/(?:^|\s)(?:@@)*@(\w+)$/);
    if (result) {
        output = "Match: " + result[0].trim() + ", " +
                 "Group 1: " + result[1];
        console.log(output);
    }
}

Here is an explanation of the regex pattern:

(?:^|\s)  what precedes is either whitespace
          or the start of the string (for single word inputs)
(?:@@)*   match any EVEN number of @
@         match a single @ (therefore odd total number of @)
(\w+)     match and capture word portion of @symbol
$         end of the input

You could change it to assert what is to the left is pairs of @@ not preceded by a @ itself.

Then match @ and capture 0+ chars other than @ or a whitespace char at the end of the string in group 1.

(?<=(?<!@)(?:@@)*)@([^\s@]*)$
  • (?<= Positive lookbehind to asser what is directly to the left is
    • (?<!@) Assert not @ directly to the left
    • (?:@@)* Match optional pairs of @@
  • ) Close lookbehind
  • @ Match a single @
  • ([^\s@]*) Capture 0+ times any char except a whitespace char or @ in group 1 (if you don't want to match an empty string, you can change * to +)
  • $ End of string

regex demo

const pattern = /(?<=(?<!@)(?:@@)*)@([^\s@]*)$/;
[
  "test",
  "@test",
  "@test aux",
  "@test @aux",
  "@test @@aux",
  "@test @@@aux",
  "@test @@@@aux",
  "@test @@@@@aux"
].forEach(s => {
  const m = s.match(pattern);
  if (m) {
    console.log(`${s} --> match: ${m[0]} | group: ${m[1]}`);
  } else {
    console.log(`${s} --> No match`);
  }
});

Related