Sep 2008

Der kleine DOM-Inspektor

 
 

Für die Analyse und Manipulation des Dokuments ist die Kenntnis der DOM-Struktur des Dokuments unbezahlbar. Safari und Firefox besitzen DOM-Inspektoren, in denen der Knotenbaum aufgeblättert werden kann – in Opera und Internet Explorer steht eine derartige Erweiterung nicht zur Verfügung.

Die strenge Baumstruktur des DOM macht es uns allerdings einfach, einen kleinen DOM-Inspektor mit Javascript zu programmieren.

Alle Knoten des Dokuments erfassen

Die Zutaten sind einfach gehalten:

  • ein globales Array tree, in dem alle Knoten des DOM gesammelt werden
  • ein Objekt Node, das die Charakteristika eines Knotens speichert
  • eine rekursive Funktion goInto(), die den Dokumentenbaum beginnend beim document-Objekt durchläuft

Wenn die Seite vollständig geladen wurde, ruft analyseAll() die rekursive Funktion goInto() auf. goInto(obj, level) eine Funktion, die rekursiv durch den vollständigen Knotenbaum eines Dokuments läuft und alle Knoten im globalen Array tree speichert. Der initiale Aufruf von goInto() erfolgt mit dem document-Objekt.

Für jeden Knoten wird ein neues Objekt Node erzeugt, in dem level – die Ebene des Knotens –, der Name des Knotens und bei einem Textknoten der Inhalt des Textknotens gespeichert werden und jedes Node-Objekt wird wiederum im Array tree gespeichert. Am Ende von goInto() enthält das Array tree also die „serialisierte" Folge aller Knoten des Dokuments mitsamt ihren Leveln.

Array tree
IndexLevelNameWertObjekt
01HEADleerleer
12#textleerleer
22METAleerleer
32#textleerleer
42TITLEleerleer
53#text"Der kleine DOM-Inspektor"leer
62LINKleerleer

Der Kern des Skripts

Bei jedem Aufruf prüft goInto(), ob das Objekt, mit dem die Funktion aufgerufen wurde, tatsächlich Kinder hat: if (obj.hasChildNodes). Wenn dies der Fall ist, wird level um Eins nach oben gezählt und das Skript sammelt die Kindknoten des Objekts in ein Array tags: var tags = obj.childNodes;.

Die for-Anweisung iteriert durch alle Kinder des Knotens. Wenn der Knoten ein Textknoten ist und einen Wert aufweist (if (tags[i].nodeValue)), wird der Wert in einem String gespeichert. Für jeden gefundenen Knoten wird ein Node-Objekt erzeugt (var elem = new Node(level, tags[i].nodeName, value);) und das Node-Objekt in einem neuen Array-Element (tree.push(elem);) gespeichert.

addEvent(window, "load", analyseAll);

/**
  Initialer Event Handler, ruft goInto auf, sobald das Dokument im Fenster 
  geladen ist
*/

function analyseAll()
{
	goInto(document, -1);
	return false;
}

var tree = new Array; 

function Node(level, name, value, obj)
{
   this.level = level;
   this.name = name; 
   this.value = value;
   this.obj = obj;
}

function goInto(obj, level)
{
   if (obj.hasChildNodes) {
      level++;
      var tags = obj.childNodes;
      for (var i=0; i<tags.length; i++) {
         var value = "";
         var name = tags[i].nodeName;
         if (tags[i].nodeValue) {
            if (tags[i].nodeValue.match(/\S/)) {
               value = tags[i].nodeValue;
               if (tags[i].nodeName != "#comment") {
                  name = "";
               }
            } 
         }
         var elem = new Node(level, name, value);
         tree.push(elem);
         if (tags[i].hasChildNodes) {
            goInto(tags[i], level);
         }
      }
   }
}

Am Ende prüft das Skript noch, ob der aktuelle Knoten Kind­knoten hat, und falls dies der Fall ist, ruft die Funktion sich selbst mit dem aktuellen Knoten auf und dem aktuellen level auf: goInto(tags[i], level);.

In diesem Dokument braucht die Erzeugung der Knotenliste ein paar Sekunden – immerhin entsteht hier schon allein durch die Länge der Navigationsleiste eine sehr lange Liste.

Auffallend sind die vielen leeren Textknoten in der Liste. Sie entstehen durch Leerzeichen und Zeilenumbrüchen zwischen den Tags. Die Namen der Knoten erscheinen in Großbuchstaben, so wie der Browser sie in der Eigenschaft nodeName zurückgibt.

Ein Vergleich des Knotenbaums in den verschiedenen Browsern

Knotenliste in FireFoxin Opera
+ HEAD
- BODY
◦ | |#text
◦ | |#comment Ein Kommentar
◦ | |#text
◦ + DIV
◦ | |#text
◦ + DIV
◦ | |#text
◦ + SCRIPT
◦ | |SCRIPT
◦ | |#text
◦ + SCRIPT
◦ | |#text
in Safariin Internet Explorer
+ HEAD
- BODY
◦ | |#text
◦ | |#comment Ein Kommentar
◦ | |#text
◦ + DIV
◦ | |#text
◦ + DIV
◦ | |#text
◦ + SCRIPT
◦ | |SCRIPT
◦ | |#text
◦ + SCRIPT
◦ | |#text
◦ | |#text
◦ | |#text

Aus diesen leichten Abweichungen heraus müssen Skripte, die im DOM-Baum mit nextSibling, previousSibling und childNodes navigieren, die Knotentypen sorgfältig mit nodeType analysieren: Was in einem Browser ein nextSibling ist, wird von Safari als Kommentar unterdrückt und nicht im Knotenbaum aufgeführt. Leere Textknoten, die nur Zeilenumbrüche des Quelltextes enthalten sind in Internet Explorer nicht als DOM-Knoten implementiert, müssen aber in den anderen Browsern sorgfältig untersucht werden.

if (obj.nodeType === 3) {
   if (obj.nodeValue.match(/\S/)) {
      /** 
      Ein „echter“ Textknoten – kein „leerer“ Knoten, 
      der ausschliesslich Whitespace der Form \n, \r, \t enthält
      */
   } 
}

/\S/ ist ein regulärer Ausdruck, der alle einzelnen Zeichen, die kein Whitespace sind, erkennt. Die if-Abfrage läßt also alle Inhalte zu, in denen mehr als ein \n, \r, \t steht.

   


Copyright © 2000 - 2010 Media Engineering Alle Rechte vorbehalten
Design + Programmierung Media Engineering U. Häßler 47506 Neukirchen-Vluyn • Impressum und Nutzungsbestimmungen