Рейтинг@Mail.ru
 

Шаг 4


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

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

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

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

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


Мы получим вовсе не то, что хотели, а именно
1
2
3
4
Array
(
    [0] => Проснувшись рано, в пол восьмого ночи, Вовочка прошел в школу, а уже в девять Вовочка
)
Т.е. регулярное выражение нашло МАКСИМАЛЬНО БОЛЬШОЙ участок вхождения.
Это называется жадный поиск.
Вот, можно убедиться:

Где искать

Что искать


Если же нам надо лишь первое, тогда нам после .* необходимо поставить знак вопроса (?). Это указывает на то, что заданное совпадение остановиться на первом вхождении.
Итого код будет выглядеть следующим образом:
1
2
3
4
5
6
7
8
9
10
<?php

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

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


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


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

Что искать


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

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

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


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

Слово,

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



Негативный просмотр вперёд неуловимо напоминает обычный просмотр, лишь с той разницей, что вместо = ставить восклицательный знак "!", то есть инверсия.
Положительный просмотр звучит как "есть ли?", отрицательный "нет ли случайно?". Ну и соответственно результат будет обратным:
1
2
3
4
5
6
7
8
9
10
<?php

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

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


Теперь рассмотрим просмотр назад. Тоесть мы должны побывать на месте того самого хвоста, и узнать - есть ли у впереди идущей группы коньяк, и нужен ли такой демарш спозаранку.
1
2
3
4
5
6
7
8
9
10
<?php

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

    if (
preg_match('#
(?<=коньячком,) хвост#u'$text)) 
        echo 
'
Есть однако'
    else 
        echo 
"
Нет";
Конструкция положительного просмотра будет иметь вид: (?<= ... ) Т.е. как и ранее, но перед равно стоит стрелка назад. Тем самым как бы намекая.
Текст

Слово,

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

В принципе стиль очень похож на просмотр вперёд, но есть одно очень важное отличие: при просмотре вперёд НЕЛЬЗЯ указывать беcконечное количество символов. Т.е. запись вида :
1
2
3

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

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

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


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

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


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

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


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

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

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

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

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

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

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

Гришин Сергей aka DedMorozzz