Синтаксический анализ 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.

Author: Community, 2011-01-04

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).

 8
Author: Gordon, 2011-01-04 18:32:18

Спасибо, Гордон.

Решение:

    $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;
 0
Author: Peter, 2011-01-04 10:47:51