Рейтинг@Mail.ru
 

Шаг 2


Как это устроено

Что обычно пугает тех, кто впервые сталкивается с регулярными выражениями. Это то, что совершенно непонятно, как вся эта бандура вообще устроена. Какой такой гномик сидит внутри и разбирает и сравнивает наши каракули.

На самом деле не так это сложно, как кажется снаружи. Это обычный посимвольный аналмз строки.
Для начала нужно уяснить себе, что строка - это ничто иное, как массив символов. А значит можно пройтись по нему и проанализировать каждый.
Давайте попробуем. Сначала представим строку, как массив.
1
2
3
4
5
6
7
8
9
10
<?php

    $string 
= array('w''o''r''d');

    echo 
$string[0], $string[1], $string[2], $string[3];

    
$string 'word';

    echo 
$string[0], $string[1], $string[2], $string[3];


Если запустить скрипт, то видно, что к строке можно обратиться так же, как к массиву. Только не пробуйте этот фокус с кирилицей, не выйдет. Дело в том, что это не совсем массив символов. Это массив байт. И у многобайтных кодировок на один символ приходится несколько элементов массива.

Ну а коль скоро это массив, можно разобрать его в цикле и сравнить каждый символ с шаблоном. Мы уже писали одну функцию, которая разбирала логин. Тут очень похоже. С той разницей, что условий проверки очень много, и задаются они динамически.

Вот сейчас напишем такую функцию, которая поможет разобраться, что там внутри.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

function regExp($pattern$subject)
{
    
$pattern trim($pattern'#');
    
    for(
$i 0$i strlen($subject); ++$i)
    {
        if(
$pattern == $subject[$i])
            return 
true;        
    }

    return 
false;
}

var_dump(regExp('#W#''Hello World')); // boolean true


Все просто, проходим циклом по строке-массиву и смотрим, есть ли символ, совподающий с паттерном.
Вот по такому алгоритму:

Теперь усложним задачу. Допустим нам нужно узнать, есть ли какая то из нескольких букв в строке. Это называется символьный класс. То есть условию должен удовлетворить хотя бы один из присутствующих в классе символов.

Мы во втором цикле разбираем паттерн и сравниваем буквы по очереди.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

function regExp($pattern$subject)
{
    
$pattern trim($pattern'#');

    if(
$pattern[0] == '[')
       
$pattern trim($pattern'[]'); // ABW
    
    
for($i 0$i strlen($subject); ++$i)
    {  
        for(
$j 1$j strlen($pattern); ++$j)
            if(
$pattern[$j] == $subject[$i $j])
                return 
true;   
    }

    return 
false;
}

var_dump(regExp('#[ABW]#''Hello World')); // boolean true


Теперь задачка посложнее. Нам нужно узнать, есть ли в слове буквы из диапазона от a до z. Тогда мы формируем массив от первого символа до последнего. То есть диапазон. Потом из этого массива склеиваем строку паттерна и проверяем как раньше.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

function regExp($pattern$subject)
{
    
$pattern trim($pattern'#');

    if(
$pattern[0] == '[')
    {
       
$pattern trim($pattern'[]');
       
$arr     range($pattern[0], $pattern[2]);
       
$pattern implode(''$arr); // abcdefghijklmnopqrstuvwxyz
    
}
    
    for(
$i 0$i strlen($subject); ++$i)
    {  
        for(
$j 1$j strlen($pattern); ++$j)
            if(
$pattern[$j] == $subject[$i $j])
                return 
true;   
    }

    return 
false;
}

var_dump(regExp('#[a-z]#''Hello World')); // boolean true


Теперь посмотрим, как можно найти несколько букв подряд. Без класса. Добавим еще одну проверку.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

function regExp($pattern$subject)
{
    
$pattern trim($pattern'#');

    if(
$pattern[0] == '['// Блок проверки символьных классов
    
{
       
$pattern trim($pattern'[]');
       
$arr     range($pattern[0], $pattern[2]);
       
$pattern implode(''$arr); // abcdefghijklmnopqrstuvwxyz

    
        
for($i 0$i strlen($subject); ++$i)
        {  
            for(
$j 1$j strlen($pattern); ++$j)
                if(
$pattern[$j] == $subject[$i $j])
                    return 
true;   
        }
    }
    else 
// Блок проверки последовательностей 
    
{
        
$return true;
        for(
$i 0$i strlen($subject); ++$i)
        {    
            for(
$j 0$j strlen($pattern); ++$j)
            {
                if(
$pattern[$j] == $subject[$i])
                {                 
                    for(
$s 0$s strlen($pattern); ++$s)
                        if(
$pattern[$s] !== $subject[$i $s])
                            
$return false;                
                }
                else
                    break;   
            }
        }    
        
        return 
$return;
    }

    return 
false;
}

var_dump(regExp('#Hello#''Hello World')); // boolean true
var_dump(regExp('#ell
H#''Hello World')); // boolean false


На это уже страшно смотреть. Тут нам нужны уже три цикла в блоке. Сначала проверяем символ в строке на соответствие символа паттерна. Если найден, проверяем следом идущие. И если подстрока полностью совпала, то ура. А если хоть одна буква не соответствет своему месту в шаблоне - значит кирдык.

Всё это конечно примитив и не соответствует истинному положению вещей в RE. Но принцип очень схож.

Мы не будем углубляться дальше - нет смысла. Мы тут как раз за тем, чтобы избежать таких сложных и тугих алгоритмов. А вот лучше рассмотрим самые простые и часто используемые регулярные выражения.

И так, самый простой паттерн.
1
2
3

"#.+#"
Обозначает он - хоть что то, но должно быть.

Или такой
1
2
3

"#.{1}#"
любой символ, только один.

Если сделать так
1
2
3

"#[0-9]{1}#"
то это будет обозначать - любая цифра, только одна.

Вот так тоже самое
1
2
3

"#[a-z]{1}#"
только с буквами латинского алфавита в нижнем регистре

Вот наша родная кирилица
1
2
3

"#[а-яё]{1}#u"
Буква ё всегда отдельно. Если работаете под UTF-8, то нужно ставить модификатор u. Это касается многобайтных символов, в которые не входят латиница, цифры и знаки препинания.

Теперь проигнорируем регистр. Пусть ловит и большие и маленькие:
1
2
3

"#[a-z]{1}#i"
Тут мы просто поставили модификатор.

Вот так будет "только буквы латинского алфавита в любом регистре, цифры, дефис и подчеркивание".
1
2
3

"#^[a-z0-9_-]+$#i"
Обратите внимание на привязку к началу ^ и концу $ строки.
Еще один важный момент - дефис нужно ставить последним. Иначе он будет интерпретирован, как символ диапазона. Если не получается, то нужно экранировать обратным слэшем.

Экранировать нужно любые метасимволы, если они нужны как обычные. Допустим точку. Иначе она будет воспринята, как "любой символ".
1
2
3

"#^[a-z0-9_\.-]+$#i"


Если нужно, чтобы обязательным условием был какой-нибудь одиночный символ посреди строки, туда его и надо поставить.
1
2
3

"#^[a-z0-9_\.-]+@[a-z0-9\.-]+\.[a-z]{2,6}$#i"
Вот это простейший паттерн для проверки на соответствие стандарту электронного адреса. Звучит так
"Любое количество символов латиницы, цифр, точки, подчеркивания или дефиса, потом собачка, потом тоже самое исключая подчеркивание, точка и от двух до шести букв латиницы. Регистр значения не имеет."

Этот паттерн в регулярных выражениях стал своего рода "Htllo, World!". Если Вам стало понятно, как он работает, дальше будет гораздо легче.