Синтаксический анализ HTML с помощью XPath и PHP
Есть ли способ (с использованием XPath и PHP) выполнить следующее (БЕЗ внешних файлов XSLT)?
- Удалить все таблицы и их содержимое
- Удалите все после первого тега h1
- Сохраняйте только абзацы (ВКЛЮЧАЯ их внутренний HTML (ссылки, списки и т.д.))
Я получил ответ XSLT здесь , но я ищу запросы XPATH, для которых не требуются внешние файлы.
В настоящее время у меня есть соответствующий HTML-код, загруженный в Элемент SimpleXMLElement через:
$doc = @DOMDocument::loadHTML($xml);
$data = simplexml_import_dom($doc);
Теперь мне нужна помощь с:
$data = $data->xpath('??????');
Работал с этим в течение нескольких дней безрезультатно. Я действительно ценю вашу помощь.
Редактировать: Меня не особенно волнует, что находится внутри абзацев, так как я могу использовать strip_tags, чтобы исключить то, чего я не хочу. Все, что мне нужно сделать, это изолировать абзацы от остальной части источника. Я полагаю, что более конкретным и точным требованием было бы следующее:
Возвращать только абзацы (и их html-содержимое), которые не содержатся в таблицах, и только перед первым тегом h1
Правка 2:
Я думаю, что я получил большую часть этого с помощью этого:$query = $xpath->query('//p[not(ancestor::table) and not(preceding::h2)]');
Единственная проблема - это потеря внутреннего HTML.
2 answers
Чтобы просто получить все элементы P не в таблице и только до первого h1, вы можете сделать
$xp = new DOMXPath($dom);
$expression = '//p[not(preceding::h1[1]) and not(ancestor::table)]';
foreach ($xp->query($expression) as $node) {
echo $dom->saveXml($node);
}
Демонстрация на кодовом контроллере
В общем случае, если вы знаете положение первого h1 в документе, более эффективно использовать прямой путь к этому элементу вместо запроса //
, который выполнял бы поиск в любом месте документа. Например, в качестве альтернативы вы также можете использовать XPath, указанный Алехандро в комментариях ниже:
/descendant::h1[1]/preceding::p[not(ancestor::table)]
Если вы хотите создать новый документ DOM из узлов в исходном документе, вам необходимо импортировать узлы в новый документ.
// src document
$dom = new DOMDocument;
$dom->loadXML($xml);
// dest document
$new = new DOMDocument;
$new->formatOutput = TRUE;
// xpath setup
$xp = new DOMXPath($dom);
$expr = '//p[not(preceding::h1[1]) and not(ancestor::table)]';
// importing nodes into dest document
foreach ($xp->query($expr) as $node) {
$new->appendChild($new->importNode($node, TRUE));
}
// output dest document
echo $new->saveXML();
Демонстрация на кодовом контроллере
Еще несколько дополнений
В вашем примере вы использовали оператор подавления ошибок. Это плохая практика. Если вы хотите игнорировать любые ошибки синтаксического анализа из DOM, используйте
libxml_use_internal_errors(TRUE); // catch any DOM errors with libxml
$dom = new DOMDocument; // remove the @ as it is bad practise
$dom->loadXML($xhtml); // use loadHTML if it's not valid XHTML
libxml_clear_errors(); // disregards any DOM related errors
Удаление узлов с помощью DOM - это всегда один и тот же подход. Найдите узел, который вы хотите удалить. Доберитесь до него parentNode
и вызовите на нем removeChild
с удаляемым узлом в качестве аргумента.
foreach ($dom->getElementsByTagName('foo') as $node) {
$node->parentNode->removeChild($node);
}
Вы также можете перейти к узлам-братьям (и дочерним узлам) без XPath. Вот как удалить всех следующих братьев и сестер после первого элемента h1
$firstH1 = $dom->getElementsByTagName('h1')->item(0);
while ($firstH1->nextSibling !== NULL) {
$firstH1->parentNode->removeChild($firstH1->nextSibling);
}
echo $dom->saveXml();
Удаление узлов из DOMDocument
немедленно повлияет на DOMDocument
. В приведенном выше коде мы всегда запрашиваем первого следующего родственника первого h1. Если таковой имеется, он удаляется из DOMDocument
. nextSibling
затем укажет на родного брата после только что удаленного (если таковой имеется).
Извлечение и печать всех абзацев одинаково просты. Чтобы получить OuterXml, просто передайте узел, для которого вы хотите получить OuterXml, методу saveXML
.
foreach ($dom->getElementsByTagName('p') as $paragraph)
{
echo $dom->saveXml($paragraph);
}
В любом случае, это должно тебя взбодрить. Я предлагаю вам ознакомиться с API DOM. Это нетрудно. Вы обнаружите, что большинство вещей, которые вы будете делать, вращаются вокруг свойств и методов в любом DOMDocument
, DOMNode
и DOMElement
(который является подклассом DOMNode
).
Спасибо, Гордон.
Решение:
$dom = @DOMDocument::loadHTML($xml);
$xpath = new DOMXPath($dom);
$query = $xpath->query('//p[
not(ancestor::table) and
not(preceding::h1[1])
]');
foreach ($query as $node){
$result .= $dom->saveXml($node);
}
echo $result;