Obtendo elementos DOM por nome de classe

124

Estou usando o PHP DOM e estou tentando obter um elemento dentro de um nó DOM que tenha um determinado nome de classe. Qual é a melhor maneira de obter esse subelemento?

Atualização: acabei usando o MechanizePHP, que era muito mais fácil de trabalhar.

bgcode
fonte

Respostas:

154

Atualização: versão Xpath do *[@class~='my-class']seletor de css

Então, depois do meu comentário abaixo em resposta ao comentário do hakre, fiquei curioso e olhei o código por trás Zend_Dom_Query. Parece que o seletor acima foi compilado no seguinte xpath (não testado):

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

então o php seria:

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

Basicamente, tudo o que fazemos aqui é normalizar o classatributo para que até uma única classe seja delimitada por espaços e a lista completa de classes seja delimitada por espaços. Em seguida, acrescente a classe que estamos procurando com um espaço. Dessa forma, estamos procurando e encontrando apenas instâncias de my-class.


Use um seletor xpath?

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

Se for apenas um tipo de elemento, você poderá substituí-lo pelo nome *da tag específico.

Se você precisar fazer muito disso com um seletor muito complexo, recomendo Zend_Dom_Queryque suporte a sintaxe do seletor CSS (a jQuery):

$finder = new Zend_Dom_Query($html);
$classname = 'my-class';
$nodes = $finder->query("*[class~=\"$classname\"]");
prodigitalson
fonte
acha a classe my-class2também, mas é muito legal. Alguma maneira de escolher apenas o primeiro de todos os elementos?
hakre
Eu não acho que você pode sem xpath2 ... No entanto, o exemplo para Zend_Dom_Query faz exatamente isso. Se você não quiser usar esse compkenet em seu projeto, poderá ver como eles estão traduzindo esse seletor de css para xpath. Talvez o DomXPath suporte o xpath 2.0 - não tenho certeza disso.
prodigitalson
1
porque classpode ter mais de uma classe, por exemplo: <a class="my-link link-button nav-item">.
Prodigitalson
2
@prodigitalson: Isso está incorreto, pois não reflete os espaços, tente //*[contains(concat(' ', normalize-space(@class), ' '), ' classname ')](Muito informativo: seletores CSS e expressões XPath ).
hakre
1
@babonk: sim, você precisa usar containsem combinação com concat... estamos discutindo os detalhes de preencher os espaços de ambos os lados da classe que você está procurando ou apenas de um lado. Ou deve funcionar embora.
Prodigitalson
20

Se você deseja obter o innerhtml da classe sem o zend, você pode usar isto:

$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
fonte
2
Ponto e vírgula ausente para a linha #$classname = 'main-article'
Kamil
12

Eu acho que a maneira aceita é melhor, mas acho que isso também pode funcionar

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
fonte
2
Onde está o exemplo disso? Teria sido bom.
224156 Robinson-a7119895
Isso é ótimo. Eu peguei o elemento com a classe. Agora eu quero editar o conteúdo do elemento, como acrescentar filho ao elemento que contém a classe. Como anexar o filho e recriar HTML inteiro? Por favor ajude. Isto é o que eu fiz. $classResult = getElementByClass($dom, 'div', 'm-signature-pad'); $classResult->nodeValue = ''; $enode = $dom->createElement('img'); $enode->setAttribute('src', $signatureImage); $classResult->appendChild($enode);
Keyur
1
para modificação dom por php Eu acho que é melhor usar phpquery github.com/punkave/phpQuery
DAV
7

Também há outra abordagem sem o uso de DomXPathou Zend_Dom_Query.

Com base na função original do dav, escrevi a seguinte função que retorna todos os filhos do nó pai cuja tag e classe correspondem aos parâmetros.

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

suponha que você tenha uma variável com $htmlo seguinte 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>

O uso de getElementsByClassé tão simples quanto:

$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
fonte
6

O DOMDocument é lento para digitar e o phpQuery tem problemas de vazamento de memória. Acabei usando:

https://github.com/wasinger/htmlpagedom

Para selecionar uma turma:

include 'includes/simple_html_dom.php';

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

Espero que isso ajude outra pessoa também

iautomation
fonte