Pobieranie elementów DOM według nazwy klasy

Odpowiedzi:

154

Aktualizacja: wersja Xpath *[@class~='my-class']selektora css

Więc po moim komentarzu poniżej w odpowiedzi na komentarz hakre, zaciekawiło mnie i zajrzałem do kodu Zend_Dom_Query. Wygląda na to, że powyższy selektor został skompilowany do następującej ścieżki xpath (nieprzetestowana):

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

więc php wyglądałoby tak:

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

Zasadniczo wszystko, co tutaj robimy, to znormalizowanie classatrybutu, tak aby nawet pojedyncza klasa była ograniczona spacjami, a cała lista klas była ograniczona spacjami. Następnie dodaj spację do szukanej klasy. W ten sposób skutecznie szukamy i znajdujemy tylko wystąpienia my-class.


Użyć selektora xpath?

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

Jeśli jest to tylko jeden typ elementu, można go zastąpić *konkretną zmienną.

Jeśli chcesz zrobić dużo tego z bardzo złożonym selektorem, polecam, Zend_Dom_Queryktóry obsługuje składnię selektora CSS (a la jQuery):

$finder = new Zend_Dom_Query($html);
$classname = 'my-class';
$nodes = $finder->query("*[class~=\"$classname\"]");
prodigitalson
źródło
znajduje również klasę my-class2, ale całkiem fajną. Czy jest jakiś sposób, aby wybrać tylko pierwsze elementy?
hakre
Nie sądzę, żebyś mógł bez xpath2 ... Jednak przykład Zend_Dom_Query robi dokładnie to. JEŚLI nie chcesz używać tego compkenetu w swoim projekcie, możesz chcieć zobaczyć, jak tłumaczą ten selektor css na xpath. Może DomXPath obsługuje xpath 2.0 - nie jestem tego pewien.
prodigitalson
1
ponieważ classmoże mieć więcej niż jedną klasę, na przykład: <a class="my-link link-button nav-item">.
prodigitalson
2
@prodigitalson: To jest niepoprawne, ponieważ nie odzwierciedla spacji, spróbuj //*[contains(concat(' ', normalize-space(@class), ' '), ' classname ')](Bardzo pouczające: selektory CSS i wyrażenia XPath ).
hakre
1
@babonk: tak, musisz użyć containsw połączeniu z concat... my tylko omawiamy szczegóły wypełniania spacji po obu stronach klasy, której szukasz, lub wypełnienia tylko z jednej strony. Jednak oba powinny działać.
prodigitalson
20

Jeśli chcesz uzyskać innerhtml klasy bez zend, możesz użyć tego:

$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;
Tschallacka
źródło
2
Brak średnika w linii$classname = 'main-article'
Kamil
12

Myślę, że przyjęty sposób jest lepszy, ale myślę, że to również może działać

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;
}
dav
źródło
2
Gdzie jest na to przykład? Byłoby miło.
robue-a7119895
To wspaniale. Mam element z klasą. Teraz chcę edytować zawartość elementu, na przykład dołączyć child do elementu zawierającego klasę. Jak dołączyć dziecko i odtworzyć cały HTML? Proszę pomóż. Oto, co zrobiłem. $classResult = getElementByClass($dom, 'div', 'm-signature-pad'); $classResult->nodeValue = ''; $enode = $dom->createElement('img'); $enode->setAttribute('src', $signatureImage); $classResult->appendChild($enode);
Keyur
1
dla Dom modyfikacji PHP Myślę, że lepiej jest użyć phpquery github.com/punkave/phpQuery
DAV
7

Istnieje również inne podejście bez użycia DomXPathlub Zend_Dom_Query.

Opierając się na oryginalnej funkcji dav, napisałem następującą funkcję, która zwraca wszystkie dzieci węzła nadrzędnego, którego tag i klasa pasują do parametrów.

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;
}

załóżmy, że masz zmienną $htmlnastępujący kod 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>

użycie getElementsByClassjest tak proste, jak:

$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".
oabarca
źródło
6

DOMDocument wolno pisze, a phpQuery ma poważne problemy z wyciekiem pamięci. Skończyło się na:

https://github.com/wasinger/htmlpagedom

Aby wybrać zajęcia:

include 'includes/simple_html_dom.php';

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

Mam nadzieję, że pomoże to również komuś innemu

iautomation
źródło