Получение элементов DOM по имени класса


124

Я использую PHP DOM и пытаюсь получить в узле DOM элемент с заданным именем класса. Как лучше всего получить этот подэлемент?

Обновление: в итоге я использовал MechanizePHP, с которым было намного проще работать.


Ответы:


154

Обновление: версия Xpath *[@class~='my-class']селектора css

Поэтому после моего комментария ниже в ответ на комментарий Хакре мне стало любопытно, и я заглянул в код Zend_Dom_Query. Похоже, что приведенный выше селектор скомпилирован в следующий xpath (непроверенный):

[contains(concat(' ', normalize-space(@class), ' '), ' my-class ')]

так что php будет:

$dom = new DomDocument();
$dom->load($filePath);
$finder = new DomXPath($dom);
$classname="my-class";
$nodes = $finder->query("//*[contains(concat(' ', normalize-space(@class), ' '), ' $classname ')]");

По сути, все, что мы здесь делаем, - это нормализуем classатрибут, так что даже один класс ограничен пробелами, а полный список классов ограничен пробелами. Затем добавьте пробел в класс, который мы ищем. Таким образом, мы эффективно ищем и находим только экземпляры my-class.


Использовать селектор xpath?

$dom = new DomDocument();
$dom->load($filePath);
$finder = new DomXPath($dom);
$classname="my-class";
$nodes = $finder->query("//*[contains(@class, '$classname')]");

Если это только один тип элемента, вы можете заменить *его конкретным тэгом.

Если вам нужно сделать много этого с помощью очень сложного селектора, я бы порекомендовал, Zend_Dom_Queryкоторый поддерживает синтаксис селектора CSS (а-ля jQuery):

$finder = new Zend_Dom_Query($html);
$classname = 'my-class';
$nodes = $finder->query("*[class~=\"$classname\"]");

находит и класс my-class2, но довольно мило. Есть ли способ выбрать только первый из всех элементов?
hakre

Я не думаю, что можно обойтись без xpath2 ... Однако пример для Zend_Dom_Query делает именно это. ЕСЛИ вы не хотите использовать этот compkenet в своем проекте, тогда вы можете посмотреть, как они переводят этот селектор css в xpath. Возможно, DomXPath поддерживает xpath 2.0 - я в этом не уверен.
prodigitalson

1
потому что classможет иметь более одного класса, например: <a class="my-link link-button nav-item">.
prodigitalson

2
@prodigitalson: это неверно, поскольку оно не отражает пробелы, попробуйте //*[contains(concat(' ', normalize-space(@class), ' '), ' classname ')](очень информативно: селекторы CSS и выражения XPath ).
hakre

1
@babonk: да, вам нужно использовать containsв сочетании с concat... мы просто обсуждаем особенности заполнения пробелов с обеих сторон класса, который вы ищете, или заполнения только одной стороны. Либо должно работать.
prodigitalson

20

Если вы хотите получить innerhtml класса без zend, вы можете использовать это:

$dom = new DomDocument();
$dom->load($filePath);
$classname = 'main-article';
$finder = new DomXPath($dom);
$nodes = $finder->query("//*[contains(concat(' ', normalize-space(@class), ' '), ' $classname ')]");
$tmp_dom = new DOMDocument(); 
foreach ($nodes as $node) 
    {
    $tmp_dom->appendChild($tmp_dom->importNode($node,true));
    }
$innerHTML.=trim($tmp_dom->saveHTML()); 
echo $innerHTML;

2
В строке отсутствует точка с запятой$classname = 'main-article'
Камил

12

Я думаю, что принятый способ лучше, но я думаю, что это тоже может сработать

function getElementByClass(&$parentNode, $tagName, $className, $offset = 0) {
    $response = false;

    $childNodeList = $parentNode->getElementsByTagName($tagName);
    $tagCount = 0;
    for ($i = 0; $i < $childNodeList->length; $i++) {
        $temp = $childNodeList->item($i);
        if (stripos($temp->getAttribute('class'), $className) !== false) {
            if ($tagCount == $offset) {
                $response = $temp;
                break;
            }

            $tagCount++;
        }

    }

    return $response;
}

2
Где для этого пример? Было бы хорошо.
robue-a7119895 06

Замечательно. Я получил элемент с классом. Теперь я хочу отредактировать содержимое элемента, например добавить дочерний элемент к элементу, содержащему класс. Как добавить дочерний элемент и воссоздать весь HTML? Пожалуйста помоги. Вот что я сделал. $classResult = getElementByClass($dom, 'div', 'm-signature-pad'); $classResult->nodeValue = ''; $enode = $dom->createElement('img'); $enode->setAttribute('src', $signatureImage); $classResult->appendChild($enode);
Keyur

1
для модификации dom с помощью php, я думаю, лучше использовать phpquery github.com/punkave/phpQuery
dav

7

Есть и другой подход без использования DomXPathили Zend_Dom_Query.

На основе исходной функции dav я написал следующую функцию, которая возвращает всех дочерних элементов родительского узла, тег и класс которого соответствуют параметрам.

function getElementsByClass(&$parentNode, $tagName, $className) {
    $nodes=array();

    $childNodeList = $parentNode->getElementsByTagName($tagName);
    for ($i = 0; $i < $childNodeList->length; $i++) {
        $temp = $childNodeList->item($i);
        if (stripos($temp->getAttribute('class'), $className) !== false) {
            $nodes[]=$temp;
        }
    }

    return $nodes;
}

предположим, у вас есть переменная $htmlследующего HTML:

<html>
 <body>
  <div id="content_node">
    <p class="a">I am in the content node.</p>
    <p class="a">I am in the content node.</p>
    <p class="a">I am in the content node.</p>    
  </div>
  <div id="footer_node">
    <p class="a">I am in the footer node.</p>
  </div>
 </body>
</html>

использование getElementsByClassтак же просто, как:

$dom = new DOMDocument('1.0', 'utf-8');
$dom->loadHTML($html);
$content_node=$dom->getElementById("content_node");

$div_a_class_nodes=getElementsByClass($content_node, 'div', 'a');//will contain the three nodes under "content_node".

6

DOMDocument медленно набирает, а phpQuery имеет проблемы с утечкой памяти. В итоге я использовал:

https://github.com/wasinger/htmlpagedom

Чтобы выбрать класс:

include 'includes/simple_html_dom.php';

$doc = str_get_html($html);
$href = $doc->find('.lastPage')[0]->href;

Надеюсь, это поможет и кому-то другому

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.