Статьи
Функции регулярных выражений
В PHP существует несколько функций для работы с регулярными выражениями. Все они используют один и тот же парсер регулярных выражений для своей работы, но при этом преследуют различные цели. Ниже мы рассмотрим все эти функции. Я буду приводить описание синтаксиса каждой функции в том виде, в котором она описана в PHP Manual, чтобы вам легче было разобраться.
Функция preg_match()
Синтаксис:
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
)
Как видите - здесь присутствуют результаты поиска по всем имеющимся регулярным выражениям.
Функция preg_match_all()
Синтаксис:
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
)
)
Как видите - здесь основной массив содержит результаты поиска, сгруппированные по порядку их нахождения в тексте, причем каждый результат представляет собой массив с результатами поиска по этому найденному фрагменту для всех имеющихся регулярных выражений.
Функция preg_replace()
Синтаксис:
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( '/(\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;
?>
Результат работы этой программы:
Обратите внимание на текст, используемый для замены. В нем использованы т.н. 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() !
<?php
// $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 );
?>
Сами по себе регулярные выражения очень просты, интересно лишь их совместное использование для решения общей задачи.
Функция preg_replace_callback()
Синтаксис:
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);
};
?>
Пример может показаться немного громоздким, но это исключительно из-за обилия комментариев.
Функция preg_split()
Синтаксис:
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
$str = 'string';
$chars = preg_split( '//', $str, -1, PREG_SPLIT_NO_EMPTY );
print_r( $chars );
?>
Значение -1 для параметра limit означает отсутствие лимита.
Функция preg_quote()
Синтаксис:
string preg_quote (string str [, string delimiter])
Эта функция - единственная, не относящаяся непосредственно к механизму регулярных выражений. Ее назначение - "квотинг" символов, имеющих специальное значение в синтаксисе регулярных выражений. Обычно это символы:
. \ + * ? [ ^ ] $ ( ) { } = ! < > :
Все эти символы, встречающиеся в строке будут "отквочены" путем добавления символа "\ " непосредственно перед каждым из них. Модифицированная таким образом строка будет возвращены в качестве результата.
Эта фцнкция также имеет необязательный параметр delimiter . Если этот параметр задан, то символ, переданный в качестве этого параметра тоже будет "отквочен" данной функцией.
Функция preg_grep()
Синтаксис:
array preg_grep (string pattern, array input)
Действие этой функции похоже на действие команды grep в Unix. Она ищет текст по регулярному выражению pattern , в массиве input и возвращает новый массив, содержащий только элементы, в которых были найдены совпадения с заданным регулярным выражением. К примеру у нас есть файл, содержащий в каждой строке числовую и текстовую информацию. Нам необходимо получить из этого файла только строки, содержащие числа:
Файл data.txt :
Код:
<?php
// Считываем содержимое файла в массив
$data = file( 'data.txt' );
// Получаем массив, содержащий цифровую информацию
$numbers = preg_grep( "/\d+/", $data );
// Выводим результат работы
print_r( $numbers );
?>
Результат работы будет:
Array
(
[0] => 123
[2] => php4
)
Как видите - мы получили все строки, содержащие цифры. Если же нам, например нужно получить только цифры - то выражение необходимо немного изменить:
Автор: Александр Грималовский
|