PHP

Регулярные выражения

Шаг 1

Знакомство

Мы уже сталкивались с регулярными выражениями, когда (если) делали систему регистрации. Настало время копнуть поглубже.

Итак, что это такое.
Регулярные выражения (regex или regular expressions) представляют собой мощный и очень гибкий мини-язык, созданный для поиска подстрок в строке по заданным параметрам. Они являются неотъемлемой частью большинства основных языков программирования (PHP, Perl, JavaScript, Python, Ruby).
В этом разделе мы будем рассматривать только один из этих языков, это PHP.


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

Попробуем.

И так, что они могут, эти регэкспы.
• Проверять, соответствует ли вся строка целиком заданным параметрам.
• Находить в строке подстроки, которые удовлетворяют заданные параметры.
• Получить из строки подстроки, соответствующие заданным параметрам.
• Изменять в строке подстроки, соответствующие параметрам.
• И еще кучу всего интересного.

Нужно сразу определиться с терминологией, которая используется при работе.

1. Строка - весь набор символов, переданный для разбора.
2. Подстрока – это символ или определенная последовательность символов, или часть строки.
3. Заданные параметры - то есть то, что нам надо найти, где искать, на что заменить и т. д.
4. Дальше будут попадаться и другие, по ходу движения все объясним.

Ну а теперь вкусное - пример.

Возьмем самый простой вариант, который часто применяется для валидации логинов.


Незадача... Использованы всякие бяки:
....


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

    function checkData($str)
    {
 
        $standart = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 
                          'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
                          'а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л',
                          'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'щ',
                          'ъ', 'ы', 'ь', 'э', 'ю', 'я', '0', '1', '2', '3', '4', '5', '6',
                          '7', '8', '9'
                          );
                  
                  
    mb_internal_encoding('utf-8');      
    $str = mb_strtolower($str);  
    $cnt = mb_strlen($str);   
  
    for($i = 0; $i < $cnt ; $i++)  
       if(!in_array(mb_substr($str, $i, 1), $standart))
           return false; 
           
    return true;
    }
    
   $login = 'עִבְרִית';
    
    if(checkData($login))
        echo 'Пойдет';    
    else
        echo 'Лазутчики!';

Мало того, что она громоздкая и запутаная, так еще и заставляет PHP решать несвойственные ему задачи. Делать посимвольный анализ на нем можно только в крайних случаях.
А с помощью регулярных выражений эта задача решается коротенькой строчкой:
<?php

   $login = 'Дормидонт';
    
    if(!preg_match('#^[a-zа-яё0-9]+$#ui', $login))
        echo 'Лазутчики!';     
    else
        echo 'Пойдет';

Разберём регулярное выражение.
Взглядом незнакомого с регулярками человека эта мешанина всяких символов может показаться устрашающей, но если всё разобрать, то получается до боли просто.

Такой символ (^ - циркумфлекс или крышка) означает, что проверка будет от начала строки, ($ - доллар) означает конец строки. Если не ставить привязки к началу и концу строки, то регулярное выражение будет применимо к любой подстроке, и вот такой логин логинְרִית будет признан кошерным. ))

Так же границы регулярного выражения - должны как-то выделяться. В данном случае они выделены "шарпом", символом решетки, т.е. #. Во многих учебниках границы задаются слэшами /. Это то же верно, но если, вдруг, в регулярном выражении необходимо будет использовать слэши, то их придется экранировать. И по этому рекомендуется использовать максимально нейтральные границы, такие как # ну или &. Но мы будет использовать #, потому что она выглядит красивее.

Меду этими символами идет так называемый паттерн. То есть шаблон, на который будет ориентироваться программа, осуществляя поиск совподений.
В нашем случае это диапазоны символов от a до z (латинский алфавит) , от a до я плюс ё (кирилица) и от 0 до 9 (цифры).
То, что это помещено в квадратные скобки, и обозначает диапазон. Или класс. Обратите особое внимание на то, что буква ё не входит в диапазон а - я, так уж исторически сложилось. По этому нужно подставлять её отдельно.

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

Буква u после границы шаблона(модификатор) означает, что мы будем работать с символами в кодировке (UTF-8), а i значит, что регистр символов не имеет значения, то – есть в верхнем, или в нижнем, или вперемешку будут символы, не важно. Есть еще куча модификаторов, которые помогают управлять процессом.

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

Вот так вот примерно работают регулярки. Подробнее в следующих разделах.

Шаг 2

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

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

На самом деле не так это сложно, как кажется снаружи. Это обычный посимвольный аналмз строки.
Для начала нужно уяснить себе, что строка - это ничто иное, как массив символов. А значит можно пройтись по нему и проанализировать каждый.
Давайте попробуем. Сначала представим строку, как массив.
<?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];

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

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

Вот сейчас напишем такую функцию, которая поможет разобраться, что там внутри.
<?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

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



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

Мы во втором цикле разбираем паттерн и сравниваем буквы по очереди.
<?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. Тогда мы формируем массив от первого символа до последнего. То есть диапазон. Потом из этого массива склеиваем строку паттерна и проверяем как раньше.
<?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

Теперь посмотрим, как можно найти несколько букв подряд. Без класса. Добавим еще одну проверку.
<?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('#ellH#', 'Hello World')); // boolean false

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

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

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

И так, самый простой паттерн.
"#.+#"

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

Или такой
"#.{1}#"

любой символ, только один.

Если сделать так
"#[0-9]{1}#"

то это будет обозначать - любая цифра, только одна.

Вот так тоже самое
"#[a-z]{1}#"

только с буквами латинского алфавита в нижнем регистре

Вот наша родная кирилица
"#[а-яё]{1}#u"

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

Теперь проигнорируем регистр. Пусть ловит и большие и маленькие:
"#[a-z]{1}#i"

Тут мы просто поставили модификатор.

Вот так будет "только буквы латинского алфавита в любом регистре, цифры, дефис и подчеркивание".
"#^[a-z0-9_-]+$#i"

Обратите внимание на привязку к началу ^ и концу $ строки.
Еще один важный момент - дефис нужно ставить последним. Иначе он будет интерпретирован, как символ диапазона. Если не получается, то нужно экранировать обратным слэшем.

Экранировать нужно любые метасимволы, если они нужны как обычные. Допустим точку. Иначе она будет воспринята, как "любой символ".
"#^[a-z0-9_\.-]+$#i"

Если нужно, чтобы обязательным условием был какой-нибудь одиночный символ посреди строки, туда его и надо поставить.
"#^[a-z0-9_\.-]+@[a-z0-9\.-]+\.[a-z]{2,6}$#i"

Вот это простейший паттерн для проверки на соответствие стандарту электронного адреса. Звучит так
"Любое количество символов латиницы, цифр, точки, подчеркивания или дефиса, потом собачка, потом тоже самое исключая подчеркивание, точка и от двух до шести букв латиницы. Регистр значения не имеет."

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

Шаг 3

Модификаторы и спецсимволы

Поехали дальше. В прошлой главе вы уже увидели пару примеров регулярных выражений. Однако возможностей у RE гораздо больше. Вот сейчас попробуем собрать в кучу то, из чего можно строить паттерны, то есть основные элементы синтаксиса регулярных выражений.

Некоторые термины которые мы будем использовать:
Шаблон – Простым языком это заданные параметры, если вы забыли, что это посмотрите Главу 1.
Модификатор – Это символ, который указывается сразу послу шаблона, пример ("#шаблон#модификатор"). С двумя модификаторами вы уже познакомились в Главе 2 это (i, u), их мы ещё раз повторим в этой главе.
Метасимвол – Символ, который имеет специально назначение и используется для поиска какой – то подстроки.
Литерал – Символ который не имеет спец. назначения или экранирован.

Сначала мы с вами познакомимся с модификаторами шаблонов.

( i ) – Этот модификатор значит, что регистр символов который будет обрабатываться регуляркой, не будет иметь значения, то есть будет ли он верхним или нижним или вообще смешанным, для нас это никакой роли играть не будет.

( m ) – Этот модификатор указывается для того, чтобы строка проверялась от начала до самого конца.

( s ) – Модификатор "s", говорит о том, что "." начинает в себя включать и перевод строки, наряду с остальными символами. Т.е. АБСОЛЮТНО все видимые и не видимые символы.

( x ) – Этот модификатор активирует режим который будет игнорировать пробелы, или табуляцию в шаблоне если перед ними не используется ( ).

( e ) – Этот модификатор игнорируют все функции, кроме preg_replace. А последняя, после того как выполнит подстановку то, что подставлено будет считаться PHP – кодом.

( A ) – Если данный модификатор установлен, значит функция вернет TRUE лишь в том случае, если подстрока будет найдена в начале строки, иначе будет FALSE.

( D ) – Если данный модификатор используется, значит, метасимвол ($ – Доллар) в шаблоне будет соответствовать окончанию поиска.

( U ) – Модификатор для того, чтобы запутать всех. ???????

( X ) – Как вы должны уже были понять, что все спец. символы должны экранироваться в шаблоне для того, чтобы воспринимались как обычные. Так вот этот модификатор вернет ошибку, если перед символом, который не имеет спец. назначения будет экранирован.

( u ) – Этот модификатор мы встречали в Главе 1 (напомню ещё раз), что он предназначен для работы с символами в кодировке UTF-8.

Теперь нам ещё предстоит рассмотреть метасимволы. Есть метасимволы и литералы.

Метасимволы:
( \ ) – Метасимвол, который экранирует спец. символы.
( ^ ) – (Крышка или циркумфлекс), означает начало строки, или линии, если многострочный режим.
( $ ) – Означает конец строки, или линии, если многострочный режим.
( . ) – Означает любой символ кроме перевода строки.
( [ ) – Начало символьного класса.
( ] ) – Конец символьного класса.
( | ) – Означает “ИЛИ” в конструкции выбора.
( ( ) – Начало подмаски или конструкции выбора.
( ) ) – Конец подмаски или конструкции выбора.
( ? ) – Если "?" стоит перед круглыми скобками, то знак вопроса является идентификатором ссылки. Если "?" стоит перед "*" или "+", тогда это показатель "ленивого" поиска. Что такое "ссылка", "ленивый" и "жадный поиск" и для чего они нужны будет рассказано чуть позднее. ( * ) – Ставиться перед символом, который должен, встретится ноль или более раз.
( + ) – Ставиться перед символом, который должен, встретится один или более раз.
( { ) – Начало квантификатора, в котором можно указать сколько должно быть последовательных символов.
( } ) – Конец предыдущего.

Часть шаблона может быть заключена в ( [ ] – квадратные скобки) – это называется Символьный класс. В нём те символы, которые мы увидели, понимаются немного иначе. Вот как:

В символьном классе, мы указываем, какие символы пропускаем, а какие нет. Например [A-ZА-ЯЁ] – здесь мы указали, что пропускаем только символы от A до Z и от А – Я плюс Ё.

( \ ) – Экранирует символы.
( ^ ) – Инвертирует весь символьный класс. Если мы напишем [^A-ZА-Я], это будет означать, все символы кроме от A до Z и от А до Я.
( - ) – Дефис означает интервал, то есть A – Z от А до Z.

Ещё можно использовать экранированные символы для указания типа символа.

( \d ) – Любая цифра. Вместо того, чтобы писать [0-9], это значит любая цифра от 0 до 9. Но можно написать попроще, то есть так: [\d]
( \D ) – Все символы кроме цифры.
( \s ) – Это значит любой пробельный символ: пробел, табуляция, символ новой строки.
( \S ) – Все символы которые не относятся к ( \s ).
( \w ) – Любой символ который образует слово. Это символы [-A-ZА-Я_].
( \W ) – Все символы которые не относятся к ( \w ).

И последнее, это специальные последовательности, которые указывают, где должна располагаться искомая подстрока.

( \b ) – Это значит граница слова, подстрока должна быть отделена пробелами.
( \B ) – Обратно от ( \b ).

Так же наряду с простым обозначением существуют "иные" символьные классы:
[[:alpha:]], что соответствует промежутку [A-z] или [A-Za-z], что одно и то же.
[[:digit:]] эквивалент - [0-9]
[[:alnum:]] Эквивалент [A-z0-9]
[[:space:]] Эквивалентом являются все невидимые символы.

Особого смысла в них нету, поскольку такой тип записи существенно сложнее. Но если встретите такое обозначение, будете знать, что под ним подразумеваеться.
Так же краткая рекомендация по написанию регулярок - не следует использовать краткие обозначения. При сложных и длинных выражениях весьма просто запутаться что имелось ввиду. В силу того, что \s обозначает одно, а \S СОВЕРШЕННО противоположное. И на регистр можно не обратить внимание или просто забыть в каком регистре находилась буква. И по сему желательно использовать символьный класс в явном виде, т.е. [A-zА-яЁё] и так далее. Вот мы и закончили с теорией, и в последующих главах у нас будет практика. Если вы что-то не поняли, не огорчайтесь, читая только теорию невозможно все понять, для этого нужна только практика, тогда будет всё понятнее.

Шаг 4

Сложные конструкции

До этого, были рассмотрены лишь простые условия, т.е. все буквы от А до Я. Все цифры от 0 до 9 и так далее. В этой главе мы рассмотрим действительно мощные выражения, без знания которых, не одна нормальная девочка с вами не согласиться гулять.
Ну что ж. Приступим.

Сейчас рассмотрим что такое "ленивый" и "жадный" поиски.
Уже вам известная конструкция .* выбирает все значения. Даже если написать вот так: "Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка был вызван к доске". И в этой фразе я хочу найти лишь то, что перед первым упоминанием "Вовочка", ибо остальное нам не интересно.
Написав следующим образом:
<?php

    $text = "Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка был вызван к доске";
    $text = preg_match('#.*Вовочка#',$text,$arr);
    
?><pre><?php

    print_r($arr);
    
?></pre>

Мы получим вовсе не то, что хотели, а именно
Array
(
    [0] => Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка
)

Т.е. регулярное выражение нашло МАКСИМАЛЬНО БОЛЬШОЙ участок вхождения.
Это называется жадный поиск.
Вот, можно убедиться:

Где искать

Что искать


Если же нам надо лишь первое, тогда нам после .* необходимо поставить знак вопроса (?). Это указывает на то, что заданное совпадение остановиться на первом вхождении.
Итого код будет выглядеть следующим образом:
<?php

    $text = "Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка был вызван к доске";
    $text = preg_match('#.*?Вовочка#',$text,$arr);
    
?><pre><?php

    print_r($arr);
    
?></pre>

В результате работы мы увидем то, что и было надобно. Т.е. это:
Array
(
    [0] => Проснувшись рано, в пол восьмого ночи, Вовочка
)

Сравните:
Где искать

Что искать


Сейчас рассмотрим возможность просмотра вперёд и назад. Он(просмотр) бывает положительный и отрицательный(негативный).
Это бывает очень полезно, когда нужно узнать, есть ли за проверяемым шаблоном что-то еще.
Рассмотрим синтаксис положительного просмотра вперёд:
<?php

    $text = 'Куда идем мы с Пятачком, большой большой секрет. '
          . 'Несем бутылку с коньячком, хвоста за нами нет.';

    if (preg_match('#коньячком,(?= хвост)#u', $text)) 
        echo 'Есть однако'; 
    else 
        echo "Нет";

Тут наших героев ждет разочарование, потому что за проверяемой подстрокой "коньячком," (запятая участвует) идет коварный "хвост". Стоит его срубить и ситуация тут же вернется под контроль:
Текст

Слово,

после которого ищем это

Негативный просмотр вперёд неуловимо напоминает обычный просмотр, лишь с той разницей, что вместо = ставить восклицательный знак "!", то есть инверсия.
Положительный просмотр звучит как "есть ли?", отрицательный "нет ли случайно?". Ну и соответственно результат будет обратным:
<?php

    $text = 'Куда идем мы с Пятачком, большой большой секрет. '
          . 'Несем бутылку с коньячком, хвоста за нами нет.';

    if (preg_match('#коньячком,(?! хвост)#u', $text)) 
        echo 'Нет'; 
    else 
        echo "Есть однако";

Теперь рассмотрим просмотр назад. Тоесть мы должны побывать на месте того самого хвоста, и узнать - есть ли у впереди идущей группы коньяк, и нужен ли такой демарш спозаранку.
<?php

    $text = 'Куда идем мы с Пятачком, большой большой секрет. '
          . 'Несем бутылку с коньячком, хвоста за нами нет.';

    if (preg_match('#(?<=коньячком,) хвост#u', $text)) 
        echo 'Есть однако'; 
    else 
        echo "Нет";

Конструкция положительного просмотра будет иметь вид: (?<= ... ) Т.е. как и ранее, но перед равно стоит стрелка назад. Тем самым как бы намекая.
Текст

Слово,

пeред которым должно быть это

В принципе стиль очень похож на просмотр вперёд, но есть одно очень важное отличие: при просмотре вперёд НЕЛЬЗЯ указывать беcконечное количество символов. Т.е. запись вида :
#text (?<=.*)#

недопустима.
Негативный просмотр назад, как и ранее - производится заменой "равно" на восклицательный знак. Т.е. вид будет таковым: (?<! ... ).

Теперь рассмотрим возможность ссылочной проверки.
Вкратце разберёмся, что это такое.
Допустим нам нужно вынуть все, что находится между тегами <b> и </b>. Это мы уже умеем:
<?php

    $text = 'Тут <b>жирный</b> текст ';
    preg_match('#<b>(.*?)</b>#u', $text, $matches);
    echo $matches[1]; 

Но вот если нам понадобится вытащить все, что находится между тегами, текст которых мы не знаем (к примеру там есть стили), так уже не выйдет. Не перечислять же всю таблицу CSS.
Можно сделать так:
<?php

    $text = 'Тут <span style="color:red">красный</span> текст ';
    preg_match('#<[^>]+?>(.*?)</[^>]+?>#u', $text, $matches);
    echo $matches[1]; 

И тогда вроде как регулярка и сама разберется. Но есть подвох. Если текст будет таким:
<?php

    $text = 'Тут <span style="color:red"><b>жирный</b> красный</span> текст ';

то последствия окажутся непредсказуемыми. Будет найден текст между спаном и </b>
Как заставить регулярку распознать именно тот закрывающий тег, который нам нужен?
Для этого и применяются так называемые "ссылки". Дело в том, что все, что находится в круглых скобках (если это принудительно не отключено), запоминается. И мы можем получить это обратно, указав ссылку на нужный участок. Ссылка обозначается двумя обратными слэшами и номером ячейки по порядку \\1. Есть еще один вариант - использовать вместо двух бэкслэшей знак доллара. Но это не работает внутри паттерна.
Подсчёт номера ячейки идёт следующим образом: скобки которые в себя включают другие скобки - имеют номер более ранний, чем внутренние. Остальные скобки считаются слева на право.
<?php

    $text = 'Тут <span style="color:red"><b>жирный</b> красный</span> текст ';
    preg_match('#<(span)[^>]+?>(.*?)</\\1>#u', $text, $matches);
    echo $matches[2];

Тут мы запомнили, какой нужно найти закрывающий тег (span), а потом подставили это в шаблон на место закрывающего. И теперь все будет найдено корректно.

Ссылки хороши еще тем, что их можно подставить во второй аргумент функции preg_replace() к примеру. И тогда мы получим в строку замены подстроку из найденной.
<?php

    $text = 'Сделаем этот текст <b>зачеркнутым</b>';
    echo preg_replace('#<b>(.*?)</b>#u', '<s>$1</s>', $text);

Вот тут можно пользоваться и долларом.

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

Шаг 4

Сложные конструкции

До этого, были рассмотрены лишь простые условия, т.е. все буквы от А до Я. Все цифры от 0 до 9 и так далее. В этой главе мы рассмотрим действительно мощные выражения, без знания которых, не одна нормальная девочка с вами не согласиться гулять.
Ну что ж. Приступим.

Сейчас рассмотрим что такое "ленивый" и "жадный" поиски.
Уже вам известная конструкция .* выбирает все значения. Даже если написать вот так: "Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка был вызван к доске". И в этой фразе я хочу найти лишь то, что перед первым упоминанием "Вовочка", ибо остальное нам не интересно.
Написав следующим образом:
<?php

    $text = "Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка был вызван к доске";
    $text = preg_match('#.*Вовочка#',$text,$arr);
    
?><pre><?php

    print_r($arr);
    
?></pre>

Мы получим вовсе не то, что хотели, а именно
Array
(
    [0] => Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка
)

Т.е. регулярное выражение нашло МАКСИМАЛЬНО БОЛЬШОЙ участок вхождения.
Это называется жадный поиск.
Вот, можно убедиться:

Где искать

Что искать


Если же нам надо лишь первое, тогда нам после .* необходимо поставить знак вопроса (?). Это указывает на то, что заданное совпадение остановиться на первом вхождении.
Итого код будет выглядеть следующим образом:
<?php

    $text = "Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка был вызван к доске";
    $text = preg_match('#.*?Вовочка#',$text,$arr);
    
?><pre><?php

    print_r($arr);
    
?></pre>

В результате работы мы увидем то, что и было надобно. Т.е. это:
Array
(
    [0] => Проснувшись рано, в пол восьмого ночи, Вовочка
)

Сравните:
Где искать

Что искать


Сейчас рассмотрим возможность просмотра вперёд и назад. Он(просмотр) бывает положительный и отрицательный(негативный).
Это бывает очень полезно, когда нужно узнать, есть ли за проверяемым шаблоном что-то еще.
Рассмотрим синтаксис положительного просмотра вперёд:
<?php

    $text = 'Куда идем мы с Пятачком, большой большой секрет. '
          . 'Несем бутылку с коньячком, хвоста за нами нет.';

    if (preg_match('#коньячком,(?= хвост)#u', $text)) 
        echo 'Есть однако'; 
    else 
        echo "Нет";

Тут наших героев ждет разочарование, потому что за проверяемой подстрокой "коньячком," (запятая участвует) идет коварный "хвост". Стоит его срубить и ситуация тут же вернется под контроль:
Текст

Слово,

после которого ищем это

Негативный просмотр вперёд неуловимо напоминает обычный просмотр, лишь с той разницей, что вместо = ставить восклицательный знак "!", то есть инверсия.
Положительный просмотр звучит как "есть ли?", отрицательный "нет ли случайно?". Ну и соответственно результат будет обратным:
<?php

    $text = 'Куда идем мы с Пятачком, большой большой секрет. '
          . 'Несем бутылку с коньячком, хвоста за нами нет.';

    if (preg_match('#коньячком,(?! хвост)#u', $text)) 
        echo 'Нет'; 
    else 
        echo "Есть однако";

Теперь рассмотрим просмотр назад. Тоесть мы должны побывать на месте того самого хвоста, и узнать - есть ли у впереди идущей группы коньяк, и нужен ли такой демарш спозаранку.
<?php

    $text = 'Куда идем мы с Пятачком, большой большой секрет. '
          . 'Несем бутылку с коньячком, хвоста за нами нет.';

    if (preg_match('#(?<=коньячком,) хвост#u', $text)) 
        echo 'Есть однако'; 
    else 
        echo "Нет";

Конструкция положительного просмотра будет иметь вид: (?<= ... ) Т.е. как и ранее, но перед равно стоит стрелка назад. Тем самым как бы намекая.
Текст

Слово,

пeред которым должно быть это

В принципе стиль очень похож на просмотр вперёд, но есть одно очень важное отличие: при просмотре вперёд НЕЛЬЗЯ указывать беcконечное количество символов. Т.е. запись вида :
#text (?<=.*)#

недопустима.
Негативный просмотр назад, как и ранее - производится заменой "равно" на восклицательный знак. Т.е. вид будет таковым: (?<! ... ).

Теперь рассмотрим возможность ссылочной проверки.
Вкратце разберёмся, что это такое.
Допустим нам нужно вынуть все, что находится между тегами <b> и </b>. Это мы уже умеем:
<?php

    $text = 'Тут <b>жирный</b> текст ';
    preg_match('#<b>(.*?)</b>#u', $text, $matches);
    echo $matches[1]; 

Но вот если нам понадобится вытащить все, что находится между тегами, текст которых мы не знаем (к примеру там есть стили), так уже не выйдет. Не перечислять же всю таблицу CSS.
Можно сделать так:
<?php

    $text = 'Тут <span style="color:red">красный</span> текст ';
    preg_match('#<[^>]+?>(.*?)</[^>]+?>#u', $text, $matches);
    echo $matches[1]; 

И тогда вроде как регулярка и сама разберется. Но есть подвох. Если текст будет таким:
<?php

    $text = 'Тут <span style="color:red"><b>жирный</b> красный</span> текст ';

то последствия окажутся непредсказуемыми. Будет найден текст между спаном и </b>
Как заставить регулярку распознать именно тот закрывающий тег, который нам нужен?
Для этого и применяются так называемые "ссылки". Дело в том, что все, что находится в круглых скобках (если это принудительно не отключено), запоминается. И мы можем получить это обратно, указав ссылку на нужный участок. Ссылка обозначается двумя обратными слэшами и номером ячейки по порядку \\1. Есть еще один вариант - использовать вместо двух бэкслэшей знак доллара. Но это не работает внутри паттерна.
Подсчёт номера ячейки идёт следующим образом: скобки которые в себя включают другие скобки - имеют номер более ранний, чем внутренние. Остальные скобки считаются слева на право.
<?php

    $text = 'Тут <span style="color:red"><b>жирный</b> красный</span> текст ';
    preg_match('#<(span)[^>]+?>(.*?)</\\1>#u', $text, $matches);
    echo $matches[2];

Тут мы запомнили, какой нужно найти закрывающий тег (span), а потом подставили это в шаблон на место закрывающего. И теперь все будет найдено корректно.

Ссылки хороши еще тем, что их можно подставить во второй аргумент функции preg_replace() к примеру. И тогда мы получим в строку замены подстроку из найденной.
<?php

    $text = 'Сделаем этот текст <b>зачеркнутым</b>';
    echo preg_replace('#<b>(.*?)</b>#u', '<s>$1</s>', $text);

Вот тут можно пользоваться и долларом.

Комбинируя все эти правила, можно добиться таких сногсшибательных результатов, что это будет граничить на уровне фантастики.
Теги:
Регулярные выражения
Добавлено: 16 Августа 2013 12:24:23 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...