Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Regex match 2 out of 4 groups

I want a single Regex expression to match 2 groups of lowercase, uppercase, numbers or special characters. Length needs to also be grater than 7.

I currently have this expression

^(?=.*[^a-zA-Z])(?=.*[a-z])(?=.*[A-Z]).{8,}$

It, however, forces the string to have lowercase and uppercase and digit or special character.

I currently have this implemented using 4 different regex expressions that I interrogate with some C# code.

I plan to reuse the same expression in JavaScript.

This is sample console app that shows the difference between 2 approaches.

class Program
{
    private static readonly Regex[] Regexs = new[] {
        new Regex("[a-z]", RegexOptions.Compiled), //Lowercase Letter
        new Regex("[A-Z]", RegexOptions.Compiled), // Uppercase Letter
        new Regex(@"\d", RegexOptions.Compiled), // Numeric
        new Regex(@"[^a-zA-Z\d\s:]", RegexOptions.Compiled) // Non AlphaNumeric
    };

    static void Main(string[] args)
    {
        Regex expression = new Regex(@"^(?=.*[^a-zA-Z])(?=.*[a-z])(?=.*[A-Z]).{8,}$", RegexOptions.ECMAScript & RegexOptions.Compiled);

        string[] testCases = new[] { "P@ssword", "Password", "P2ssword", "xpo123", "xpo123!", "xpo123!123@@", "Myxpo123!123@@", "Something_Really_Complex123!#43@2*333" };

        Console.WriteLine("{0}\t{1}\t", "Single", "C# Hack");
        Console.WriteLine("");
        foreach (var testCase in testCases)
        {
            Console.WriteLine("{0}\t{2}\t : {1}", expression.IsMatch(testCase), testCase, 
                    (testCase.Length >= 8 && Regexs.Count(x => x.IsMatch(testCase)) >= 2));
        }

        Console.ReadKey();
    }
}

Result  Proper     Test String
------- -------    ------------

True    True     : P@ssword
False   True     : Password
True    True     : P2ssword
False   False    : xpo123
False   False    : xpo123!
False   True     : xpo123!123@@
True    True     : Myxpo123!123@@
True    True     : Something_Really_Complex123!#43@2*333
like image 610
Eugene S. Avatar asked Dec 03 '13 00:12

Eugene S.


1 Answers

For javascript you can use this pattern that looks for boundaries between different character classes:

^(?=.*(?:.\b.|(?i)(?:[a-z]\d|\d[a-z])|[a-z][A-Z]|[A-Z][a-z]))[^:\s]{8,}$

if a boundary is found, you are sure to have two different classes.

pattern details:

\b # is a zero width assertion, it's a boundary between a member of 
   # the \w class and an other character that is not from this class.

.\b. # represents the two characters with the word boundary.

boundary between a letter and a number:

(?i) # make the subpattern case insensitive
(?:
    [a-z]\d # a letter and a digit
  |         # OR
    \d[a-z] # a digit and a letter
)

boundary between an uppercase and a lowercase letter:

[a-z][A-Z] | [A-Z][a-z]

since all alternations contains at least two characters from two different character classes, you are sure to obtain the result you hope.

like image 167
Casimir et Hippolyte Avatar answered Nov 15 '22 01:11

Casimir et Hippolyte