Функции регулярных выражений
Автор: Александр Грималовский
В PHP существует несколько функций для работы с
регулярными выражениями. Все они используют один и тот же парсер
регулярных выражений для своей работы, но при этом преследуют
различные цели. Ниже мы рассмотрим все эти функции. Я буду приводить
описание синтаксиса каждой функции в том виде, в котором она описана
в PHP Manual, чтобы вам легче было разобраться.
Синтаксис:
int preg_match (string pattern, string subject [, array matches])
Эта функция предназначена для проверки того, совпадает ли
заданная строка (subject
) с заданным регулярным
выражением (pattern
). В качестве результата функция
возвращает 1
, если совпадения были найдены и 0
, если нет. Если при вызове функции был задан
необязательный параметр matches
, то после работы
функции ему будет присвоен массив, содержащий результаты поиска по
заданному регулярному выражению. Заметьте, что вне зависимости от
того, сколько именно совпадений было найдено при поиске - вам будет
возвращено только первое совпадение. Рассмотрим пример того, как это
работает:
<?php
$str = "123 234 345 456 567"; // Строка для поиска
$result = preg_match('/\d{3}/',$str,$found); // Производим поиск
echo "Matches: $result<br>"; // Выводим количество найденных совпадений
print_r($found); // Выводим результат поиска
?>
Результатом работы этой программы будет:
Matches: 1
Array
(
[0] => 123
)
Если вы внимательно прочитали предыдущий выпуск и понимаете, как
работают регулярные выражения, то вы должны заметить, что реально
функция preg_match()
обнаружила в заданной строке 5
совпадений с заданным выражением, но вернула только первое из них.
Казалось бы, что в этом случае было бы логичнее возвращать
результаты поиска в виде строки, а не в виде массива, но это не так.
Вспомните, что регулярное выражение может содержать в себе
внутренние регулярные выражения, которые также возращают результат.
А для того, чтобы вернуть результаты поиска по всем регулярным
выражениям нам как раз и требуется массив. Для того, чтобы
проиллюстрировать сказанное выше давайте немного изменим регулярное
выражение и посмотрим на результат:
<?php
$str = "123 234 345 456 567";
// Теперь мы не просто ищем трехзначное число,
// но и получаем его среднюю цифру
$result = preg_match('/\d(\d)\d/',$str,$found);
echo "Matches: $result<br>";
print_r($found);
?>
Результат будет следующим:
Matches: 1
Array
(
[0] => 123
[1] => 2
)
Как видите - здесь присутствуют результаты поиска по всем
имеющимся регулярным выражениям.
Синтаксис:
int preg_match_all (string pattern, string subject, array matches [, int order])
Эта функция очень похожа на предыдущую и предназначена для тех же
самых целей. Единственное ее отличие от preg_match()
состоит в том, что она осуществляет "глобальный" поиск в заданном
тексте по заданному регулярному выражению и, соответственно, находит
и возвращает все имеющиеся совпадения. Посмотрим, как отличается
работа этой функции на том же самом примере:
<?php
$str = "123 234 345 456 567";
$result = preg_match_all('/\d{3}/',$str,$found);
echo "Matches: $result<br>";
print_r($found);
?>
Результат работы:
Matches: 5
Array
(
[0] => Array
(
[0] => 123
[1] => 234
[2] => 345
[3] => 456
[4] => 567
)
)
Как видите - здесь мы получили все найденные совпадения и их
количество в качестве результата.
Необходимо обратить ваше внимание на дополнительный параметр,
появившийся в этой функции по сравнению с preg_match()
: order
. Значение этого параметра определяет структуру
выходного массива с найденными совпадениями. Его значение может быть
одним из перечисленных ниже:
PREG_PATTERN_ORDER
- результаты поиска будут
сгруппированы по номеру регулярного выражения, которое возвратило
этот результат (это значение используется по умолчанию).
PREG_SET_ORDER
- результаты поиска будут
сгруппированы по месту их нахождения в тексте
Для того, чтобы лучше понять разницу между этими значениями,
посмотрим на результат работы одного и того же скрипта при
использовании каждого из них:
Сначала посмотрим на то, как выглядит результат при использовании PREG_PATTERN_ORDER
:
<?php
$str = "123 234 345 456 567";
$order = PREG_PATTERN_ORDER
;
$result = preg_match_all('/\d(\d)\d/',$str,$found,$order);
print_r($found);
?>
Результат:
Array
(
[0] => Array
(
[0] => 123
[1] => 234
[2] => 345
[3] => 456
[4] => 567
)
[1] => Array
(
[0] => 2
[1] => 3
[2] => 4
[3] => 5
[4] => 6
)
)
Как видите - массив результатов содержит внешние индексы,
соответствующие номерам регулярных выражений, от которых получен
результат (индекс 0 имеет основное регулярное выражение). По этим
индексам в массиве расположены массивы, содержащие непосредственно
найденную информацию, причем индекс в этом внутреннем массиве
соответствует "порядковому номеру" данного фрагмента в исходном
тексте.
Теперь попробуем то же самое, но с PREG_SET_ORDER
:
<?php
$str = "123 234 345 456 567";
$order = PREG_SET_ORDER
;
$result = preg_match_all('/\d(\d)\d/',$str,$found,$order);
print_r($found);
?>
Результат:
Array
(
[0] => Array
(
[0] => 123
[1] => 2
)
[1] => Array
(
[0] => 234
[1] => 3
)
[2] => Array
(
[0] => 345
[1] => 4
)
[3] => Array
(
[0] => 456
[1] => 5
)
[4] => Array
(
[0] => 567
[1] => 6
)
)
Как видите - здесь основной массив содержит результаты поиска,
сгруппированные по порядку их нахождения в тексте, причем каждый
результат представляет собой массив с результатами поиска по этому
найденному фрагменту для всех имеющихся регулярных выражений.
Синтаксис:
mixed preg_replace (mixed pattern, mixed replacement, mixed subject [, int limit])
Эта функция позволит вам произвести замену текста по регулярному
выражению. Как и в предыдущих функциях, здесь производится поиск по
регулярному выражению pattern
в тексте subject
, и каждый найденный фрагмент текста заменяется
на текст, заданный в replacement
. Задание
необязятельного параметра limit
позволит ограничить
количество заменяемых фрагментов в тексте.
Например, нам необходимо "сжать" текст, убрав из него все лишние
пробелы и символы перевода строки:
<?php
$text = "there is\t\n\t\t some text \n \t just \n\n\n for test";
echo "<b>Перед заменой:</b>\n$text\n\n";
$text = preg_replace("/(\n \s{2,})/"," ",$text);
echo "<b>После замены:</b>\n$text";
?>
Результатом работы данной программы будет следующий текст:
Перед заменой:
there is
some text
just
for test
После замены:
there is some text just for test
Как видите - всего одна строчка позволила нам решить достаточно
нетривиальную в обычной практике задачу. Объяснять само регулярное
выражение я не буду, если вы внимательно прочитали предыдущий выпуск
- понять его вам будет несложно.
Однако основная прелесть этой функции, которая и придает ей всю
ее мощь - это тот факт, что вы можете ссылаться на результаты
поиска при генерации замещающего текста. В качесте примера покажу,
как можно очень быстро и элегантно решить задачу, которая возникает
достаточно часто - конвертация дат из одного формата в другой. Как
вы знаете, на Западе обычно используется формат mm/dd/yyyy
, тогда как у нас обычно - dd.mm.yyyy
. Следующий пример осуществляет конвертацию
дат между этими форматами в заданном тексте:
<?php
$text = 'Today is 11/16/2001';
$text = preg_replace("/(\d{2})\/(\d{2})\/(\d{4})/","\\2.\\1.\\3",$text);
echo $text;
?>
Результат работы этой программы:
Today is 16.11.2001
Обратите внимание на текст, используемый для замены. В нем
использованы т.н. backreferences, т.е. ссылки на
найденный ранее текст. Всего таких ссылок может быть не более 100 с
номерами от 0 до 99 (соответственно в тексте они выглядят как \0
, \1
, \2
... \99
). Backreference с номером 0 будет заменена на весь
найденный текст, \1
- на текст, найденный первым
внутренним регулярным выражением, \2
- вторым и т.д.
Номерв внутренним регулярным выражениям присваиваются по мере их
находжения в тексте, т.е. слева-направо. В нашем случае \1
- это месяц, \2
- день, \3
- год.
Помимо стандартного синтаксиса регулярных выражений, в PHP, совместно с функцией preg_replace()
используется еще один дополнительный модификатор - 'e
'.
Его использование заставляет PHP рассматривать текст замены
не как текст, а как PHP код, что дает возможность еще больше
расширить сферу применения этой функции в вашем коде. Следующий
пример демонстрирует использование этого модификатора - он
производит замену всех целых десятичных чисел в тексте на их
шестнадцатиричные эквиваленты:
<?php
$text = "123 234 345 456 567";
$text = preg_replace("/\d+/e","'0x'.dechex('\\0')",$text);
print_r($text);
?>
Результатом работы этой программы будет:
0x7b 0xea 0x159 0x1c8 0x237
И еще одно. Функция preg_replace()
также умеет
работать с массивами регулярных выражений. Т.е. это позволит вам
осуществить поиск и замену сразу по множеству условий! В качестве
примера приведу фрагмент кода, описанный в PHP Manual и осуществляющий
конвертацию HTML документа в текст при помощи всего лишь одного
вызова preg_replace()
!
// $document should contain an HTML document.
// This will remove HTML tags, javascript sections
// and white space. It will also convert some
// common HTML entities to their text equivalent.
$search = array ("'<script[^>]*?>.*?</script>'si", // Strip out javascript
"'<[\/\!]*?[^<>]*?>'si", // Strip out html tags
"'([\r\n])[\s]+'", // Strip out white space
"'&(quot #34);'i", // Replace html entities
"'&(amp #38);'i",
"'&(lt #60);'i",
"'&(gt #62);'i",
"'&(nbsp #160);'i",
"'&(iexcl #161);'i",
"'&(cent #162);'i",
"'&(pound #163);'i",
"'&(copy #169);'i",
"'&#(\d+);'e"); // evaluate as php
$replace = array ("",
"",
"\\1",
"\"",
"&",
"<",
">",
" ",
chr(161),
chr(162),
chr(163),
chr(169),
"chr(\\1)");
$text = preg_replace ($search, $replace, $document);
Сами по себе регулярные выражения очень просты, интересно лишь их
совместное использование для решения общей задачи.
Синтаксис:
mixed preg_replace_callback (mixed pattern, mixed callback, mixed subject [, int limit])
Эта функция является расширенной версией функции preg_replace()
(хотя, казалось бы, чего еще можно
пожелать?). Единственным отличием ее от preg_replace()
является то, что в качестве текста для замены в ней задается не сам
текст, а имя функции, которая будет производить обработку найденного
текста и возвращать замещающий текст. Т.е. с использованием этой
функции мощь инструментария PHP по обработке текста
становится поистине безграничной! В качестве примера хочу привести
фрагмент кода, который выполняет работу, аналогичную той, что
производится механизмом сессий в PHP: добавление
дополнительного аргумента (идентификатора сессии) к каждой ссылке
внутри HTML документа.
<?php
// Список тегов и аттрибутов, к котроым необходимо
// добавить дополнительный параметр.
// Формат строки:
// <имя тега>[ <имя аттрибута>]+
// Т.е. сначала идет имя тега, а затем, через пробел,
// имена одного или нескольких аттрибутов.
$tagsList = array(
'a href',
'area href',
'frame src',
'input src',
'img src',
'form action'
);
// Идентификатор сессии
$sid = 12345;
// HTML документ для обработки. Здесь, в качестве примера
// мы берем его из внешнего файла, но вообще-то метод
// получения исходного документа может быть различным.
$document = join('',file('document.html'));
// Начинаем обработку всех тегов, указанных в массиве $tagsList
foreach($tagsList as $tag)
{
// Разделяем список аттрибутов на составляющие
$attrs = explode(' ',$tag);
// Получаем имя тега (в массиве $attrs остается лишь список аттрибутов)
$tag = array_shift($attrs);
// Выполняем "патч" всех имеющихся в документе ссылок, содержащихся
// в каждом из аттрибутов текущего тега
foreach($attrs as $attr)
$document = preg_replace_callback("/<".$tag.".+?".$attr."=[\'\"](.+?)[\'\"]/si",
'callback',$document);
};
// Выводим документ и выходим
echo $document;
exit();
// Эта функция будет вызываться для каждой найденной
// ссылки в тексте HTML документа.
// На входе она получает результат поиска (массив,
// аналогичный возвращаемому функцией preg_match()).
// На выходе из функции должна быть строка с текстом замены.
function callback($data)
{
// Регулярное выражение, использованное для поиска находит полные
// HTML теги, содержащие аттрибуты, в которых могут находиться
// URL адреса. Поскольку текст, возвращаемый данной функцией будет
// использован для замещения всего найденного фрагмента текста -
// нам необходимо взять полный текст, чтобы не потерять его при
// дальнейшей обработке. Он будет возвращен без изменений, если
// окажется, что аттрибут не содержит URL адреса.
$href = $data[0];
// Используем функцию PHP для разбора URL адреса на составляющие.
// В качестве "исходного материала" передаем содержимое интересующего
// нас аттрибута, найденного внутренним регулярным выражением.
// Подробнее о том, что возвращает эта функция см. PHP Manual.
$parts = parse_url($data[1]);
// Мы должны добвлять идентификатор сессии только к ссылкам, которые
// являются "локальными" для данного сайта. Т.е. мы не должны обрабатывать:
// - полные URL адреса (<a href="http://www.php.net/">)
// - указатели на "якоря" внутри страницы (<a href="#part2">)
if ((!isset($parts['scheme'])) && // Если URL содержит идентификатор
(!isset($parts['host'])) && // протокола или имя домена - это
// полный URL адрес.
(substr($data[1],0,1)!='#')) // Если URL начинается с символа '#'
// то это ссылка на "якорь" внутри страницы
{
// Берем путь к странице, указанный в URL и добавляем разделитель для параметров
// потому что нам необходимо будет добавить по крайней мере 1 параметр
$href = $parts['path'].'?';
// Если в этом URL уже были какие-либо параметры - добавляем их и добавляем
// разделитель. Заметьте, что в качестве разделителя используется &, а не &,
// это позволяет нам добиться совместимости с XHTML.
if (isset($parts['query']))
$href .= $parts['query'].'&';
// Добавляем наш собственный параметр - идентификатор сессии
$href .= 'sid='.$GLOBALS['sid'];
// Если в оригинальном URL была ссылка на фрагмент документа - возвращаем ее
// на место.
if (isset($parts['fragment']))
$href .= '#'.$parts['fragment'];
// "Вставляем" новый URL на место того, который был там раньше
$href = str_replace($data[1],$href,$data[0]);
};
// Возвращаем результат
return($href);
};
?>
Пример может показаться немного громоздким, но это исключительно
из-за обилия комментариев.
Синтаксис:
array preg_split (string pattern, string subject [, int limit [, int flags]])
Данная функция выполняет действие, аналогичное функциям split()
и explode()
- разбивает строку на части по
какому-либо признаку и возвращает массив, содержащий части строки.
Однако ее возможности по заданию правил разбиения больше, чем у этих
функций, потому что в ее основе лежит механизм регулярных выражений,
в мощи которого, я надеюсь, вы уже смогли убедиться. Если говорить
более конкретно, то строка subject
разбивается на части
по разделителю, заданному регулярным выражением pattern
. При этом количество фрагментов может быть
ограничего необязятельным параметром limit
. Кроме того
эта функция поддерживает необязательный параметр flags
,
который позволяет в некоторой степени контролировать процесс
разбиения строки.
Параметр flags
может принимать следующие значения
(или их комбинации с использованием знака '
'):
PREG_SPLIT_NO_EMPTY
- возвращать только непустые
части строк, полученные в результате разбиения.
PREG_SPLIT_DELIM_CAPTURE
- возвращать также
результаты поиска по внутренним регулярным выражениям.
Рассмотрим пару примеров. Для начала - выражение, которое
разбивает произвольный текст на отдельные слова:
<?php
$text = join('',file('my_text.txt'));
$words = preg_split("/\s+/s",$text);
print_r($words);
?>
Как видите - мы получаем содержимое файла
'my_text.txt
' в виде строки, разбиваем его на отдельные
слова и выводим содержимое массива слов, чтобы убедиться, что все
работает правильно.
Еще один пример производит разбиение заданного слова на буквы (он
описан в PHP Manual):
<?php
$str = 'string';
$chars = preg_split('//',$str,-1,PREG_SPLIT_NO_EMPTY);
print_r($chars);
?>
Значение -1
для параметра limit
означает отсутствие лимита.
Синтаксис:
string preg_quote (string str [, string delimiter])
Эта функция - единственная, не относящаяся непосредственно к
механизму регулярных выражений. Ее назначение - "квотинг" символов,
имеющих специальное значение в синтаксисе регулярных выражений.
Обычно это символы:
. \ + * ? [ ^ ] $ ( ) { } = ! < > :
Все эти символы, встречающиеся в строке будут "отквочены" путем
добавления символа '\
' непосредственно перед каждым из
них. Модифицированная таким образом строка будет возвращены в
качестве результата.
Эта фцнкция также имеет необязательный параметр delimiter
. Если этот параметр задан, то символ,
переданный в качестве этого параметра тоже будет "отквочен" данной
функцией.
Синтаксис:
array preg_grep (string pattern, array input)
Действие этой функции похоже на действие команды grep в
Unix. Она ищет текст по регулярному выражению pattern
,
в массиве input
и возвращает новый массив, содержащий
только элементы, в которых были найдены совпадения с заданным
регулярным выражением. К примеру у нас есть файл, содержащий в
каждой строке числовую и текстовую информацию. Нам необходимо
получить из этого файла только строки, содержащие числа:
Файл data.txt:
123
abc
php4
Код:
<?php
// Считываем содержимое файла в массив
$data = file('data.txt');
// Получаем массив, содержащий цифровую информацию
$numbers = preg_grep("/\d+/",$data);
// Выводим результат работы
print_r($numbers);
?>
Результат работы будет:
Array
(
[0] => 123
[2] => php4
)
Как видите - мы получили все строки, содержащие цифры. Если же
нам, например нужно получить только цифры - то выражение необходимо
немного изменить: /^\s*\d+\s*$/
.