Ahmed
Ahmed

Reputation: 245

How to match only specific characters in a given string with regex?

I want a specific value, the value be only numbers and:

Input string, and expected result.

01012345678          -----> allowed.
0101234a5678         -----> not allowed., letter exists.
01112345688          -----> allowed, 10th, 11st are the same
01112345677          -----> allowed, 10th, 11st are the same
01112345666          -----> allowed, 10th, 11st are the same
01112345689          -----> not allowed..10th, 11st different
01112345-678         -----> not allowed..hyphen exists.
01298765532          -----> allowed..8th, 9th are the same.
01298765732          -----> not allowed, 8th, 9th different.
01298765mm432        -----> not allowed, letter exists.
01500011122          -----> allowed..
020132156456136      -----> not allowed..more than 11 digit.
01530126453333       -----> not allowed..more than 11 digit.
00123456789          -----> not allowed.. second digit not 1.

This is my attempt at regex101,^01[0125][0-9]{8}$ https://regex101.com/r/cIcD0R/1 but it ignore specific cases also it works for specific cases.

Upvotes: 21

Views: 1971

Answers (3)

3limin4t0r
3limin4t0r

Reputation: 21150

If you're looking for a regex, purely to filter certain numbers without error messaging, this answer is probably not for you.

For validation purposes, a regex might not be the best way to go. If you would use one giant regex you would show one universal error message. This might leave a user confused since they partially complied with some of the criteria.

Instead split up the criteria so you can show a user relevant error messages.

function isValid(input, criteria) {
  const errors = [];
  
  for (const [isValid, error] of criteria) {
    if (!isValid(input)) errors.push(error);
  }
    
  return [!errors.length, errors];
}

const criteria = [
  [input => input.length === 11,
    "must have a length of 11"],
  [input => input.match(/^\d*$/),
    "must only contain digits (0-9)"],
  [input => input[0] === "0",
    "must have 0 as 1st digit"],
  [input => input[1] === "1",
    "must have 1 as 2nd digit"],
  [input => ["0","1","2","5"].includes(input[2]),
    "must have 0, 1, 2 or 5 as 3rd digit"],
  [input => input[2] !== "1" || input[9] === input[10],
    "the 10th and 11th digit must be the same if the 3rd digit is 1"],
  [input => input[2] !== "2" || input[7] === input[8],
    "the 8th and 9th digit must be the same if the 3rd digit is 2"],
];

document.forms["validate-number"].addEventListener("submit", function (event) {
  event.preventDefault();
  
  const form = event.target;
  const inputs = form.elements.inputs.value.split("\n");
  
  inputs.forEach(input => console.log(input, ...isValid(input, criteria)));
});
<form id="validate-number">
<textarea name="inputs" rows="14" cols="15">01012345678
0101234a5678
01112345688
01112345677
01112345666
01112345689
01112345-678
01298765532
01298765732
01298765mm432
01500011122
020132156456136
01530126453333
00123456789</textarea>
<br />
<button>validate</button>
</form>

Upvotes: 15

RavinderSingh13
RavinderSingh13

Reputation: 133750

With your shown samples please try following regex. Here is the Online Demo for used regex.

^01(?:(?:[05][0-9]{8})|(?:1[0-9]{6}([0-9])\1)|(?:2[0-9]{4}([0-9])\2[0-9]{2}))$

Here is the JS code for above regex, using foreach loop along with using test function in it.

const regex = /^01(?:(?:[05][0-9]{8})|(?:1[0-9]{6}([0-9])\1)|(?:2[0-9]{4}([0-9])\2[0-9]{2}))$/;
[
  "01012345678",
  "0101234a5678",
  "01112345688",
  "01112345677",
  "01112345666",
  "01112345689",
  "01112345-678",
  "01298765532",
  "01298765732",
  "01298765mm432",
  "01500011122",
  "020132156456136",
  "01530126453333",
  "00123456789"  
].forEach(element => 
  console.log(`${element} ----> ${regex.test(element)}`)
);

Explanation: Adding detailed explanation for used regex.

^01                              ##Matching 01 from starting of the value.
(?:                              ##Starting outer non-capturing group from here.
  (?:                            ##In a non-capturing group
    [05][0-9]{8}                 ##Matching 0 OR 5 followed by any other 8 digits.
  )
  |                              ##Putting OR condition here.
  (?:                            ##In a non-capturing group
    1[0-9]{6}([0-9])\1           ##Matching 1 followed by 6 digits followed by single digit(in a capturing group) and making sure next digit is matching previous.
  )
  |                              ##Puting OR condition here.
  (?:                            ##In a non-capturing group matching, 2 followed by 4 digits followed by 1 digit in capturing group followed by it followed by 2 any other digits.
    2[0-9]{4}([0-9])\2[0-9]{2}
  )
)$                               ##Closing outer non-capturing grouo here at the last of the value.

Upvotes: 12

The fourth bird
The fourth bird

Reputation: 163577

You could make use of an alternation with 2 capture groups and backreferences:

^01(?:[05]\d{8}|1\d{6}(\d)\1|2\d{4}(\d)\2\d\d)$

Explanation

  • ^ Start of string
  • 01 Match literally
  • (?: Non capture group for the alternatives
    • [05]\d{8} Match either 0 or 5 and 8 digits
    • | Or
    • 1\d{6}(\d)\1 Match 1, then 6 digits, capture a single digit in group 1 followed by a backreference to match the same digit
    • | Or
    • 2\d{4}(\d)\2\d\d Match 2, then 4 digits, capture a single digit in group 2 followed by a backrefence to match the same digit and match the last 2 digits
  • ) Close the non capture group
  • $ End of string

See a regex101 demo

const regex = /^01(?:[05]\d{8}|1\d{6}(\d)\1|2\d{4}(\d)\2\d\d)$/;
[
  "01012345678",
  "0101234a5678",
  "01112345688",
  "01112345677",
  "01112345666",
  "01112345689",
  "01112345-678",
  "01298765532",
  "01298765732",
  "01298765mm432",
  "01500011122",
  "020132156456136",
  "01530126453333",
  "00123456789"
].forEach(s => console.log(`${s} => ${regex.test(s)}`))

Upvotes: 27

Related Questions