Завершаем изучение регулярных выражений в 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 editor

влажность:
давление:
ветер:

Обзор игрового ноутбука Lenovo Legion 5i 15IMH05: дуализм



Аналитики говорят о важности умения быть гибким, что в жизни, что в бизнесе. Рассматриваемый ноутбук Lenovo Legion 5i 15IMH05 – хороший пример того, как можно успешно работать на две стороны – и в работе, и в развлечениях

Viber дозволив блокувати дзвінки від невідомих контактів
Viber блокировка мессенджер обновлениеУ Viber з’явилась функція “Блокування невідомих абонентів”, користувачі не будуть отримувати дзвінки від невідомих контактів
Игровой смартфон Asus ROG Phone 5 стоит 35 000 грн в Украине
ASUS Republic of Gamers игры смартфон события в УкраинеКомпания ASUS начинает продажи в Украине нового игрового смартфона ROG Phone 5. Модель имеет очень впечатляющие характеристики и некоторые необычные решения
