Obtener elementos DOM por nombre de clase

Respuestas:

154

Actualización: versión Xpath del *[@class~='my-class']selector css

Entonces, después de mi comentario a continuación en respuesta al comentario de hakre, sentí curiosidad y busqué en el código detrás Zend_Dom_Query. Parece que el selector anterior está compilado en el siguiente xpath (no probado):

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

entonces el php sería:

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

Básicamente, todo lo que hacemos aquí es normalizar el classatributo para que incluso una sola clase esté limitada por espacios, y la lista de clases completa esté limitada por espacios. Luego agregue la clase que estamos buscando con un espacio. De esta manera, estamos buscando y encontrando de manera efectiva instancias de my-class.


¿Usar un selector xpath?

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

Si solo es un tipo de elemento, puede reemplazarlo *con el nombre de etiqueta particular.

Si necesita hacer mucho de esto con un selector muy complejo, recomendaría Zend_Dom_Queryque admita la sintaxis del selector CSS (a la jQuery):

$finder = new Zend_Dom_Query($html);
$classname = 'my-class';
$nodes = $finder->query("*[class~=\"$classname\"]");
prodigitalson
fuente
encuentra la clase my-class2también, pero muy dulce. ¿Alguna forma de elegir solo el primero de todos los elementos?
Hakre
No creo que puedas sin xpath2 ... Sin embargo, el ejemplo de Zend_Dom_Query hace exactamente eso. SI no desea usar ese compkenet en su proyecto, entonces quizás quiera ver cómo están traduciendo ese selector css a xpath. Quizás DomXPath sea compatible con xpath 2.0, no estoy seguro de eso.
prodigitalson
1
porque classpuede tener más de una clase, por ejemplo: <a class="my-link link-button nav-item">.
prodigitalson
2
@prodigitalson: Esto es incorrecto ya que no refleja los espacios, intente //*[contains(concat(' ', normalize-space(@class), ' '), ' classname ')](Muy informativo: Selectores CSS y Expresiones XPath ).
Hakre
1
@babonk: sí, debe usarlo containsen combinación con concat... solo estamos discutiendo los detalles de rellenar los espacios en ambos lados de la clase que está buscando o solo rellenar un lado. Sin embargo, cualquiera debería funcionar.
prodigitalson
20

Si desea obtener el innerhtml de la clase sin el zend, puede usar esto:

$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
fuente
2
Falta punto y coma para la línea$classname = 'main-article'
Kamil
12

Creo que la forma aceptada es mejor, pero supongo que esto también podría 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
fuente
2
¿Dónde está el ejemplo para esto? Hubiera sido lindo.
robue-a7119895
Eso es genial. Tengo el elemento con la clase. Ahora quiero editar el contenido del elemento, como agregar hijo al elemento que contiene la clase. ¿Cómo agregar al niño y recrear HTML completo? Por favor ayuda. Esto es lo que he hecho. $classResult = getElementByClass($dom, 'div', 'm-signature-pad'); $classResult->nodeValue = ''; $enode = $dom->createElement('img'); $enode->setAttribute('src', $signatureImage); $classResult->appendChild($enode);
Keyur
1
para la modificación de dom por php Creo que es mejor usar phpquery github.com/punkave/phpQuery
dav
7

También hay otro enfoque sin el uso de DomXPatho Zend_Dom_Query.

Basado en la función original de dav, escribí la siguiente función que devuelve todos los hijos del nodo padre cuya etiqueta y clase coinciden con los 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;
}

supongamos que tiene una variable $htmlel siguiente 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>

El uso de getElementsByClasses tan simple como:

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

DOMDocument es lento de escribir y phpQuery tiene problemas de pérdida de memoria. Terminé usando:

https://github.com/wasinger/htmlpagedom

Para seleccionar una clase:

include 'includes/simple_html_dom.php';

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

Espero que esto ayude a alguien más también

iautomation
fuente