Завершаем изучение регулярных выражений в php-скриптах
17.11.08Мой Компьютер, №11 (515), 28.07.2008
Эпизод 4. А поподробнее можно?
Пока что мы только проверяли соответствие строки паттерну и извлекали из текста все совпадения. Однако возникают и ситуации, в которых надо не просто проверить, содержится ли в строке искомый образец, но также получить, где именно он находится (найти позицию, с которой начинается совпадение). Такая возможность часто требуется, например, при написании различных парсеров.
(А вот парсер, если кому некогда заглянуть в справочную Сеть, это синтаксический анализатор — программа или часть программы, выполняющая синтаксический анализ. — Прим. ред.)
Функции preg_match() и preg_match_all() могут возвращать для каждого найденного совпадения номер позиции, но по умолчанию данная возможность выключена. Для ее включения используется специальный флаг PREG_OFFSET_CAPTURE, который указывается после массива с результатами:
preg_match ($pattern, $string, $matches, PREG_OFFSET_CAPTURE);
preg_match_all ($pattern, $string, $matches, PREG_OFFSET_CAPTURE);
При включенном флаге меняется и структура массивов-результатов.
preg_match() вернет массив следующей структуры:
$matches = array(
0=>array( // совпадение
0 => ”………………………», //текст совпадения
1 => n // позиция
)
);
А preg_match_all() — такую структуру:
$matches = array(
0=>array( // совпадения
0 => array( // первое совпадение
0 => ”………………………», //текст совпадения
1 => n // позиция
),
1 => array( // второе совпадение
0 => ”………………………», //текст совпадения
1 => n // позиция
),
……………………………………// и т.д.
)
);
Номер возвращаемой позиции содержит порядковый номер первого символа, с которого началось совпадение с паттерном. Напомню, что как и в языке С, нумерация символов начинается с нуля.
Ниже приведен код демонстрационного скрипта example5.php, который реализует простейший парсер арифметических выражений. Скрипт получает введенное в форму выражение и вычисляет его значение. Поддерживаются целые числа, четыре арифметические операции (с учетом приоритета умножения и деления над сложением и вычитанием) и скобки. Для разбиения выражения на операторы и операнды используются функции preg_match() и preg_match_all() с включенным флагом PREG_OFFSET_CAPTURE.
<?php
// функция получения «атома»
// один «атом» — элемент арифметического выражения:
// операнд, оператор или скобка
function get_atom($str, &$pos)
{
// выполняем поиск первого совпадения в строке
// паттерн задает одну из трех альтернатив:
// — либо последовательность десятичных цифр d+
// — либо открывающая скобка (
// — либо знак арифметической операции [+-*/]
preg_match(«/(|d+|[+-*/]/», $str, $m, PREG_OFFSET_CAPTURE);
// позиция
$pos = $m[0][1];
// найденный «атом»
return $m[0][0];
}
// функция находит для скобки позицию
// соответствующей ей закрывающей скобки
// с учетом вложенности скобок
function find_close_bracket($str)
{
// переменная, куда будет записываться позиция
$pos = 0;
// глубина вложенности скобок
$depth = 0;
// получаем все скобки, начиная с позиции,
// открывающей скобки, для которой ищем закрывающую
preg_match_all(«/(|)/», $str, $m, PREG_OFFSET_CAPTURE);
// идем циклом по найденным совпадениям
foreach ($m[0] as $key=>$value)
{
// на каждом шаге сохраняем значение позиции
$pos = $value[1];
// если встретили открывающую скобку — увеличиваем глубину вложенности
if ($value[0] == «(«) $depth++;
// если встретили закрывающую скобку — уменьшаем глубину вложенности
if ($value[0] == «)») $depth—;
// если после очередного изменения глубины
// ее значение ноль — искомая скобка найдена
if ($depth == 0) break;
}
// возвращаем позицию
return $pos;
}
// если в начале выражения стоит знак минус,
// это значит, что первое число отрицательное
// также в начале выражения может стоять
// открывающая скобка
// любые другие символы надо вырезать
// это делает данная функция
function trim_arr(&$arr)
{
// если первый элемент массива с последовательностью «атомов» выражения
// не число, не знак минус и не скобка,
// то вырезаем его
if (!preg_match(«/d+|-|(/», $arr[0])) unset($arr[0]);
}
// функция принимает массив с «атомами» выражения
// и выполняет мультипликативные операции (умножение, деление)
function multiplicative (&$expr)
{
// указатель на начало массива
reset($expr);
// получили первое значение
$value = current($expr);
// идем циклом по массиву
do
{
// если текущий элемент содержит операцию
if (($value == «*») or ($value == «/»))
{
// здесь мы возьмем предыдущий и следующий элементы,
// которые содержат операнды, и выполним операцию
// ключ текущего элемента
$key = key($expr);
// получаем первый операнд — предыдущий элемент массива
$op1 = prev($expr);
// получили ключ предыдущего элемента массива
$prevkey = key($expr);
// двигаемся вперед обратно к текущему элементу
next($expr);
// и еще вперед на следующий элемент. Получаем второй операнд
$op2 = next($expr);
// и ключ следующего элемента
$nextkey = key($expr);
// не забываем со следующего элемента вернуться назад к текущему
prev($expr);
// выполняем нужную операцию
// результат помещаем в текущий элемент,
// где до этого содержался оператор
switch ($value)
{
case «*»:
$expr[$key] = $op1 * $op2;
break;
case «/»:
$expr[$key] = $op1 / $op2;
break;
}
// удаляем предыдущий и следующий
// элементы массива с операндами
unset($expr[$prevkey]);
unset($expr[$nextkey]);
}
}
while ($value = next($expr));
}
// функция принимает массив с «атомами» выражения
// и выполняет аддитивные операции (сложение, вычитание)
function additive($arr)
{
// тут будет накапливаться сумма
$sum = 0;
// тут будет храниться последний оператор
// по умолчанию полагаем, что это оператор значения
$op = «+»;
// идем циклом по массиву
foreach ($arr as $key=>$value)
{
// если текущий элемент оператор
if (($value == «-«) or ($value == «+»))
// сохраняем его в переменной
$op = $value;
else
{
// если текущий элемент — операнд,
// в зависимости от последнего оператора,
// прибавляем или отнимаем его от текущей
// суммы
switch ($op)
{
case «+»:
$sum += $value;
break;
case «-«:
$sum -= $value;
break;
}
}
}
// возвращаем сумму
return $sum;
}
// собственно рекурсивная функция,
// которая парсит выражение и
// вычисляет его значение
function calculate($str)
{
// инициализируем массив для хранения «атомов»
$expr = array();
// выполняем поиск всех «атомов» в строке
do
{
$p = -1;
// получаем первый встретившийся атом
$atom = get_atom($str, $p);
// если это скобка
if ($atom == «(«)
{
// ищем для нее закрывающую скобку
$p1 = find_close_bracket($str);
// рекурсивно вычисляем значения выражения в скобках
// и заносим его значение как операнд в массив «атомов»
$expr[] = calculate(substr($str, $p+1, $p1 — $p — 1));
// удаляем обработанную часть строки
$str = substr($str, $p1 + 1, strlen($str) — $p1 — 1);
}
elseif ($atom)
{
// если не скобка — добавляем оператор или операнд в массив атомов
$expr[] = $atom;
// удаляем обработанную часть строки
$str = substr($str, $p + strlen($atom), strlen($str) — $p — strlen($atom));
}
}
while ($atom);
// обрезаем при надобности первый элемент массива
trim_arr($expr);
// сначала выполняем мультипликативные операции,
// т.к. у них более высокий приоритет
multiplicative($expr);
// затем выполняем аддитивные операции
// и возвращаем результат
return additive($expr);
}
// если был произведен ввод в поле
if (isset($_REQUEST[«expr»]))
{
// вычисляем результат выражения
$result = calculate($_REQUEST[«expr»]);
}
?>
<form id=»checkform» name=»checkform» action=»example5.php» method=»post»>
<table width=»250″>
<tr>
Окончание на стр. 29
Окончание. Начало на стр. 26-27
<td>
Выражение:
</td>
<td>
<input type=»text» id=»expr» name=»expr» size=»15″ value=»<?php echo $_REQUEST[«expr»]; ?>»>
</td>
</tr>
<tr>
<td colspan=»2″ align=»center»>
<input type=»submit» value=»Вычислить»>
</td>
</tr>
</table>
</form>
<?php
// если был произведен ввод в поле
if (isset($_REQUEST[«expr»]))
{
?>
<br><b>Результат: </b>
<?php
// выдаем результат
echo $result;
}
?>
В качестве «домашнего задания» предлагаю заинтересовавшимся читателям доработать данный скрипт. Добавить поддержку дробных чисел, операцию возведения в степень, различные математические функции (синус, косинус, корень, логарифм и т.п.). Надеюсь, что у вас все получится!
Алексей «CyberAdmin» СЕРДЮКОВ
Web-droid редактор
Не пропустите интересное!
Підписывайтесь на наши каналы и читайте анонсы хай-тек новостей, тестов и обзоров в удобном формате!


Обзор смартфонов Samsung Galaxy A36 и Galaxy A56: просвет



У Samsung Galaxy A36 и Galaxy A56 одинаково хорошие дисплеи, емкие аккумуляторы, есть поддержка обновлений софта в течение 6 лет. Расскажем подробнее чем еще они интересны

Новые электрокары BYD EV заряжаются полностью за полчаса автомобиль электротранспорт
В Китае компания BYD представила первые электромобили на новой платформе Super e-Platform — седан Han L и внедорожник Tang L
Kawasaki представила концепт робо-коня Corleo робот
На выставке Osaka Kansai Expo 2025 компания Kawasaki представила концептуального робота для передвижения по пересеченной местности.