Отримання елементів DOM за назвою класу


124

Я використовую PHP DOM і намагаюся отримати елемент у вузлі DOM, який має задане ім’я класу. Який найкращий спосіб отримати цей піделемент?

Оновлення: я закінчив використовувати Mechanizeдля PHP, з яким було набагато простіше працювати.


Відповіді:


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 (a la jQuery):

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

знаходить клас my-class2також, але досить солодкий. Будь-який спосіб вибрати лише перший з усіх елементів?
хакре

Я не думаю, що ти можеш без xpath2 ... Однак приклад Zend_Dom_Query робить саме це. Якщо ви не хочете використовувати цей компроменет у своєму проекті, ви можете побачити, як вони перекладають цей селектор css на xpath. Можливо, DomXPath підтримує xpath 2.0 - я не впевнений у цьому.
блудники

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

2
@prodigitalson: Це неправильно, оскільки не відображає пробілів, спробуйте //*[contains(concat(' ', normalize-space(@class), ' '), ' classname ')](Дуже інформативно: CSS Selectors And XPath Expressions ).
хакре

1
@babonk: так, вам потрібно використовувати containsв поєднанні з concat... ми обговорюємо деталі прокладки пробілів з обох сторін класу, які ви шукаєте або лише додаєте одну сторону. І все, хоча працювати.
блудники

20

Якщо ви хочете отримати внутрішній код класу без зайвих зусиль, ви можете скористатися цим:

$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

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

1
для модифікації dom за допомогою php Я думаю, що краще використовувати phpquery github.com/punkave/phpQuery
дав

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.