Задача
Необходимо вычленить текст, находящийся внутри тегов HTML. Например, требуется найти все заголовки в HTML-документе.
Решение
Прочитайте HTML-файл в строку и используйте в шаблоне непоглощающее сравнение:
$html = join('',file($file));
preg_match('#<h([1-6])>(.+?)</h\1>#is', $html, $matches);
В этом примере элемент $matches содержит массив найденных заголовков.
Обсуждение
Посредством простого регулярного выражения трудно правильно проанализировать HTML-документ. В этом состоит одно из преимуществ XHTML; с его помощью значительно легче проверить действительность (validity) документа и провести анализ.
Так, шаблон в разделе «Решение» достаточно изощрен, чтобы найти только соответствующие заголовки, поэтому <h1>Dr. Strangelove<h1> удовлетворяет шаблону, поскольку он заключен в теги <h1>, а <h2>How I Learned to Stop Worrying and Love the Bomb</h3> не удовлетворяет, так как в качестве открывающего тега использован <h2>, а в качестве закрывающего – другой тег (</h3>).
Эта технология работает также при нахождении текста, заключенного в теги полужирного шрифта и в теги курсива:
$html = join('',file($file));
preg_match('#<([bi])>(.+?)</\1>#is', $html, $matches);
Однако это не помогает в случае вложенных заголовков. Применение данного регулярного выражения к следующей строке:
Dr. Strangelove or: <i>How I Learned to Stop Worrying and Love the Bomb</i> не выделяет текст внутри тегов <i> как самостоятельный элемент.
До сих пор это не было проблемой, поскольку заголовки представляют собой элементы уровня блока и не могут быть вложенными. Однако для линейных элементов, таких как выделение полужирным шрифтом или курсивом, вложение тегов вполне допустимо. Найденный текст можно обработать, выполняя цикл по массиву совпадений. Например, следующий фрагмент кода анализирует документ на предмет заголовков и красиво их печатает с отступами, соответствующими уровню заголовков:
$html = join('',file($file));
preg_match('#<h([1-6])>(.+?)</h\1>#is', $html, $matches);
for ($i = 0, $j = count($matches[0]); $i < $j; $i++) {
print str_repeat(' ', 2 * ($matches[$i] - 1)) . $matches[$i] . "\n";
}
В случае такого применения этого рецепта к HTML-тексту вида:
$html =<<<_END_
<h1>PHP Cookbook</h1>
Other Chapters
<h2>Regular Expressions</h2>
Other Recipes
<h3>Capturing Text Inside of HTML Tags</h3>
<h4>Problem</h4>
<h4>Solution</h4>
<h4>Discussion</h4>
<h4>See Also</h4>
_END_;
preg_match_all('#<h([1-6])>(.+?)</h\1>#is', $html, $matches);
for ($i = 0, $j = count($matches[0]); $i < $j; $i++) {
print str_repeat(' ', 2 * ($matches[$i] - 1)) . $matches[$i] . "\n";
}
Получаем:
PHP Cookbook
Regular Expressions
Capturing Text Inside of HTML Tags
Problem
Solution
Discussion
See Also
Выделяя уровень заголовка и сам текст заголовка по отдельности, можно получить прямой доступ к уровню и трактовать его как число при определении размера отступа. Чтобы избежать отступа в два пробела для всех строк, вычтите из уровня 1.