Obtenir des éléments DOM par nom de classe

Réponses:

154

Mise à jour: version Xpath du *[@class~='my-class']sélecteur css

Donc, après mon commentaire ci-dessous en réponse au commentaire de hakre, je suis devenu curieux et j'ai regardé le code derrière Zend_Dom_Query. Il semble que le sélecteur ci-dessus soit compilé sur le chemin xpath suivant (non testé):

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

donc le php serait:

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

Fondamentalement, tout ce que nous faisons ici est de normaliser l' classattribut afin que même une seule classe soit délimitée par des espaces et que la liste complète des classes soit délimitée par des espaces. Ajoutez ensuite la classe que nous recherchons avec un espace. De cette façon, nous recherchons et trouvons uniquement des instances de my-class.


Utiliser un sélecteur xpath?

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

S'il ne s'agit que d'un seul type d'élément, vous pouvez remplacer le *par la variable particulière.

Si vous avez besoin d'en faire beaucoup avec un sélecteur très complexe, je recommanderais celui Zend_Dom_Queryqui prend en charge la syntaxe du sélecteur CSS (à la jQuery):

$finder = new Zend_Dom_Query($html);
$classname = 'my-class';
$nodes = $finder->query("*[class~=\"$classname\"]");
prodigitalson
la source
trouve la classe my-class2aussi, mais assez douce. Un moyen de ne choisir que le premier de tous les éléments?
hakre
Je ne pense pas que vous puissiez le faire sans xpath2 ... Cependant, l'exemple de Zend_Dom_Query fait exactement cela. SI vous ne voulez pas utiliser ce compkenet dans votre projet, vous voudrez peut-être voir comment ils traduisent ce sélecteur css en xpath. Peut-être que DomXPath prend en charge xpath 2.0 - je n'en suis pas sûr.
prodigitals le
1
parce que classpeut avoir plus d'une classe par exemple: <a class="my-link link-button nav-item">.
prodigitals le
2
@prodigitalson: Ceci est incorrect car cela ne reflète pas les espaces, essayez //*[contains(concat(' ', normalize-space(@class), ' '), ' classname ')](très informatif: sélecteurs CSS et expressions XPath ).
hakre
1
@babonk: oui, vous devez utiliser containsen combinaison avec concat... nous discutons juste des détails du remplissage des espaces des deux côtés de la classe que vous recherchez ou ne rembourrez qu'un côté. Les deux devraient fonctionner cependant.
prodigitals le
20

Si vous souhaitez obtenir le code interne de la classe sans zend, vous pouvez utiliser ceci:

$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
la source
2
Point-virgule manquant pour la ligne$classname = 'main-article'
Kamil
12

Je pense que la méthode acceptée est meilleure, mais je suppose que cela pourrait également fonctionner

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
la source
2
Où en est l'exemple? Ça aurait été bien.
robue-a7119895
C'est génial. J'ai eu l'élément avec la classe. Maintenant, je veux modifier le contenu de l'élément, comme ajouter un enfant à l'élément contenant la classe. Comment ajouter l'enfant et recréer tout le HTML? Veuillez aider. C'est ce que j'ai fait. $classResult = getElementByClass($dom, 'div', 'm-signature-pad'); $classResult->nodeValue = ''; $enode = $dom->createElement('img'); $enode->setAttribute('src', $signatureImage); $classResult->appendChild($enode);
Keyur
1
pour la modification dom par php, je pense qu'il est préférable d'utiliser phpquery github.com/punkave/phpQuery
dav
7

Il existe également une autre approche sans l'utilisation de DomXPathou Zend_Dom_Query.

Sur la base de la fonction originale de dav, j'ai écrit la fonction suivante qui renvoie tous les enfants du nœud parent dont la balise et la classe correspondent aux paramètres.

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

supposons que vous ayez une variable $htmlle HTML suivant:

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

l'utilisation de getElementsByClassest aussi simple que:

$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
la source
6

DOMDocument est lent à taper et phpQuery a de graves problèmes de fuite de mémoire. J'ai fini par utiliser:

https://github.com/wasinger/htmlpagedom

Pour sélectionner une classe:

include 'includes/simple_html_dom.php';

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

J'espère que cela aide aussi quelqu'un d'autre

iautomation
la source