Разбор HTML с XPath и PHP - PullRequest
       0

Разбор HTML с XPath и PHP

2 голосов
/ 04 января 2011

Есть ли способ (с использованием XPath и PHP) сделать следующее (БЕЗ внешних XSLT-файлов)?

  • Удалить все таблицы и их содержимое
  • Удалить все после первогоh1 tag
  • Сохранять только абзацы (ВКЛЮЧАЯ их внутренний 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 ]

8 голосов
/ 04 января 2011

Чтобы просто получить все элементы 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, данный Alejandro в комментариях ниже:

/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 будет указывать на брата после только что удаленного (если есть).


Получение и печать всех параграфов одинаково просты.Чтобы получить externalXML, просто передайте узел, для которого вы хотите externalXML, методу saveXML.

foreach ($dom->getElementsByTagName('p') as $paragraph)
{
    echo $dom->saveXml($paragraph);
}

В любом случае, это должно помочь вам.Я предлагаю вам ознакомиться с DOM API .Это не сложно.Вы обнаружите, что большинство вещей, которые вы будете делать, вращаются вокруг свойств и методов в DOMDocument, DOMNode и DOMElement (который является подклассом DOMNode).

0 голосов
/ 04 января 2011

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

Решение:

    $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;
...