Michael: <div> mit bestimmter Klasse auslesen

Heyho!

Ich versuche mich gerade an den CURL-Funktionen bzw. dan der Weiterverarbeitung meiner eingelesenen Daten.
Ich versuche alle Elemente einer bestimmten Klasse mit ihren Kindern aus einem HTML-Quelltext zu extrahieren.

Bsp.:

<html>  
    <head>  
        <title>hund</title>  
    </head>  
    <body>  
        <!-- infos -->  
        <div class="foobar">  
            <h3>test1</h3>  
            <ul>  
                <li>bla</li>  
                <li>blub</li>  
            </ul>  
        </div>  
        <!-- infos -->  
        <div class="foobar">  
            <h3>test2</h3>  
            <ul>  
                <li>blubblub</li>  
                <li>blabla</li>  
            </ul>  
        </div>  
    </body>  
</html>

Die Kontainer mit der Klasse "foobar" sollen also Komplett eingelesen, der Rest ignoriert werden.

Mir fehlt der richtige Ansatz wie ich das lösen kann.
Könnte mir bitte jemand dabei helfen?

Meine spontane Idee war erstmal alles vor dem ersten <!-- infos --> zu entfernen und danach einfach mit explode() den String zu zerteilen.
Als Trennzeichen bietet sich dabei das <!-- infos --> an weil es immer vor dem Element steht das ich haben will.
Dabei werde ich aber auf Probleme mit dem letzten Element stoßen solange ich den String nicht nach dem schließenden </div> abschneide.

Klingt meine Idee gut? Wie sollte ich es stattdessen sonst lösen?

LG

  1. Hallo Michael,

    Die Kontainer mit der Klasse "foobar" sollen also Komplett eingelesen, der Rest ignoriert werden.

    Mir fehlt der richtige Ansatz wie ich das lösen kann.

    wenn Du selbst über die Ressource des zu parsenden HTMLs verfügst, kannst Du Dich zwar darauf verlassen, dass <!-- infos --> als Beginn des gewünschten Blocks notiert ist.

    Meine spontane Idee war erstmal alles vor dem ersten <!-- infos --> zu entfernen und danach einfach mit explode() den String zu zerteilen.
    Als Trennzeichen bietet sich dabei das <!-- infos --> an weil es immer vor dem Element steht das ich haben will.
    Dabei werde ich aber auf Probleme mit dem letzten Element stoßen solange ich den String nicht nach dem schließenden </div> abschneide.

    Genau; was willst Du als Abschluss heranziehen? </div> wird bei verschachtelten Strukturen mehrerer <div>-Elemente zu Fehlern führen. Also bliebe Dir nur übrig, ein eindeutiges Blockende ebenso zu notieren (vielleicht <!-- ende -->).

    Klingt meine Idee gut? Wie sollte ich es stattdessen sonst lösen?

    Da dies aber der Praxis nicht entspricht und auch ein herumfriemeln mit eigenen rekursiven Funktion schnell unübersichtlich und überproportional wird, würde ich Dir zum erheblich schwererem Geschütz raten, was alles schon inklusive kann: Nutze DOM!

    Gruß aus Berlin!
    eddi

    --
    Wenn der Weg zur Hölle mit guten Vorsätzen gepflastert ist, wäre der zum Himmel aus bösen Absichten betoniert. Was aber ist dann ein Weg voller Irrtümer?
    Wer weiß - vielleicht der Mittelweg. ;)
    1. Heyho!

      wenn Du selbst über die Ressource des zu parsenden HTMLs verfügst(...)

      Tu ich nicht.

      kannst Du Dich zwar darauf verlassen, dass <!-- infos --> als Beginn des gewünschten Blocks notiert ist.

      Ja das ist auch so ein Problem. Am besten wär schon das Element mit der gewünschten Klasse. Den Namen der Klasse kann ich ja dann nach belieben ändern.

      Genau; was willst Du als Abschluss heranziehen? </div> wird bei verschachtelten Strukturen mehrerer <div>-Elemente zu Fehlern führen.

      Das war halt meine Überlegung.

      Also bliebe Dir nur übrig, ein eindeutiges Blockende ebenso zu notieren (vielleicht <!-- ende -->).

      Genau das geht leider nicht, weil die Ressource ebend nicht mir gehört.

      (...)Nutze DOM!

      Ok ich habe jetzt folgendes versucht:

       $doc = new DOMDocument();  
       // $content ist die HTML-Ressource  
       $doc->loadHTML($content);  
       echo htmlspecialchars($doc->saveHTML());
      

      Das Gute daran ist, dass ich den Quelltext so ausgegeben bekomme.
      Aber über der Ausgabe steht:

      Warning: DOMDocument::loadHTML() [domdocument.loadhtml]: Unexpected end tag : script in Entity, line: 33 in .............
      Warning: DOMDocument::loadHTML() [domdocument.loadhtml]: Unexpected end tag : div in Entity, line: 302 in .............

      Beide Fehler kann ich auf den ersten Blick auch nicht nachvollziehen.
      Das </script> in Zeile 33 schließt z.B. tatsächlich ein vorheriges <script>.
      Deutet das trotzdem darauf hin, dass der Quelltext nicht ganz sauber ist?
      Wenn das so ist versteh ich das Problem nicht, denn bei php.net steht:

      Unlike loading XML, HTML does not have to be well-formed to load.

      Die Ausgabe der Warnung kann ich sicher unterdrücken aber ich würde den Grund schon gern kennen.

      Ok, dann muss ich mich jetzt wohl durch die DOM-Funktionen kämpfen.
      Hast du vielleicht noch einen Tipp für mich welche Funktion ich nutzen kann oder muss?

      LG

      1. Re:

        Beide Fehler kann ich auf den ersten Blick auch nicht nachvollziehen.
        Das </script> in Zeile 33 schließt z.B. tatsächlich ein vorheriges <script>.
        Deutet das trotzdem darauf hin, dass der Quelltext nicht ganz sauber ist?

        m. M. n. ja; aber schicke es Doch ganz einfach mal durch einen Validator.

        Ok, dann muss ich mich jetzt wohl durch die DOM-Funktionen kämpfen.

        Das halte ich mit Deinen und meinen Vorüberlegung wirklich für das Bester. ;)

        Gruß aus Berlin!
        eddi

        --
        Wenn der Weg zur Hölle mit guten Vorsätzen gepflastert ist, wäre der zum Himmel aus bösen Absichten betoniert. Was aber ist dann ein Weg voller Irrtümer?
        Wer weiß - vielleicht der Mittelweg. ;)
        1. Heyho!

          Das halte ich mit Deinen und meinen Vorüberlegung wirklich für das Bester. ;)

          Das Problem ist, dass es den entsprechenden Absatz nich auf Deutsch gibt, deshalb wär ich für einen kleinen Schubs in die richtige Richtung sehr dankbar.

          LG

        2. Heyho!

          Deutet das trotzdem darauf hin, dass der Quelltext nicht ganz sauber ist?
          m. M. n. ja; aber schicke es Doch ganz einfach mal durch einen Validator.

          Der Validator bemängelt diese JS-Zeile:
          $('#flash-wrap-inner').append('<div id="right-flash"><img src="http://*******'" alt=""/></div>');
          </script>
          (Sternchen sind von mir)

          Dann noch ein paar Kleinigkeiten wie fehlende alt-Attribute.
          Was ich interessant finde ist, dass mir der Validator und der Validator unterschiedliche Ergebnisse liefern. Ersterer 7 Fehler, der zweite "nur" 6.
          Wenn ich einen finde der 0 anzeigt, nehm ich den ;)

          Das Problem ist aber, dass ich an dem Quelltext sowieso nichts ändern kann also muss ich mit dem arbeiten was ich habe ;)
          Es wird ja auch ein DOMObject erzeugt, also reicht es wohl ein @ unterzubringen oder die Fehlerausgabe für das Script zu deaktivieren.

          Ok, dann muss ich mich jetzt wohl durch die DOM-Funktionen kämpfen.
          Das halte ich mit Deinen und meinen Vorüberlegung wirklich für das Bester. ;)

          Wie gesagt tu ich mich ein bisschen schwer mit dem Fachenglisch, deshalb wär ich noch für einen kleinen Hinweis oder eine Seite mit guter Übersetzung dankbar.

          LG

          1. Hi!

            Der Validator bemängelt diese JS-Zeile:
            $('#flash-wrap-inner').append('<div id="right-flash"><img src="http://*******'" alt=""/></div>');
            </script>

            Zu recht, denn in einem Script-Bereich beendet das erste Auftreten der Sequenz </ selbigen. Wenn du das so notierst

            ... alt=""/><' + '/div>');

            sollte er damit kein Problem mehr haben.

            Lo!

            1. Heyho!

              Zu recht, denn in einem Script-Bereich beendet das erste Auftreten der Sequenz </ selbigen. Wenn du das so notierst (...)

              Wie oben geschrieben, bin ich leider nicht der Eigentümer der Ressource.
              Ich muss also mit dem arbeiten was ich zur Verfügung habe.

              Ich unterdrücke die Warnung einfach mit:

              // $content ist der HTML-String  
              @$doc->loadHTML($content);
              

              Das ist zwar nicht schön, was besseres ist mir aber auch nicht eingefallen.

              LG

              1. Hi!

                Wie oben geschrieben, bin ich leider nicht der Eigentümer der Ressource.
                Ich muss also mit dem arbeiten was ich zur Verfügung habe.

                Warum? Kannst du nicht mit dem "Eigentümer der Ressource" reden und ihn auf seinen Fehler hinweisen?

                Lo!

                1. Heyho!

                  Warum? Kannst du nicht mit dem "Eigentümer der Ressource" reden und ihn auf seinen Fehler hinweisen?

                  Könnte ich, aber ob er ihn behebt ist eine andere Frage.
                  Bei externen Quellen kann man sich leider sowieso nicht darauf verlassen, dass sie 'sauber' sind.
                  Für den Fall, dass das Document gar nicht geparst werden kann, wird ein Fehler ausgegeben.
                  Ansonsten kann ich mit der Unterdrückung der Warnung erstmal ganz gut leben solange sonst alles läuft wie ich es wollte :)

                  Das Script ist jetzt übrigens fertig und alle foreach() durch for() ersetzt.
                  Läuft alles bestens, danke für die Hilfe!

                  LG

  2. Heyho!

    Mit kleinen Schritten komme ich ans Ziel :)

    Mit den DOM-Funktionen von PHP komm ich schon mal an meine gewünschten Kontainer:

    $needle = 'foobar';  
    $doc = new DOMDocument();  
    // $content ist die per CURL eingelesene HTML-Datei  
    $doc->loadHTML($content);  
    // alle divs finden - '*' für alle Elemente  
    $datas = $dom_ele->getElementsByTagName('div');  
    // divs verarbeiten  
    foreach($datas as $data){  
        if($data->getAttribute('class') == $needle){  
    	// wie weiter??  
        }  
    }
    

    Wie aber komme ich an die Kinder und deren Inhalt?
    Ich würde gern bestimmte Attribute und Inhalte der Elemente auslesen und die Werte in ein Array speichern.

    Z.B.:

    <div class="foobar">  
        <h3>1. Überschrift</h3>  
        <dl>  
            <dt class="temp-23"></dt>  
            <dd class="temp">Temperatur</dt>  
        </dl>  
    </div>  
    <div class="foobar">  
        <h3>2. Überschrift</h3>  
        <dl>  
            <dt class="temp-10"></dt>  
            <dd class="temp">Temperatur</dt>  
        </dl>  
    </div>
    

    soll zu einem Array werden:

    array(  
        [0]->  
            header->1. Überschrift  
            temp->23  
        [1]->  
            header->2. Überschrift  
            temp->10  
    )
    

    Ich werde also mein foreach()-Konstrukt erweitern müssen und dann mein Array mit den Daten befüllen. Nur habe ich absolut keine Ahnung, wie ich die Kindelemente durchlaufe.

    Könnte mir da vielleicht noch einmal jemand helfen?

    LG

    1. Guten Morgen Michael,

      parallel zu den eigenen Programmierschritten lohnt es sich immer auch einen Blick in die DOM-Spezifikation zu werfen. Daraus ergeben sich dann ganz interessante Einblicke, wie PHP zu funktionieren hat.

      $needle='foobar';  
      $doc   =new DOMDocument();  
      $doc->loadHTML($content);  
              # Wo kommt $dom_ele her?  
      $datas =$dom_ele->getElementsByTagName('div');  
        
      # Methode getElementsByTagName() gibt eine [link:http://de2.php.net/manual/de/class.domnodelist.php@title=DOMNodeList] zurück  
      # vgl. [link:http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-536297177@title=NodeList der Spezifikation] dabei ist [link:http://de2.php.net/manual/de/domnodelist.item.php@title=DOMNodeList::item()]  
      # Teil des Spezifikationsinterfaces, Deine [code lang=php]foreach
      ~~~-Schleife kann  
      # funktionieren, wird auch funktionieren, muss es aber -von der  
      # Spezifikation her- nicht.  
      foreach($datas as $data){  
          if($data->getAttribute('class') == $needle){  
              # $date ist vom DOM aus betrachtet [DOMNode](http://de2.php.net/manual/de/class.domnode.php) vgl. [Node](http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1950641247)  
              # Für dich sollten also die Methode [hasChildNodes()](http://de2.php.net/manual/de/domnode.haschildnodes.php),  
              # [removeChild()](http://de2.php.net/manual/de/domnode.removechild.php) und Eigenschaften firstChild  
        
              # ach ja \*BG\*; in Javascript wird sowas dann so gelöst:  
              # [Node](http://de.selfhtml.org/javascript/objekte/node.htm) (ganze Seite bitte nachvollziehen)  
              # Das sollte Hilfestellung genug sein.  
          }  
      }[/code]  
        
      Ansonsten sieht das doch alles schon mal recht gut aus. :)  
        
        
      Gruß aus Berlin!  
      eddi
      
      -- 
      Wenn der Weg zur Hölle mit guten Vorsätzen gepflastert ist, wäre der zum Himmel aus bösen Absichten betoniert. Was aber ist dann ein Weg voller Irrtümer?  
      Wer weiß - vielleicht der Mittelweg. ;)
      
      1. In Berichtigung:

        # Für dich sollten also die Methode hasChildNodes(),
                # removeChild() und Eigenschaften firstChild

        von Interesse sein. Aber auch nextSibling in einer Schleife
                     würde gehen, um an die einzelnen Kindknoten zu gelangen.

        'tschuldigung, da fehlt derzeit die üblich morgentliche Dosis Nikotin. ;(

      2. Heyho!

        # Wo kommt $dom_ele her?
        $datas =$dom_ele->getElementsByTagName('div');

        Tschuldigung, ein Copy&Past-Fehler.

        (..) dabei ist DOMNodeList::item()

        Teil des Spezifikationsinterfaces

        Ich habe versucht item() zu verwenden, habe aber immer Fehler bekommen. Auf php.net steht leider gar nichts dazu. Ich weiß nicht wie man damit arbeitet.

        Deine foreach-Schleife kann

        funktionieren, wird auch funktionieren, muss es aber -von der

        Spezifikation her- nicht.

        Ich habe das foreach()-Konstrukt von php.net. Erscheint mir auch logisch und kann nicht falsch sein wenn es da so steht, dachte ich mir.

        Was du mir mit den Links sagen willst versteh ich leider nicht, mein englisch ist nicht so gut. Wie sollte ich es denn machen?

        Folgendes funktioniert:

        $needle = 'foobar';  
        $doc = new DOMDocument();  
        // $content ist die per CURL eingelesene HTML-Datei  
        $doc->loadHTML($content);  
        // alle divs finden - '*' für alle Elemente  
        $datas = $doc->getElementsByTagName('div');  
        // array anlegen  
        $array = array();  
        // counter mitschleifen  
        $i = 0;  
        // divs verarbeiten  
        foreach($datas as $data){  
            if($data->getAttribute('class') == $needle){  
                // array erweitern  
                $array[$i] = array();  
                // Überschrift finden:  
                $header = $data->getElementsByTagName('h3');  
                foreach($header as $data_header){  
                    $array[$i]['header'] = $data_header->nodeValue;  
                    break;  
                }  
                // Daten finden:  
                $tempe = $data->getElementsByTagName('dt');  
                foreach($tempe as $data_temp){  
                    if(substr($data_temp->getAttribute('class'), 0, 5) == 'temp-'){  
                        $array[$i]['temp'] = substr($data_temp->getAttribute('class'), 5, 8);  
                    }  
                }  
                $i++;  
            }  
        }  
          
        print_r($array);
        

        Wie müsste ich es richtig machen? Vielen Dank!

        LG

        1. Heyho!

          Ok, ich bin auch noch nicht richtig bei der Sache...

          Ich habe gerade folgendes bei php.net gelesen:

          ´´It seems that with zend.ze1_compatibility_mode on, the only way to iterate over the items list is with :
          for ($i = 0; $i < $nodeList->length; ++$i) {
              $nodeName = $nodeList->item($i)->nodeName;
              $nodeValue = $nodeList->item($i)->nodeValue;
          }´´

          Ok ich werde also versuchen, die foreach()-Schleifen zu for()-Schleifen zu ändern. Damit wär ich dann richtig und fertig oder habe ich noch etwas übersehen?

          LG

          1. Re:

            Damit wär ich dann richtig und fertig oder habe ich noch etwas übersehen?

            Auch auf die Gefahr hin, dass Du mich haust, kann ich nur sagen, Du hast von uns beiden nunmehr die weitreichenderen, praktischen Erfahrungen beim Gebrauch von DOM mittels PHP hast. Ich noch nix DOM-Objekt in PHP genutzt, ich Anfänger sein.
            (Ich weiß nur, wo steht, wie es geht. Das aber ist [leider] Das Wichtigste. ;)

            Gruß aus Berlin!
            eddi

            --
            Wenn der Weg zur Hölle mit guten Vorsätzen gepflastert ist, wäre der zum Himmel aus bösen Absichten betoniert. Was aber ist dann ein Weg voller Irrtümer?
            Wer weiß - vielleicht der Mittelweg. ;)
            1. Heyho!

              Auch auf die Gefahr hin, dass Du mich haust, kann ich nur sagen, Du hast von uns beiden nunmehr die weitreichenderen, praktischen Erfahrungen beim Gebrauch von DOM mittels PHP hast. (...)

              Wenn der Schüler zum Meister wird...
              Jap, das hatte ich jetzt nicht erwartet :D

              Eigentlich ist der Einstieg relativ simpel wenn einem erstmal klar geworden ist was Nodes und Elements sind und wie man mit diesen umgehen muss.
              Wenn man mehr Erfahrung (als ich) mit JavaScript hat, sollte der Einstieg noch einfacher sein.

              Allerdings find ich so grundlegende Sachen wie nodeValue bei php.net ziemlich versteckt. Das musste ich mir ergooglen.
              Zu meinem Leid ist auch fast alles auf englisch. Aber hat ja geklappt :)

              Danke nochmal!

              LG