zaLinux.ru

Поиск многострочных совпадений регулярными выражениями PHP


Многострочность в регулярных выражениях PHP

По умолчанию регулярные выражения в PHP ищут совпадения в пределах одной строки. И в этом случае символ «.» (точка) который обычно описывается как «всё что угодно» на самом деле означает «всё что угодно кроме перехода на новую строку».

Это поведение по умолчанию можно поменять модификатором шаблона в том случае, если вам нужно найти совпадения выходящие за пределы одной строки, то есть многострочные совпадения. Как это сделать для поиска многострочных совпадений, а также почему не работают анкоры ^ (начало строки) и $ (конец строки) — данная статья посвящена рассмотрению всех этих вопросов поиска регулярными выражениями PHP многострочных совпадений.

Смотрите также:

Пример HTML для парсинга и тестирования регулярных выражений

В последующих примерах я в качестве текста, по которому выполняется поиск, буду использовать следующий HTML код.

<!DOCTYPE html>
<html>

<head>
	<title>HTML example for ZaLinux.ru and Suay.Site</title>

	<link rel="stylesheet" href="highlightjs/vs.min.css">
	<script src="highlightjs/highlight.min.js"></script>
	<script>hljs.highlightAll();</script>
</head>

<body>
<h2>An Unordered HTML List</h2>
<ul>
	<li>Coffee
	<li>Tea
	<li>Milk
</ul>

<h2>An Ordered HTML List</h2>
<ol>
	<li>Coffee
	<li>Tea
	<li>Milk
</ol>

<h2>HTML styles</h2>
<p>I am normal
<p style="color:red;">I am red
<p style="color:blue;">I am blue
<p style="font-size:50px;">I am big

<h2>Source code of this page:</h2>
<pre><code>
&lt;!DOCTYPE html&gt;
&lt;html&gt;

&lt;head&gt;
	&lt;title&gt;HTML example for ZaLinux.ru and Suay.Site&lt;/title&gt;

	&lt;link rel=&quot;stylesheet&quot; href=&quot;highlightjs/vs.min.css&quot;&gt;
	&lt;script src=&quot;highlightjs/highlight.min.js&quot;&gt;&lt;/script&gt;
	&lt;script&gt;hljs.highlightAll();&lt;/script&gt;

&lt;/head&gt;



&lt;body&gt;

&lt;h2&gt;An Unordered HTML List&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Coffee&lt;/li&gt;
  &lt;li&gt;Tea&lt;/li&gt;
  &lt;li&gt;Milk&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;An Ordered HTML List&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Coffee&lt;/li&gt;
  &lt;li&gt;Tea&lt;/li&gt;
  &lt;li&gt;Milk&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;HTML styles&lt;/h2&gt;

&lt;p&gt;I am normal&lt;/p&gt;
&lt;p style=&quot;color:red;&quot;&gt;I am red&lt;/p&gt;
&lt;p style=&quot;color:blue;&quot;&gt;I am blue&lt;/p&gt;
&lt;p style=&quot;font-size:50px;&quot;&gt;I am big&lt;/p&gt;

&lt;h2&gt;Something else&lt;/h2&gt;

&lt;div class=&quot;maincontent&quot;&gt;
	&lt;p&gt;The most useful content is here.
&lt;/div&gt;

&lt;div class=&quot;maincontent&quot;&gt;&lt;p&gt;One-liner DIV.&lt;/div&gt;

&lt;div class=&quot;maincontent&quot;&gt;
	&lt;p&gt;Multi line
	&lt;p&gt;DIV content
	&lt;p&gt;here.
&lt;/div&gt;

&lt;/body&gt;
</code></pre>

<h2>Something else</h2>
<div class="maincontent">
	<p style="color:red;">The most useful content is here.
</div>

<div class="maincontent"><p><i>One-liner DIV</i></div>

<div class="maincontent">
	<p><b>Multi line DIV</b></p>
	<p><b> content here.</b></p>
	<p><b>I have a P ending tag for some reason.</b></p>
</div>

</body>
</html>

Этот код не имеет особого смысла, но содержит различные многострочные элементы, которые хорошо подходят для целей данной статьи.

Поиск совпадений, включающих переход на новую строку

Попытаемся найти HTML-тэги DIV. Следующий пример:

<?php

$html = file_get_contents('demo3.htm');
preg_match_all ('#<div class="maincontent">.*?</div>#', $html, $result);
print_r ($result);

Найдёт всего лишь один DIV:

В этом регулярном выражении использеттся «.*» (точка и звёздочки), точка означает «что угодно», а звёздочка «сколько угодно раз», а вмести они означают «что угодно сколько угодно раз, в том числе и ноль раз». То есть несмотря на использование этой конструкции не были найдены многострочные тэги DIV. Как уже было сказано, это связано с тем, что в понятие «что угодно» по умолчанию не включены переходы строки.

Знак вопроса в данном случае означает «сделать регулярное выражение ленивым», то есть будет найдена наименьшая совпадающая часть.

Как в регулярном выражении PHP обозначить новую строку

Новая строка (newline (hex 0A)) обозначается как:

\n

Также имеется обозначение возврата каретки (carriage return (hex 0D)):

\r

Добавим символы новой строки в наше регулярное выражение:

$html = file_get_contents('demo3.htm');
preg_match_all ('#<div class="maincontent">\n.*?\n</div>#', $html, $result);
print_r ($result);

Как можно увидеть теперь найден другой, трёхстрочный тэг DIV.

Но не найден однострочный и многострочные тэги DIV.

Регулярное выражение PHP для поиска по всему тексту без разделения по строкам

Для многострочного поиска по регулярному выражению добавьте модификатор шаблона «s». Если использовать данный модификатор, то метасимвол «.» действительно начинает означать «любой символ», в том числе включая перевод строк.

preg_match_all ('#<div class="maincontent">.*?</div>#s', $html, $result);

Результат:

То есть найдены все три тэга DIV независимо от количества в них строк:



    [0] => <div class="maincontent">
	<p style="color:red;">The most useful content is here.
</div>
    [1] => <div class="maincontent"><p><i>One-liner DIV</p></i></div>
    [2] => <div class="maincontent">
	<p><b>Multi line DIV</b></p>
	<p><b> content here.</b></p>
	<p><b>I have a P ending tag for some reason.</b></p>
</div>

Как обозначить конец и начало строки в регулярном выражении PHP

Рассмотрим следующий пример — в нём мы пытаемся найти все HTML тэги P. Причём HTML код имеет начальные тэги P, но не имеет завершающих тэгов P — это не совсем правильно, но в веб-браузере такая HTML разметка всё равно отображается правильно. То есть мы не можем искать строки вида

<p>…….</p>

В данном случае нужно искать строки вида

<p>…….$

Где символ «$» означает «конец строки».

Итак, регулярное выражение для поиска тэгов P:

<?php

$html = file_get_contents('demo3.htm');
preg_match_all ('#(<p>.*?$)|(<p .*?$)#', $html, $result);
print_r ($result[0]);

Вопреки ожиданиям, ничего не найдено:

Дело в том, что метасимволы '^' и '$', на самом деле, соответствуют началу всего обрабатываемого текста и концу всего обрабатываемого текста — даже если текст разбит на несколько строк. Для изменения этого поведения по умолчанию необходимо использовать модификатор шаблона «m». Если используется этот модификатор, то метасимволы '^' и '$' начинают работать как и предполагается: они означают начало строк и конец строк. В случае, если обрабатываемый текст не содержит символов перевода строки, либо шаблон не содержит метасимволов '^' или '$', данный модификатор не имеет никакого эффекта.

Попробуем:

$html = file_get_contents('demo3.htm');
preg_match_all ('#(<p>.*?$)|(<p .*?$)#m', $html, $result);
print_r ($result[0]);

Теперь найдены все тэги P:


Как совместно использовать модификаторы шаблонов «s» и «m» в регулярных выражениях PHP

Возникает вопрос, если при использовании модификатора «s» весь текст для поиска как бы объединяется в одну строку, то что в этом случае произойдёт если ещё добавить и модификатор «m»?

На самом деле, действие модификаторов не мешает друг другу. Если использовать оба модификатора в одном регулярном выражении, то будет достигнут следующий эффект:

  • - метасимвол «.» (точка) начинает означать «любой символ, в том числе новая строка»
  • - метасимволы '^' и '$' начинают означать «начало строки» и «конец строки» соответственно.

Попытаемся найти все тэги OL, UL (в том числе многострочные), а также все тэги P:

preg_match_all ('#(<p>.*?$)|(<p .*?$)|(<ol.*?</ol>)|(<ul.*?</ul>)#ms', $html, $result);

Полученный результат соответствует тому, что мы ожидали:

В чём различие символа новая строка «\n» и метасимволов '^' и '$' в регулярных выражениях PHP

Различия следующие:

1. Символ «\n» обозначает «новую строку» даже без модификатора шаблона «m». Немного изменённый предыдущий пример: убран модификатор шаблона «m» и заменены символы «$» на «\n»:

preg_match_all ('#(<p>.*?\n)|(<p .*?\n)|(<ol.*?</ol>)|(<ul.*?</ul>)#s', $html, $result);

Результат:

С одной стороны, результат точно такой же — найдены те же 11 совпадений. Но, с другой стороны, результаты выглядят как-то по-другому, список выглядит как будто бы в нём больше пробелов. Объяснение дано во втором пункте.

2. Символ «\n» добавляет в найденные совпадения обозначения перехода строки. То есть на конце найденных строк имеется символ «новая строка».

3. Метасимволы '^' и '$' при использовании модификатора шаблона «m» работают одинаково для текстов созданных в разных операционных системах. Что касается экранированной последовательности «\n», то она будет работать как вы ожидаете только для текстов, созданных в Linux. В операционных системах Windows и Mac OS для обозначения перевода строк могут использоваться символы «\r\n» или «\r».

Заключение

Итак, если вы хотите, чтобы поиск по регулярному выражению выполнялся не по отдельным строкам, а по всему тексту, то используйте модификатор шаблона «s».

Чтобы метасимволы '^' и '$' начали обозначать начало и конец строки, добавьте к регулярному выражению модификатор шаблона «m».

Если вы хотите обозначить символ новой строки в определённом месте шаблона регулярного выражения, то вы можете использовать экранированную последовательность «\n», либо метасимволы '^' и '$' с добавлением модификатора шаблона «m».


Рекомендуемые статьи:

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *