PHP, регулярные выражения. Извлечение строк между тегами.

Рейтинг: 2Ответов: 3Опубликовано: 24.05.2011

Здравствуйте! Моя задача получить одну таблицу (от < table..... до < /table>) из большой каши на html-е. Эта таблица отличается от массы остальных тем, что имеет в открывающем теге следующую запись:

class="table01"

Исходя из этого составляю соответствующий шаблон:

'/<table .* class="table01" .*>[\S\s]*<\/table>/Uix'

И в итоге получаю нуль. Причем хоть так, хоть так:

$pregTable = '/<table .*? class="table01" .*?>[\S\s]*?<\/table>/ix';

Вот сам код:

$file = file_get_contents('test.html');
$pregTable = '/<table .* class="table01" .*>[\S\s]*<\/table>/Uix';
$arrTable = array();
preg_match_all($pregTable, $file, $arrTable, PREG_SET_ORDER);
print_r($arrTable);

Перепробовал кучу разных вариантов, мучаюсь целый день, ничего не выходит. Получаю либо текст от начала нужной таблицы до закрытия последней - если не использую ? или модификатор U, либо нуль - если с ними. Что я тут делаю не правильно?

Ответы

▲ 10

Самый простой и самый эффективный способ в данном случае - это распарсить HTML при помощи DOM и получить таблицу через XPath:

$text = <<< EOS
<body>
<table class="table01">
    <tr><th>First table</th></tr>
    <tr><td><table><tr><th>Inner <table><tr><th></th></tr></table> table</th></tr></table></td></tr>
    <tr><td><table><tr><th>Second inner table</th></tr></table></td></tr>
</table>
<table>
    <tr><td>Second outer table</th></tr>
</table>
</body>
EOS;

$dom = new DOMDocument();
$dom->loadHTML($text);
$xpath = new DOMXPath($dom);
$nodes = $xpath->evaluate('//table[@class="table01"]');
var_dump($dom->saveXML($nodes->item(0)));

Однако, при желании можно решить данную задачу и при помощи регулярного выражения. Проблема со вложенными таблицами в данном случае решается при помощи рекурсивных выражений:

$class = 'table01';

// Любой символ, с которого не начинается тег <table>
$any = "(?: [^<] | <(?!/?table\b) )";

// Открытый и закрытий теги <table>, между которыми любое количество символов $any,
// либо подставить рекурсивно подшаблон #2 (шаблон #1 - это кавычка, см. далее)
$inner = "(<table[^>]*> (?> $any | (?2) )+? </table>)";

// Тоже самое, что и $inner, но с дополнительным атрибутом у тега <table>
// Модификатор 's' в данном случае не нужен, т.к. мы не используем мета-символ '.'
// А модификатор 'U' не нужен, поскольку мы оперируем только ascii символами
$pattern = "~<table\b[^>]*\bclass=(\"|')?$class\\1[^>]*> (?> $any | $inner )+ </table>~xi";

preg_match($pattern, $text, $m);
var_dump($m);
▲ 3

Как бы вам объяснить ... есть два варианта:

первый:

preg_match('~<table.*?>(.*?)</table>~is', $content, $m );

и второй:

preg_match('~<table.*?>(.*)</table>~is', $content, $m );

Разница только в "?"

первый не подойдёт если внутри таблицы есть еще одна, вложенная таблица.

А второй вариант не подойдёт когда на странице есть 2 таблици паралельно.

Первое правило парсинга гласит: перед тем как смотреть что на выходе, посмотри что на входе (это моё правило, вывел его после таких долгих мучений :) )

Поэтому посмотрите что вы таки парсить пытаетесь, скорее всего вы получаете не ту страницу что ожидаете, либо вообще пустой текст ( например запрещены исходящие запросы )

▲ 2

Раз уж завели речь о DOM парсерах:

http://simplehtmldom.sourceforge.net/manual.htm

$html = file_get_html('test.htm');
$ret = $html->find('table[class=table01]');